本系列涉及的內容如下:
- 元件庫基礎搭建,react + ts + less
- 專案規範,包括但不限於 prettier、eslint、stylelint、husky、lint-staged、commitlint
- pnpm monorepo + turborepo 整合
- gulp + webpack 構建 esm、cjs 和 umd
- storybook 文件整合
此係列不包含釋出 npm 和構建 CI 流程。
本系列目錄如下:
- 專案初始化搭建+程式碼規範整合
- 元件庫多產物編譯及文件編寫
此係列不是所謂的最佳實踐,流行元件庫經過迭代都非常完善,不是一人力能完成的,而本系列只是將功能上的核心進行了總結,不足之處依然很多
- 閱讀本系列需要對元件庫的構成有一定的瞭解,不多,一點點即可,純小白可能會有點難入手。
- 後續有時間會追加一篇 React Icon 元件庫的構建。
本篇的內容可直接檢視 commit:https://github.com/json-q/rc-library-templete/commit/edaa1adb6772babf475612a754566f0dbefa52fb
初始化專案
mkdir react-library-templete
cd react-library-templete
pnpm init
package.json 初始化工作
由於是 pnpm monorepo 專案,必須使用 pnpm,可以在 package.json
中做一下限制
"scripts": {
"preinstall": "npx only-allow pnpm",
},
"packageManager": "pnpm@9.12.3",
"engines": {
"node": ">=20"
},
packageManager
可以不寫,主要作用是為了約束開發都是用這個版本,避免包管理工具版本不一致導致的相容問題engines
指定專案使用的 node 版本
注意: 一旦宣告 packageManager
,在執行 install
時,會提示你是否替換成指定版本,此替換為全域性的版本替換,之前的安裝版本將被覆蓋
安裝基礎依賴
使用 TypeScript + Less 編寫,肯定要安裝 typescript 和 less,如果涉及到 node 相關的,還可以再安裝一個 @types/node 型別提示
pnpm i typescript less @types/node -D
專案規範
整合 eslint
需提前安裝 ESLint
外掛
pnpm dlx @eslint/create-config
eslint 基本結構有了,再裝一些輔助外掛
pnpm i eslint-plugin-react-hooks eslint-plugin-jsx-a11y eslint-plugin-react-refresh -D
eslint-plugin-jsx-a11y
是一個無障礙訪問的 eslint 檢測,可按需安裝eslint-plugin-react-refresh
檢測 react 元件是否可以安全重新整理,可按需安裝
在 eslint.config.mjs
中新增這幾個外掛
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import reactRefresh from 'eslint-plugin-react-refresh';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import reactHooks from 'eslint-plugin-react-hooks';
/** @type {import('eslint').Linter.Config[]} */
export default [
{files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"]},
{languageOptions: { globals: globals.browser}},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
jsxA11y.flatConfigs.recommended,
{
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
},
},
{
ignores: [
'**/dist/*',
'**/es/*',
'**/lib/*',
'node_modules',
'*.woff',
'.ttf',
'.vscode',
'.idea',
'.husky',
'.local',
'/bin',
'.eslintcache',
'.stylelintcache',
],
},
];
eslint
配置過程中,可以新建一個 ts 檔案,寫一些不規範程式碼,時刻檢視 eslint 的檢測是否會失效,因為由於寫法錯誤也會導致 eslint 的配置失效。
整合 Prettier
需提前安裝 Prettier
外掛
pnpm i prettier eslint-plugin-prettier eslint-config-prettier -D
新建 .prettierrc.cjs
/** @type {import("prettier").Config} */
module.exports = {
printWidth: 120,
useTabs: false,
singleQuote: true,
proseWrap: 'never',
};
新建 .prettierignore
**/dist/*
**/es/*
**/lib/*
**/.local
**/node_modules/**
**/*.svg
**/*.sh
.eslintcache
.stylelintcache
此時你可以發現 .prettierrc.cjs
被 eslint 報錯了
這是由於 module 是 node 環境的全域性變數,而 eslint 不識別 node 環境。在 eslint.config.mjs
中的 globals
屬性新增 node 全域性變數
export default [
// ...
{languageOptions: { globals: {...globals.browser,...globals.node}}},
// ...
];
此時就沒問題了
prettier 和 eslint 的整合
前邊寫過的都省略掉了,只列出更改的部分。該部分讓 eslint 和 prettier 做了整合,並新增了一項 eslint 關閉校驗的規則 @typescript-eslint/no-explicit-any
,即此時是允許使用 any
的(預設禁止使用 any)
// ...
import eslintConfigPrettier from 'eslint-config-prettier';
import prettierRecommended from 'eslint-plugin-prettier/recommended';
/** @type {import('eslint').Linter.Config[]} */
export default [
// ...
eslintConfigPrettier,
prettierRecommended,
{
// ...
rules: {
// ...
'@typescript-eslint/no-explicit-any': 'off',
},
},
];
不出意外的話,此時就能看到 eslint.config.mjs
中存在大量的 prettier 報錯提示。
在根目錄新建一個 .vscode
資料夾,在資料夾內部新建 settings.json
檔案
{
"search.exclude": {
"**/node_modules": true,
"dist": true,
"es": true,
"lib": true,
"storybook-static": true,
"pnpm-lock.yaml": true
},
"editor.codeActionsOnSave": {
"source.fixAll": "explicit"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"stylelint.validate": ["css", "less", "scss", "sass", "html"]
}
其實主要就是自動儲存格式化,然後回到 eslint 的檔案,儲存就會自動格式化,如果格式化後依然報錯,可以嘗試重啟 vscode。
編碼問題
在 windows 上預設是 CRLF 編碼,而 prettier 認為這是一種錯誤的編碼方式,格式化也不行。
- 安裝
EditorConfig for VS Code
外掛 - 根目錄新建
.editorconfig
,寫入內容,再次儲存,就可以看到編碼已經變成LF
了
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
使用 .gitattributes 解決多環境編碼不一致問題
editconfig 只是解決本地開發的編碼問題,當你提交到 git 後,再拉取一個全新的,如果是 win 系統,依然是 CRLF。
為了解決這個問題,可以在程式碼提交時做格式上的統一,在根目錄新建 .gitattributes
,統一編碼為 LF
,且檔案類的不做處理
* text=auto eol=lf
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary
Stylelint 整合
由於使用 less 開發,所以就額外安裝了 less 的解析和校驗外掛
pnpm i stylelint stylelint-config-recommended stylelint-config-standard-less stylelint-prettier stylelint-order stylelint-config-recess-order postcss-less -D
這幾個我就不過多介紹了,其中 stylelint-order
和 stylelint-config-recess-order
就是 css 排序外掛和排序規則,兩個結合使用(可選安裝)。
根目錄新建檔案 .stylelintrc.cjs
/** @type {import("stylelint").Config} */
module.exports = {
plugins: ['stylelint-order'],
customSyntax: 'postcss-less',
extends: [
'stylelint-config-recommended',
'stylelint-prettier/recommended',
'stylelint-config-recess-order',
'stylelint-config-standard-less',
],
rules: {
// 規則自定義即可
'color-function-notation': null,
'less/no-duplicate-variables': null,
},
ignoreFiles: ['**/.js', '/*.jsx', '/.tsx', '**/.ts', '**/dist/**', '**/es/**', '**/lib/**', '**/node_modules/**'],
};
完畢之後可以隨便建一個測試的 less 檔案,寫一些內容來測試外掛是否生效
由此可見,排序外掛是生效了,此時儲存程式碼就可以自動修復排序規則,那這個 stylelint 的配置應該也沒什麼問題。
如果沒有生效,嘗試重啟 vscode,且每次更改 stylelint 的配置後,都建議重啟驗證
scripts 新增 fix 命令(可忽略)
package.json
新增 script
命令
"scripts": {
"preinstall": "npx only-allow pnpm",
"lint:eslint": "eslint . --fix --cache",
"lint:stylelint": "stylelint \"**/*.{less,css}\" --fix --cache"
},
monorepo 基本結構搭建
這裡不介紹 monorepo 單體倉庫的作用,能看到該文章的應該都對其有一定的瞭解。
該專案的多包倉庫思路是:
packages
資料夾作為元件開發的核心模組- 多個子包都需要 tsconfig 的配置,那就可以把這個配置抽離出來
- icon 元件庫作為一個單獨的包
- 其它廣泛使用的元件為元件庫核心包(常用 util hook 什麼的也可以抽離,這裡不再做更細緻劃分)
storybook
文件作為單獨的模組site
元件庫網站作為單獨模組(可選)
此時結構目錄應該是
- packages
- components
- icons
- tsconfig
- site
- storybook
目錄新建完畢後,根目錄新建 pnpm-workspace.yaml
packages:
- packages/*
- site
- storybook
此時 monorepo 的基本目錄結構就已經搭建好了
初始化 packages 目錄的子包
其中 site
和 storybook
先設為 TODO(待辦),優先整理 packages
下的子包
packages
下的包統一初始化執行 pnpm init
命令
tsconfig
進入 tsconfig 目錄,將 package.json
中的 name
修改為你喜歡的包名,後續都是以 name
欄位的名字安裝依賴,由於專案內使用,設定成 private 即可,我設定的 rclt
就是 react-component-library-templete
的簡寫。
{
"name": "rclt-tsconfig",
"version": "0.0.1",
"description": "",
"keywords": [],
"author": "",
}
json 內容僅供參考
新建 base.json
檔案
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"composite": false,
"declaration": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"allowImportingTsExtensions": true,
"allowJs": true,
"inlineSources": false,
"isolatedModules": true,
"module": "ES2020",
"moduleResolution": "Bundler",
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strictNullChecks": true
},
"exclude": ["dist", "build", "es", "lib", "node_modules"]
}
新建 react-library.json
檔案
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"module": "ES6",
"target": "ES5",
"jsx": "react",
"lib": ["DOM", "ES2015"]
}
}
此時就可以在根目錄新增 rclt-tsconfig
依賴
"devDependencies": {
"rclt-tsconfig": "workspace:*",
}
執行 pnpm i 安裝,新建一個 tsconfig.json
來使用這個包
{
"extends": "rclt-tsconfig/base.json",
"compilerOptions": {
"noEmit": true
},
"include": ["packages/**/*.tsx", "packages/**/*.ts"]
}
不要問我為什麼不使用命令安裝,問就是沒找到根目錄安裝子包的命令(不會)
components
將 rclt-tsconfig
連結到 components
包下,以下命令的意思就是從本地找 rclt-tsconfig
安裝到 rclt-components
下
rclt-components
是 components 包package.json
中的name
。-workspace
是從本地查詢- 暴力一點的話直接在
package.json
中寫入包依賴,比如"rclt-tsconfig": "workspace:*"
,再執行 pnpm install 即可。
pnpm --filter rclt-components add rclt-tsconfig -D -workspace
新建 tsconfig.json
{
"extends": "rclt-tsconfig/react-library.json",
"compilerOptions": { "emitDeclarationOnly": true }, // 只生成宣告檔案
"include": ["src"]
}
沒問題的話,點 extends
的路徑連結是可以跳到 json 檔案的
提交規範
這些依賴的整合都是在根目錄進行的,記得安裝的時候帶上 -w
來指定在根工作區安裝。
husky + lint-staged 整合
安裝依賴之前的準備工作
在此之前,先把我們的專案 git 初始化一下,執行 git init
命令,最好再建一個 git 倉庫,關聯原生代碼,這部分我就不贅述了,大家應該都會。
根目錄新建 .gitignore
,以下是我的檔案內容,大家可自行修改忽略檔案(我個人習慣不上傳 lock 依賴,除非特殊情況需要鎖定版本)
# Dependencies
node_modules
.pnp
.pnp.js
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Testing
coverage
# Turbo
.turbo
# Vercel
.vercel
# Build Outputs
.next/
out/
build
dist
es
lib
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.DS_Store
*.pem
# cache
.eslintcache
.stylelintcache
# lock
pnpm-lock.yaml
整合依賴
安裝並初始化 husky
pnpm i husky lint-staged -D -w
# 初始化 husky
npx husky init
執行完畢 script 中會新增一個 husky 命令,根目錄會生成 .husky
檔案,其中有一個 pre-commit
指令碼檔案,寫入以下內容
npx lint-staged
這件事就是在程式碼 commit 之前,執行 lint-staged
做一些事情,比如檢測程式碼規範,格式化程式碼等,接下來就幹這個事。
根目錄新建 .lintstagedrc
{
"packages/**/*.{js,jsx,ts,tsx}": ["eslint --fix --cache", "prettier --write"],
"packages/**/package.json": ["prettier --write"],
"packages/**/*.{css,less}": ["stylelint --fix --cache", "prettier --write"],
"**/*.md": ["prettier --write"]
}
內容應該都能看得懂,就是匹配到的檔案執行相應的命令
commitlint 整合
pnpm i @commitlint/cli @commitlint/config-conventional -D -w
根目錄新建 commitlint.config.js
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
};
.husky
資料夾下新建 commit-msg
npx --no-install commitlint --edit $1
測試整合是否正常工作
我們把 lint-staged 和 commitlint 準備工作都完成了,如何測試呢?
首先測試 lint-staged
,即寫一段被 eslint 警告的程式碼進行提交:
// pakcgaes/components/src/index.ts
const a = 1; // 'a' is assigned a value but never used.
然後執行 commit(初次執行程式碼校驗時間可能較長)
上圖可以看到,lint 校驗沒透過,無法 commit,此時再把 const a = 1;
刪除掉,消除 eslint 的報錯,再次執行 commit
上圖可知,這次程式碼的校驗已經透過,但是 commitlint 丟擲了錯誤,提示 commit 資訊不規範,此時修改一下再提交
此時提交就可以正常透過了,接下來推送到遠端倉庫即可。