⚠️本文爲掘金社區首發簽約文章,未獲受權禁止轉載php
你們好,我是洛竹🎋,一隻住在杭城的木系前端🧚🏻♀️,若是你喜歡個人文章📚,能夠經過點贊幫我彙集靈力⭐️。前端
洛竹有一個朋友小黑最近在面試時被問到如何設計一個前端組件庫。沒啥經驗的小黑回答了業務提取封裝成庫以及基於 antd 結合業務二次封裝。最後小黑被 HR 以靈力不夠掛掉了。其實這個問題考察的並非假大空的概念,而是有關開發者倉庫管理、組件設計、單元測試、持續集成、協做管理等等能力。那麼爲了賦能小黑完美回答這個問題呢,我決定帶領小黑一步一步建設一個 React Native 組件庫。vue
這是一篇乾貨比較多的組件庫搭建實戰教程,不只有通用的代碼規範、提交規範、文檔維護、單元測試、GitHub Action 配置的講解,還涉及基於 lerna 的多包管理架構、React Native 圖標庫建設、React Native 組件庫開發調試、按需加載原理及實現。工程化的思想是通用的,因此不管是你用的框架是什麼,本文都值得一讀。node
若是電腦前的掘友也對組件庫開發感興趣,不妨先給個點贊,再持續關注洛竹和小黑的組件庫開發之旅。PS:配合倉庫和組件庫文檔閱讀本文效果更佳喲!react
維護開發一個組件庫無疑是須要投入不少時間和精力的,Flag 立了倒,倒了又立。可謂萬事開頭難,首先咱們要有自知之明,在沒有設計師和業餘開發的狀況下,我選擇了給現有 UI Design 實現 React Native 版本的方式開啓組件庫開發之旅。在調研了 vant、fishd-mobile 和 antd-mobile 後我選擇了 vant。這是幾個倉庫的現狀對比:android
組件庫 | 團隊 | Github Star | Npm 周下載量 | 維護度 |
---|---|---|---|---|
vant | 有贊 | 17.7K | 27,789 | 維高度高,流行度也高 |
antd-mobile | Ant Design Team | 8.9K | 31,470 | 幾乎不維護,聽說螞蟻內部也不用了 |
fishd-mobile | 網易雲商前端 | 29 | 22 | 看起來是個 KPI 項目無疑了 |
肯定了旅程的方向,就是給咱們的組件庫起一個合適的名字和口號,用前端工程師的方式表述就是 package.json
的 name
和 description
字段:webpack
// package.json
{
"name": "vant-react-native",
"description": "Lightweight React Native UI Components inspired on Vant"
}
複製代碼
因爲咱們的組件庫定位是 vant 的 RN 版,參照 lottie-react-native、styled-react-native、jpush-react-native 的命名方式咱們將組件庫命名爲 vant-react-native,同時也是但願組件庫完成時能得到 vant 官方的支持。ios
Lerna 是一個管理工具,用於管理包含多個軟件包(package)的 JavaScript 項目。由 Lerna 管理的倉庫咱們通常稱之爲單體倉庫(monorepo)。基於 Lerna 的多包管理架構的優勢在於:git
.
└── packages
├── button # @vant-react-native/button
└── icons # @vant-react-native/icon
複製代碼
$ mkdir vant-react-native && lerna init --independent
複製代碼
使用 yarn workspaces 結合 Lerna useWorkspaces
能夠實現 Lerna Hoisting。這並非畫蛇添足,這可讓你在統一的地方(根目錄)管理依賴,這即節省時間又節省空間。github
配置 lerna.json
:
{
...
"npmClient": "yarn",
"useWorkspaces": true
}
複製代碼
託管給 yarn wrokspace 以後,lerna 的 packages
將會被頂級 package.json
的 workspaces
覆蓋:
{
"private": true,
...
"workspaces": [
"packages/*"
],
}
複製代碼
若是你不想在全部 package.json
文件中單獨明確設置你的註冊表配置,例如使用私有註冊表時,設置 command.publish.registry
頗有用。配置 ignoreChanges
則是爲了不沒必要要的版本升級。
"ignoreChanges": [
"ignored-file",
"**/__tests__/**",
"**/*.md"
],
"command": {
"publish": {
"registry": "https://registry.npmjs.org"
}
}
複製代碼
除此以外,若是你的包名是帶 scope 的,須要在那個包的
package.json
中設置publishConfig.access
爲"public"
。
當配置 conventionalCommits
爲 true
後,lerna 版本將使用 Conventional Commits Specification 來肯定版本升級並 生成 CHANGELOG.md 文件。
"command": {
"version": {
"conventionalCommits": true,
"message": "chore(release): publish"
}
}
複製代碼
規範化 git commit
對於提升 git log
可讀性、可控的版本控制和 changelog 生成都有着重要的做用。洛竹以前在 一文搞定規範化Git Commit 中詳細講述了 Conventional Commits 的概念以及 commitizen、cz-customizable、@commitlint/cli、yorkie 和 commitlint-config-cz 等工具的配置。
因爲配置繁瑣,我在 @youngjuning/cli 中添加了 init-commit
命令一鍵配置 conventional commit。能夠打開這個 commit 查看配置信息。
注意:husky 高版本用法不向後兼容,我在這個 commit 中用尤大的 yorkie 代替了 husky。
代碼規範化的重要性不言而喻,代碼規範化涉及的工具備 editorconfig、eslint、prettier 等,在 裝它|不再用操心ESLint配置 一文中我介紹瞭如何一步一步建設屬於本身的 eslint config 插件併產出了 @youngjuning/eslint-config 和 @youngjuning/prettier-config。
vant-react-native 暫時使用 @youngjuning/eslint-config、@youngjuning/prettier-config 約束項目代碼規範。相關配置以下文。
首先安裝 react-native 所需的插件。
yarn add -D eslint-plugin-react \
eslint-plugin-react-hooks \
eslint-plugin-jsx-a11y \
eslint-plugin-import \
eslint-plugin-react-native
複製代碼
而後配置 .eslintrc.js
// .eslintrc.js
module.exports = {
extends: ['@youngjuning/eslint-config/react-native']
}
複製代碼
// .prettierrc.js
module.exports = require('@youngjuning/prettier-config');
複製代碼
@youngjuning/eslint-config 計劃也用 lerna 管理,產出 @youngjuning/eslint-config-react、@youngjuning/eslint-config-react-native、@youngjuning/eslint-config-vue 讓開發者無需過多配置開箱即用。
# .editorconfig
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
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
[*.gradle]
indent_size = 4
[BUCK]
indent_size = 4
複製代碼
$ yarn add -D yorkie lint-staged
複製代碼
{
"gitHooks": {
"commit-msg": "commitlint -e -V",
"pre-commit": "lint-staged"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"eslint --fix",
"git add ."
]
},
}
複製代碼
一個成熟的組件庫都會擁有本身的一套 Icon,Icon 通常由設計師經過 Sketch 設計,而後導出 svg 文件。
ant-design-icons 的 svg 文件是 保存在本地,而後經過腳本生成 react 組件、vue 組件 和 icons-react-native 等組件,因爲支持的框架比較完備咱們無需本身實現,RN 咱們直接使用 icons-react-native。
vant 以及 fishd-mobile 則是經過 Iconfont 維護 svg 文件,而後經過設置 @font-face
的方式實現 Icon 組件,如圖所示:
有了 ttf 文件,咱們能夠像 @ant-design/icons-react-native 同樣基於 ttf 文件使用腳本生成 Icon 組件,可是使用 ttf 字體有一個弊端,就是每次更新圖標,都要相應的更新 ttf 文件,而後再次打包發佈 APP。並且 ttf 不支持多種色彩的圖標,致使全部圖標都是單色。若是你是藉助 react-native-vector-icons,該庫內置了 10 多套 ttf 文件,合起來有 2M 左右;你可能用不到它們,可是它們仍然會被打包進你的 APP 裏,這也是我認爲 react-native-elements 這個庫外強中乾的一大緣由。
那麼只有 Iconfont 連接咱們如何實現 vant-icons 的 React Native 版本呢?這裏洛竹沒有本身寫腳本,而是使用了一款叫 react-native-iconfont-cli 的工具,fwh1990 大佬針對以上痛點用純 Javascript 實現 iconfont 到 React 組件的轉換操做,不須要依賴 ttf 字體文件,不須要手動下載圖標到本地。
# 建立主包,主包用來統一導出全部的組件
$ lerna create vant-react-native -y
# 建立 icons 包,咱們的第一個組件!
$ lerna create @vant-react-native/icons -y
複製代碼
咱們的目錄結構看起來是這樣的:
.
└── packages
├── icons
│ ├── README.md
│ └── package.json
└── vant-react-native
├── README.md
└── package.json
複製代碼
yarn workspace @vant-react-native/icons add -D react-native-svg react-native-iconfont-cli
複製代碼
咱們在 packages/icons
目錄下使用 npx iconfont-init
命令會生成 iconfont.json
文件,自定義後內容以下:
{
"symbol_url": "https://at.alicdn.com/t/font_2553510_7cds497uxwn.js",
"use_typescript": false,
"save_dir": "./lib",
"trim_icon_prefix": "van-icon",
"default_icon_size": 18
}
複製代碼
執行 npx iconfont-rn
命令便可生成標準 React Native 組件。因爲圖標文件比較多,咱們不將圖標產物加入 git 管理。因此咱們須要在 npm 發佈前執行構建命令:
{
"build": "npx iconfont-rn",
"prepublishOnly": "yarn build"
}
複製代碼
咱們前面提到 packages/vant-react-native
是主包的目錄,咱們須要將 @vant-react-native/icons
包添加到主包的依賴中並導出。
$ lerna add @vant-react-native/icons --scope vant-react-native
複製代碼
// packages/vant-react-native/src/index.ts
export { default as Icon } from '@vant-react-native/icons';
export * from '@vant-react-native/icons';
複製代碼
對與每一個子包咱們指望使用同樣的配置,因此咱們會先在整個項目的根目錄新建 tsconfig. base.json,在子包繼承便可。
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "lib",
},
"include": ["src/**/*"]
}
複製代碼
和 @vant-react-native/icons
子包同樣,咱們須要添加 build
和 prepublishOnly
腳本:
{
"build": "tsc",
"prepublishOnly": "yarn build"
}
複製代碼
第一次發佈的話,注意使用的是 lerna publish 0.0.1
,由於 lerna 的發佈命令沒有第一次發佈這個參數,因此須要顯示指定初始版本。或者能夠將初始版本設置爲 0.0.0
而後執行 lerna publish
。
小技巧:若是發佈後想查看包內容,能夠經過 jsdelivr 查看。好比剛發佈的 vant-react-native 和 @vant-react-native/icons
一個完善且體驗良好的調試流程不只可以知足在開發階段驗證組件是否符合預期,還能夠下降開源社區基友的參與難度。React Native 組件庫的調試和其餘技術棧流程大致沒有區別,只不過由於 Metro 不支持軟鏈接 以及 vant-react-native 是基於 lerna 的單體倉庫項目,咱們的配置會有不一樣。
因爲是 React Native 項目,咱們須要初始化一個 React Native 項目。首先找一個地方使用 react-native init vantapp --template react-native-template-typescript
建立一個新的 React Native App。而後將生成的 App 與咱們的主項目合併。合併後的項目結構以下:
.
├── App.tsx
├── __tests__
│ └── App-test.tsx
├── android
│ ├── app
│ ├── build.gradle
│ ├── gradle
│ ├── gradle.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── settings.gradle
├── app.json
├── babel.config.js
├── commitlint.config.js
├── index.js
├── ios
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Pods
│ ├── vantapp
│ ├── vantapp.xcodeproj
│ ├── vantapp.xcworkspace
│ └── vantappTests
├── lerna.json
├── metro.config.js
├── package.json
├── packages
│ ├── icons
│ └── vant-react-native
├── tsconfig.base.json
├── tsconfig.json
└── yarn.lock
複製代碼
主要衝突的是 Prettier、eslint 等工具的配置,合併沒那麼難。在運行項目以前,咱們通常須要編譯項目。咱們能夠藉助 lerna run build
命令批量運行子包裏的 build
npm script。
注意📢:因爲子包之間有依賴關係,不要使用
--parallel
參數並行執行打包腳本。
如今咱們編寫一個九宮格 Demo 驗證一下:
// App.tsx
import React, { Component } from 'react';
import { View, Text, SafeAreaView, ScrollView } from 'react-native';
import { Icon } from 'vant-react-native';
// 咱們也能夠只安裝 @vant-react-native/icons 包
// import { VanIconAdd } from '@vant-react-native/icons'
type IconNameType = React.ComponentProps<typeof Icon>['name'];
export default class App extends Component {
render() {
return (
<SafeAreaView> <ScrollView> <Text style={{ textAlign: 'center', paddingVertical: 20, fontSize: 25, color: '#007fff' }} > vant-react-native </Text> <View style={{ flexWrap: 'wrap', flexDirection: 'row' }}> {data.map((item, index) => { const lastLineLength = data.length % 4 || 4; return ( <View key={item} style={{ width: '25%', marginBottom: index < data.length - lastLineLength ? 40 : 0, alignItems: 'center', }} > <Icon name={item} size={40} /> <Text style={{ color: '#646566', marginTop: 10 }}>{item}</Text> </View> ); })} </View> </ScrollView> </SafeAreaView>
);
}
}
const data: IconNameType[] = ['location-o', 'like-o', 'star-o', 'phone-o', 'setting-o', 'fire-o', 'coupon-o', 'cart-o', 'shopping-cart-o', 'cart-circle-o', 'friends-o', 'comment-o', 'gem-o', 'gift-o', 'point-gift-o', 'send-gift-o', 'service-o', 'bag-o', 'todo-list-o', 'balance-list-o', 'close', 'clock-o', 'question-o', 'passed'];
複製代碼
而後執行 yarn ios
查看實際效果(以後咱們就能夠執行 yarn start --reset-cache
快速開始調試):
上面的示例代碼中咱們能夠看到咱們直接使用了 import { Icon } from 'vant-react-native';
而不是相對路徑引用 packages 下的模塊。但是咱們的項目並沒與安裝這個依賴,編譯器是怎麼找到的呢?這裏也沒有什麼銀彈,這是由於 lerna 會把子包軟連接到 node_modules 中,咱們可使用 ls -al
發現看到包的實際指向:
咱們也能夠在類型提示中看到實際指向的是 packages 下的文件:
注意📢:Metro 不支持符號連接 指的是軟鏈接的目錄不在項目根目錄下,這裏咱們軟鏈接指向的位置還在根目錄下,因此能夠正確工做✅。這個特性保證了調試與生產開發的一致性和便利性。
如今咱們的調試流程是:
lerna run build
編譯每一個子包yarn ios
調試項目lerna run build
從新編譯yarn start --reset-cache
運行項目儘管 React Native 有 Fast Refresh 功能,可是因爲咱們的代碼是須要編譯的,因此咱們須要重複編譯運行的動做。
任何重複的工做均可以用腳本代替。首先咱們須要給每一個子包添加實時編譯的 script,像 rollup、babel、webpack、typescript 都有參數能夠實現實時編譯:
{
"scripts": {
"dev": "tsc -w",
"build": "tsc",
"prepublishOnly": "yarn build"
},
}
複製代碼
而咱們的 @vant-react-native/icons 包使用的 npx iconfont
沒有實時編譯選項,通過調研,我引入了 onchange 這個庫能夠基於 glob 模式監聽文件改動後執行一個命令:
{
"scripts": {
"dev": "onchange -i 'iconfont.json' -- yarn build",
}
}
複製代碼
而後咱們須要使用 lerna run dev --parallel
批量執行實時編譯腳本,這裏加 --parallel
是由於子包若是是實時編譯,進程會卡住。爲了補救,咱們不得不預先編譯 @vant-react-native/icons
包,而後由於一樣的緣由我引入了 npm-run-all
來並行執行 lerna run dev
和 react-native start
,完整腳本以下:
{
"predev": "lerna run build --scope @vant-react-native/icons",
"dev": "lerna run dev --parallel",
"start": "react-native start",
"debug": "run-p dev start",
}
複製代碼
小黑:「洛竹哥哥,我以前爲了使用 react-native-elements 的其中幾個組件而引入了整個組件庫。由於這個組件庫依賴了 react-native-vector-icons 致使 bundle 包變大。若是我就是想用整套 vant-react-native,如何解決這個問題呢?」
衆所周知,React Native 的打包工具 Metro 不支持 tree-shaking。解決這個問題的方式其實很簡單,機智的你可能知道配合 babel-plugin-import 是能夠實現按需加載的需求的。但因爲咱們是多包管理架構,須要針對多包的架構設計一個方案。
爲了比對優化先後包大小,咱們須要使用 react-native bundle
命令看一下純 JS 包的大小,咱們來簡單看下這個命令:
react-native bundle --platform ios --entry-file index.js --bundle-output ./bundle/ios/index.ios.jsbundle --assets-dest ./bundle/ios --dev false --reset-cache
複製代碼
--entry
:入口 js 文件--bundle-output
:生成的 bundle 文件路徑--platform
:平臺--assets-dest
:圖片資源的輸出目錄--dev
:是否爲開發版本,打正式版的安裝包時咱們將其賦值爲 false--reset-cache
:重置緩存,避免打包使用舊的緩存前面咱們提到 packages/vant-react-native
只有一個文件 src/index.ts
用來導出全部子包,如今咱們添加一個新的包 Button,看上去就是這樣:
export { default as Icon } from '@vant-react-native/icons';
export * from '@vant-react-native/icons';
export { default as Button } from '@vant-react-native/icons';
複製代碼
這種導出方式,用戶只能經過 import Button from '@vant-react-native/button';
或 import Button from 'vant-react-native/lib/button';
的方式手動實現按需加載,這不只不方便開發者使用,從打包產物來講也增長了不少字節。那麼問題來了,怎麼樣的組織形式才能知足按需加載呢?答案就在 babel-plugin-import 插件的文檔中:
從圖中咱們看出 babel-plugin-import 插件是在編譯階段將引用指向了模塊所在文件夾。用戶使用時安裝插件並作以下配置就完成了按需加載。
"plugins": [
["import", { libraryName: "antd", style: true }]
]
複製代碼
依然沒有銀彈,插件作的工做只是代替了你的右手。知道了原理咱們就能夠按照文檔要求的格式從新組織咱們的 vant-react-native 包:
.
├── CHANGELOG.md
├── lib # 上傳到 NPM 的編譯產物
│ ├── button # 符合 babel-plugin-import 的默認配置要求
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── icon
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── index.d.ts
│ └── index.js # export * from './button';
├── package.json
├── src # 源碼目錄
│ ├── button
│ │ └── index.ts
│ ├── icon
│ │ └── index.ts
│ └── index.ts
└── tsconfig.json # 編譯配置,將 ts 文件編譯到 lib 文件夾下
複製代碼
vant-react-native/src/button/index.ts:
import Button from '@vant-react-native/button';
export default Button;
export { Button };
複製代碼
vant-react-native/src/icon/index.ts:
import Icon from '@vant-react-native/icons';
export default Icon;
export { Icon };
export * from '@vant-react-native/icons';
複製代碼
vant-react-native/src/index.ts:
export * from './icon';
export * from './button';
複製代碼
而後項目中修改 babel.config.js:
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
["import", {libraryName: 'vant-react-native'}]
],
};
複製代碼
雖然經過修改主包的導出方式能夠完成需求,可是卻極大地增長了項目自己的複雜度。前面咱們已經知道 babel-plugin-import 的原理是轉換引用路徑。那麼咱們是否是能夠經過插件動態把 import {Button} from 'vant-react-native'
轉成 import Button from '@vant-react-native/button'
呢?答案是確定的,下面是我基於 babel-plugin-import 的 customName
配置編寫了一套配置並封裝在 babel-plugin-import-vant 包中:
import camelCase from 'camelcase';
export default (): any[] => [
[
'import',
{
libraryName: 'vant-react-native',
customName: (name: string) => {
if (name === 'icon') {
return '@vant-react-native/icons';
}
if (name.match(/^van-icon-/)) {
return `@vant-react-native/icons/lib/${camelCase(name, { pascalCase: true })}`;
}
return `@vant-react-native/${name}`;
},
},
'vant-react-native',
],
[
'import',
{
libraryName: '@vant-react-native/icons',
customName: (name: string) => {
return `@vant-react-native/icons/lib/${camelCase(name, { pascalCase: true })}`;
},
},
'@vant-react-native/icons',
],
];
複製代碼
在項目的 babel.config.js
配置中添加 plugins: [...require('babel-plugin-import-vant').default()]
便可實現按需加載。
還有能夠優化的地方嗎?機智的你可能又發現我只是經過函數導出了一個配置而已,並非真正的插件,因此將來我會定製一個 vant-react-native 本身的按需加載 babel 插件。
name.match(/^van-icon-/)
這個判斷條件是由於@vant-react-native/icons
包除了包含一個默認導出的 Icon 組件,還導出了不少單個圖標組件,爲了進一步減少打包體積,咱們對這個子包也進行了按需加載處理。咱們已經知道按需加載的原理是沒有中間商賺差價直接和賣家談,因此後面咱們碰見相似的需求經過轉換返回賣家地址便可。不須要破壞性地改項目結構。
初始包大小 | 未配置按需加載(引入 Button) | 按需加載(引入 Button) | 按需加載(引入 Icon) | 按需加載(引入 VanIconAdd) |
---|---|---|---|---|
723KB | 1.8M | 725KB | 1.8M | 1.22M |
之因此 Icon 包會大,是由於 react-native-svg 這個庫大,因此不建議直接使用 Icon 組件,而是使用 VanIconAdd、VanIconEye 這種單獨的圖標組件,少了 593KB 仍是挺香的。
組件庫文檔比較重要的是有能夠交互的 Demo 演示,我是 Dumi 的資深用戶,藉助 dumi-theme-mobile 和 umi-plugin-react-native 咱們能夠很好地知足 React Native 組件庫文檔的搭建。
安裝依賴:
$ yarn add dumi dumi-theme-mobile umi-plugin-react-native -D
複製代碼
配置文件:
在項目根目錄添加 .umirc.ts
import { defineConfig, IConfig } from 'dumi';
export default defineConfig({
title: 'vant-react-native',
mode: 'site',
logo: 'https://img01.yzcdn.cn/vant/logo.png',
favicon: 'https://img01.yzcdn.cn/vant/logo.png',
resolve: {
includes: ['docs', 'packages/button', 'packages/icons'],
},
// more config: https://d.umijs.org/config
} as IConfig);
複製代碼
值得一提的是,Dumi 是支持 Lerna 倉庫的,它默認會以 packages/[包名]/src
爲基礎路徑搜尋全部子包的 Markdown 文檔並生成路由。經過 resolve.includes
能夠配置 dumi 嗅探的文檔目錄,dumi 會嘗試在配置的目錄中遞歸尋找 markdown 文件。
添加 NPM 腳本:
注意📢:因爲實際依賴的是 packages 下的包,咱們必須先編譯全部的包,不然部署的時候會報
This dependency was not found:
的錯誤。
{
"scripts": {
"start:dumi": "dumi dev",
"build:dumi": "lerna run build && dumi build"
}
}
複製代碼
忽略文件(.gitignore):
# umi
.umi
.umi-production
.env.local
dist/
複製代碼
在根目錄新建 .github/workflows/gh-pages
:
name: github pages
on:
push:
branches:
- main # default branch
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- run: yarn install
- run: yarn build:dumi
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
複製代碼
如今咱們能夠訪問 youngjuning.js.org/vant-react-… 查看效果了:
如今基於 dumi 的文檔站點只是初始化,不少配置(.umirc.ts)能夠優化,好比:
const isProd = process.env.NODE_ENV === 'production';
...
publicPath: isProd ? 'https://cdn.jsdelivr.net/gh/youngjuning/vant-react-native@gh-pages/': '/',
複製代碼
{
hash: true
}
複製代碼
{
scripts: ['https://s9.cnzz.com/z_stat.php?id=1280093214&web_id=1280093214'],
styles: ['a[title=站長統計] { display: none; }'],
}
複製代碼
exportStatic: {}
將全部路由輸出爲 HTML 目錄結構,以避免刷新頁面時 404。考慮到後期社區會貢獻代碼和文檔。在 pr 合進主分支以前,咱們須要預覽文檔或組件。知足這一需求的是一個叫 surge.sh 的靜態託管服務,surge 支持在命令行經過簡單的命令免費發佈 HTML、CSS 和 JS 文件到 web。
安裝 surge cli:
npm install --global surge
複製代碼
註冊 surge 帳號:
suerge login
複製代碼
獲取 token:
suerge token
複製代碼
因爲 GitHub 的安全問題,surge-preview Action 插件沒法使用,咱們參考 dumi 官方的配置自定義了 CI,首先咱們拷貝下圖中的三個文件到項目中。
而後修改 preview-build.yml
中的 build step
:
- NODE_OPTIONS='--max-old-space-size=4096' yarn build
+ NODE_OPTIONS='--max-old-space-size=4096' PREVIEW_PR=true yarn build:dumi
複製代碼
添加環境變量 PREVIEW_PR=true
是爲了讓 dumi 打包時識別出不是生產環境打包,.umirc.ts
須要相應修改成:
const isProd =
process.env.NODE_ENV === 'production' && process.env.PREVIEW_PR !== "true";
...
publicPath: isProd ? 'https://cdn.jsdelivr.net/gh/youngjuning/vant-react-native@gh-pages/': '/',
...
複製代碼
再而後,修改 preview-deploy.yml
文件中的部署域名 dumi-preview
爲 vant-react-native-preview
。
最後咱們把前面獲取的 Surge Token 添加到倉庫的 Secrets 便可。
正在部署 PR 預覽狀態:
部署成功狀態:
訪問 vant-react-native-preview-pr-1.surge.sh/ 便可驗證文檔的正確性✅。
我在 使用 Jest 和 Enzyme 進行 React Native 單元測試|技術點評 一文中曾提交單元測試和文檔同樣,是保障程序最小單元質量的重要一環。誠然一個成熟的組件庫是必然有單元測試的身影。本章就不展開講單元測試了,主要講 vant-react-native 是如何配置單元測試的。
jest、babel-jest、@types/jest 這些依賴都已經安裝了,咱們須要安裝的是 enzyme 這個基於 jest 的單元測試框架。
$ yarn add enzyme jest-enzyme enzyme-adapter-react-16 enzyme-to-json @types/enzyme react-native-mock-render -DW
複製代碼
Enzyme 是用於 React 的 JavaScript 測試實用程序,能夠更輕鬆地測試 React 組件的輸出。您還能夠根據給定的輸出進行操做,遍歷並以某種方式模擬運行時。
jest.config.js:
module.exports = {
preset: 'react-native',
verbose: true,
collectCoverage: true, // 生成測試覆蓋率報告
moduleNameMapper: {
// for https://github.com/facebook/jest/issues/919
'^image![a-zA-Z0-9$_-]+$': 'GlobalImageStub',
'^[@./a-zA-Z0-9$_-]+\\.(png|gif)$': 'RelativeImageStub',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], // 使用 Jest 運行安裝文件以配置 Enzyme 和適配器(以下文jest.setup.js中所示),以前是setupTestFrameworkScriptFile,也可使用setupFiles
snapshotSerializers: ['enzyme-to-json/serializer'], // 推薦使用序列化程序使用 enzyme-to-json,它的安裝和使用很是簡單,並容許您編寫簡潔的快照測試。
};
複製代碼
jest.setup.js:
import 'react-native';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
複製代碼
一個簡單的示例:
// packages/button/__test__/index.tsx
import React from 'react';
import { shallow } from 'enzyme';
import Button from '../src/index';
function setup(props = {}) {
const wrapper = shallow(<Button />);
const instance = wrapper.instance();
return { wrapper, instance };
}
describe('Button Component', () => {
it('renders correctly', () => {
const { wrapper } = setup();
expect(wrapper).toMatchSnapshot();
});
});
複製代碼
執行 jest
命令後能夠查看覆蓋率以下:
能寫長文的不算勇士,能堅持看到這裏的纔是勇士。洛竹在此感謝您的閱讀。然而組件庫工程化這只是一個起點,若是本文反響好,組件庫具體組件的設計實現、完整的 React Native 單元測試教程等等洛竹會在後續的文章中展開講。
固然了,vant-react-native 並非你惟一的選擇,下面的幾個 UI 庫都是很優秀的項目。在實現 vant-react-native 時我也多少借鑑了前人優秀的設計。