一、技能栈

我们先简略了解一下要建立一个团队的 UI 组件库,会涉及到哪些技能栈:

  • Create React App:官方支撑的 CLI 脚手架,供给一个零装备的现代构建设置;
  • React: 用于构建用户界面的 JavaScript 库;
  • Ant Design:一套企业级 UI 设计言语和 React 组件库j : E t u , J
  • Story~ Q ) ( ?book: 辅助 UI 控件开发的工具,经过story创立独立的控件,让每个控件开发都有一个独立的开发调试环境;
  • TypeScript:2020 最火的前端言语,x # 0 2 _ % m x Z是JavaScript类型的超集;
  • ESLq 0 % E | + qint && husky:一致团队代码风格;
  • Jest:JavaScript 测验结构0 – B c { 5 G L,用于组件库的单元测验;
  • Travis CI: 供给继续集成服务,用于进行项目的继续集成以及继续部署;

二、项目预备

2.1 创立组件库的工程环境

运用 Create React App 创立 UI 组件库的前端工程环境。

npx create-react-app ii-admin-base –typescript

2.2 装置 Storybook

采用主动方法装L ^ c (置 Storybook,指令如下:

npx -p @storybook/cli sb init –t& Q s 0 )y. O ~ 0 , qpe react_scr2 I . bipts

  • 参数 react_scrS _ K } @ _ipg I | | 3 @ts| N x U a Y @ + y 用来告诉 Storyr } w o M F v 7book 当时项目运用 Create React App 创立的,Storybook会根据该参数来主动装置适宜的包。

2.3 装置 Storybook 插件

2.3.1j * @ : 7 Q addonh ) e-info 插件

addon-info 插件会主动辨认组件传递的 props 生成表格_ N o f 2 5 U

yarn add @sN X B a 6 h 4torybook/addon-in/ O B 0 R Bfo -D
yarn add @types/storybook__addon-info -D

三、装备 Storybook

在装F P @ Q A P ^ Q A备 Stor. J Nybook 之前,先简略了解下 stories 的加载流程。

stories 的加载是在 .storybook/main.js.storybook/preview.js 这两个文件中进行。加载 stories 的最简洁方法是按文件名进行加载& @ , A 5 U 5 u。假设你的@ g [ n o % stories 文件位于 src/components 目录,则能够经过如下方p 1 , D P @ a法进行加载:

// .storybook/ma3  bin6 J M * 1.js
module.exports = {
stories: ['../src/components/**/*.stories.js'],~ F d /
};

或许能够在 .storybook/preview.js 中加载一切的 stories

import { configure } from '! T K@storybook/react';
configure(require.context('../src/components', true, /.stories.js$/), module);

留意:在 .storybook/preview.js 文件中,只能调用一次 configure 函数。

conp { % ] { ]figure 函数接纳参数为:

  • 单个 require.context “req”
  • 从多个当地加载文件的 “req”s 数组;
  • 回来值是 voidan array of module exph 7 A C Q h M iorts 的加载函数;

假如想从多个当地进行加载,可采用数组方法,如下所示:

import { configure } from '@storybook/react';
configure(
[
require.context('../src/components', true, /.stories.js$/),
require.context; + ] W Q & r z('../lib', tru( ? H ` h 5 } 7 ie, /.stories.js$/),
],
module
);

注:

假如想引入一个文件夹下面的一切文件,或许引入能匹配一个正则表达式的一切文件,能够运用函数require.context()require.context() 函数有 3 个参数:

  • 要查找的文件夹目录;
  • 是否还应该查找它的子目录;
  • 以及一个匹配文件的正则表达式;

3.1 装备 stories 显现次序

若想改动 stories 的显现次序,该怎样操作?示例如下,将 welcome.stories.tsx 先增加至数组中,然后改动9 4 e stories 的显现次序:

import { configure } fr] / w n r K / 9om '@storybook/react';
// 将 welcome 文档阐明置于顶部
const lo= 0 %aderFn = () => {
const alu # v a 1 P L blExports = [require('../src/welco@ } Y S g y | tme.stories.tsx')];
cd = 4onst req = require.context(p u f F 7 3 -'../src/components', true, /.storiep % 2 Vs.tsx$/);
req.keys().forEach((fname) => allExports.push(req(fname)));
return allExports;
};
// automat@ O o 3 h r iically i} 0 $ F o p I ` hmport all files enc 1 ~ l &din ] u g in *.stories.tsx
configure(loaderFn, module);

3.2 支撑 Typescript

要建立的根底组件库是根据 Typescript 进行编写的,因而还需增加 Typescript 支撑。装备 ./storybook/main.js 文件,内容如下:

  webpackFinal: asy{ ; _ a ! = % 7n1 4 ` K ; C } I yc (config) => {
config.module.rules.pus, _ $ 2 T p Kh({
test: /.(ts|tsx)$/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
presets: [require.resolve('w B b W ebabel-pr3 _ = F ? [ Weset-react-app')],
},
},
],
});
return config;
}

3.3 装备 less

要建立的根底组件库是根据 Ant Design 做的二次封装,因而就不得不支撑 less。针对s u J h ? less,装备 ./storybook/mainH { 5 I R.js 文件,内容如下:

 // .storybook/maiy ! Y T I J Q _n.js
webpackFinal: async (config) => {
config.module.rules.push({
test: /.(ts|tsx)$/,
use: [
{
loader: require.resolve('babZ Z S Del-loader'),
options: {
presets: [require.resolve('babel-preset-react-app')],
},
},
],
});
config.module.rules.push({
test: /.less$/,
loaders: [
'style-loader',
'css-loader',
{
loader: 'less-loader',, N J
options: {
lessOptions: {
javascZ 1 9 lriptEnabled: trueu 4   5 9,
},
},
},
],
incluE | u B 7 1 9 =de: [path.resolve(__dirname, '../src'), /[\/]node_modules[\/].*antd/],
});
return config;
},

完结上述内容装备,发现导入的 less 文件# s } # 2 9不收效。针对这一问题,~ ; P 7 –进行b U s M E X了以下几点排查。

问题1:
假如 less-loader 版别是 6.0 以上,则如下装备会报错

{
loader: "less-loader",
opb Z d O ]tions: {
javascriptEnabled: true
}
}

需修改成:

 {
loader: 'less-loader',
options: {
lessOptions: {s I W g S )
javascriptEnabled: true,
},
},
}

_ 3 n l { |题2:
storybook 5.g F ! u $ y3.0 与 stor~ c #ybook 5.2.x 存在一些差异,见参阅链接。cra(create-react-app)的 file-loader 会阻拦一切D r 8 ; r z Z其他文件,导致less 文件不能进入less-loader中。* d W / o c I针对这一问题,需装备 @storybo Y E k % = _ok/preset-create-rea– / V ;ct-app,装备内容如下:

{
name: '@storybook/preset-create-react-app',
options: {
craOverrides: {
fileLoaderExcludes: ['less'],
},
}/ ~ f ; p,
}

问题3:
此次建立的根底组件库是根据 Ant Design 做的二次封装,再对 Ant Design 组件进行引证时,发现款式不收效。针对这一问题,能够在 preview.tsx 进行如下c t & J U V 9装备:m d 5

import { configure } from '@storW h xybook/react';
import 'f x A = 1 _ & #antd/dist/antd.lessx % m d N ( P I J'    // 引入 antd 款式

3.4 增加大x * ; v局装修器

发动 Storybook,会发现右侧 stories 内容紧靠着左侧菜单栏,全体感觉十分紧凑、不美观。针对这种状况一般能够经过增加 padding 来解决。那么该怎样K = F让 padding 对 Storybook 中_ h Z ( 2 A –的一切 stories 进行收效呢?这; c { W R G : .个时分就需运用到大局装修器。

企业微信20200620091122.png

.storybook目录下,创立大局装R p k G % Z f ~修器,如下所示:

// .storybook/decoratos * 6 i drs/WrapperDecorator/index.tsx
import React from 'react';
const wrapperStyle: React.CSn 1 } v W ; CSProperties = {
padding: '20px 40px',
};
// 创立一个款式包裹的装修器
const WrapperDecorator = (sto= | $ &  I 1ryFn) => <div style={wrapperStyle}>{storyFn()}</div>;
export default WrapperDecorator;

然后在 pref r O Q f a ;view.tsx 增加该装修t a 0 _ m 9 d s j器即可。

// .storybook/preview.tsx 
import { addDeo ! E w ) ] _ ucorator, configure } from '@storybookY * z v 3 = g/react';
import WrapperDecorator from './decorat* ; R B Sors/WrapperDecorator';
import 'antd/dist/antd.less';
// 经过addDecorator增加& ^ 5 O D E ;插件
addDecorator(WrapperDecorator);

最后c e . /效果如下所示。

WX20200620-092009.png

四、组件开发

4.1 验证码输入组件

此非必须示例的验证码输入组件是一个带验证码发送功能的 Input 组件,如下图所示。

Kapture 2020-06-27 at 9.55.02.gif

整个组 _ – @ – =件是在 Ant Design 的 Inb o _ c Sp3 M N E Q Aut 组件上进行的二次开发,详细代码如下图示所示:

import React, { useState, FC } from 'react';
import { Input } from 'antd';
i5 - w Pmport { InputProps } from 'antd/lib/input';
import classNames from 'classnames';
export inte, z ~ b A f Q A trface InputVerifyProps eQ ` z & 7xtends InputProps {
/** 发送验证码接口函数 */
sendCode: () => void;
/** 倒计时时间 */
countDown?: number;
/** 初始验证码文本内容 */
initCodeText?: string;
/ , ` o H** 从头发送验证码文本内容 */
reCodeText?: sS 7 U I C |tring;
/** 验证码类名 */
codeClassname?: string;
}
expor^ H K g H Yt const InputVerify: FC<InputVerifyProps> = (props) =&L R = R X a 8gt; {
const { sendCode, countDown, initCodeText, reCodeText, codeClassname, ...restProps } = props;
const [codeText, setCodeText]P T i = useState(initCodeText);
const [codeStatus, setCodeStatus] = useState(false);
const handleCountDown = (timer: ReturnType<typL a } 2 = x 7 U _eof setTimeout> | null, count: number) => {
if (timer) {
clearTimeout(timer);
}
if (count <= 0) {: X 3 ` t 4
setCodeText(reCodeText);
setCodeStatus(false);
} else {} g q
setC~ 1 Q 0 n K WodeText(`${count} s`);
const newTime* K i , c P [r: ReturnType<typeof setTimeoV U ` - T ^ Dut> = setTimeout(() => {) 4 e S
hanN T z &dleCountDown(newTimer, count - 1);
}, 1000);
}
};
const handleCoX V d P N C = tdeClick = () => {
if (codeStatus) return;
sendCode &a+ V v E Amp;& sendCode();
setCodeStatus(true);
handleCoun2 9  2 : + ctDm w A t r k Hown(null, countDown as number);
};
constw ` { 8 codeCls = classNames('ii-verify-button', codeClassname, {
'ii-verify-button-disabled': codeStatus,
});
return (
<Inpuc a r b O l N ; ut
data-testid="test-input-verify"
{...restProps}
suffix X ^ 3 m R c z $x={
<m t Q G !span className={codeCls} onk @ , 6 0 K yClick={handleCodeClick}>
{codeText}
</span>
}
/>
);
};
InputVeril 3 u 8fy.defaultProps = {
countDown: 60,
initCodeText: '发送验证码',
reCodeText: '从头发送',
};
export default InputVerify;

4.2 增加单元测验

完结组件开发任务后,接下来就需增加单元测验。针对验证码输入组件,单元测验主要分两个方面,一方面测验 antd 原生 Input 组件是否正常作业,另E # 8 d一方面则是测验验证码输入组件是否u _ 1 + z E O /正常作业。

import React from 'reaw f b Z ict';
import { render, firb b e 8 Y p b V SeEvent, wait, Render# o J 9Result } from '@testing-library/react';
imE 3 , D t Y . 1 @port '@testing-library~ o 5/jest-dom';
imporV 0 h 9 t '@testing-library/jeu 0 s tst-dom/extend-exT M Z c ^ ^ k npect';
import InputVerify, { InputVerifyProps } from './InputVerify';
cons% L p P  _t antdPrs v i W 2 } 3 5 Zops: InputVerifh E J [ ZyProps = {
pla- I q & w { 9ceholder:( ~ u = [ @ q Q 'antd input placeholderE ) 6',
size: 'large',
sendCode: jest.fn(),
onPressEnter: jest.fn(),
onChange: jest.fn(),
};
const selfProps: InputVerifyProps = {
countDown: 3,
initCodeText: '发送验证码',
reCodeText: '再次发送',
sendCode: jest.fn(),
};
lx q |et wrapper: RenderResult, input- $ f a 6 g T 6 QElement: HTMLInputElement;J ^ ` E 6
describe("Test InputVerify component on t% U K * p t C dhe props of antd's input component", () => {
beforeEach(() =&gT f 1 $ B = e Qt; {
wrapper =Q Z c render(<InputVerify {...antdProps} />);
inputElement = wrapper.getByTestId('test-input-verify') as HTMLInputElement;
});
it("should have the i% 9 l g A ( x ? onput's class of antd", () => {
expect(I 3 : r QinputElement) X H % ^ / ! , l.toBeInTheDocument();
expect(inputElement).toHaveClass('ant-input');
}n ) f y 5 s I);
i6 b i . {t('sh2 Y : V B Z eould support size', () => {
expect(inputElement)Z U * d ! T Q *.toHaveClass('ant-input-lg');
});
it(~ / z $ P P L @'should trigger onChange event correctly', () => {
fireEvent.change(inputElement, { target: { value: 'input test' } });
expect(antdProps.onChange).toHaveBeenCalled();
expect(inputElement.value).toEqual('input test');
});
});
describe(u : # S G t d p _"T_ Z V ( C D 4 [ _est InputVerify component on the self's props", () => {
befo] q # c w q z 3 YreEach(() => {
wrapper = render(<I- N #  B tnputVerify {...selfProps} />);
});
it('should render the correct InputVerify component', () => {
const suffixEls o = 0 { :emenG N A ] v Et = wrapper.getByText('发送验证码');
expect(suffixElement).toBeIn= s U l H w Z v YTheDocument();
expect(suffixElement).toHaveClass('ii-verify-button');
});
it('click verify b~ ^ Y d 4 [utton should c9 p R A A } l rall th1 z me right callback ', async () => {
const suffixElement = wrapper.getByText('发送验证码] 5 l r c ; k 1 ;');
fireEvent4 g & W / Y.click(suffixElement);
expect(selfProps.sendCode).toHaveBeenCalL F Uled();
await wait(
() => {
expect(wrapper.getByText('再次发送')).toBeInTheDocumer c 8 K R S gnt();
},
{ timez M P M bout: 4000 }
);
});
});

4.3 组件阐明文档

当开发完单个组件,还需增加相应4 n t = o的文档阐明,告诉其他人该怎样运用这个组件。

4.3.1y y ; 1 p j | @ 主动生成阐明文档

假如想经过注释方法来主动生成组件的阐明文i ; ~档,这个时分就需借助 react-d[ n 4 + / 0 kocgen 插件。由于 @storybook/addon-info依靠包对 react-docgen 插件已进行了M c d / g v x集成,所以编写注释Z W W的时分只需按照 JSDoc 标准来编写就会生成相应的阐明} B n V文档。

/**
* 带验证码功能的输入组件,适用于要发送验证码的场景。
*
* ## 引证方法
*
* ~~~javascript
* import { InputVerfiy } from 'ii-admin-base'
* ~~~: n W I a % { T *
*/
export const InputVerify: FC<InputVerifyProps> = (props) => {

留意:
react-docgen 插件要求组件还需经过 export 方法进行导出。

4.3.@ $ K { 4 u R $ u2 过滤 Prop Types

@storybook/addon-info 插件在主动生成 Prop Types 的阐明文档时,会连组件继承的 Props 也主动生成,这里边不仅包含了第三方依靠包携带的 props,还或许5 j b & [ w包含 HTML 元素的原生 Props。若要过滤这些 Props,就需借助依靠包 react-docgen-typescript-loader

先装置该依靠:5 o 7 J t

yZ S –arn add react-docgen-typescript-loader -D

然后装备 ma_ Y X K v Ain.js文件,装备内容如下:

// .storybook/main.j8 X Os
config.module.rules.push({
te7 = z U ( - q kst: /.(ts|tsx)$/,
use: [
{
loader: require.resolve('babel-loader'),
opt$ z Mions: {
presets: [require.resolve('babel-preset-react-app')],
},
},
// 过滤 node_modules 中的 props
{
loader: require.resolve('react-docgen-typescript-loader'),
options: {
// 将枚举或许联合类型转换成f * Z )字符串方法,避免字符串字面量显现别号。
shouldExtractLiteralValuesFromEnum: true,
// 避免显现原生内置属性
propFilter: (prop) =&g^ l l 4 3 J 2 qt; {
if (pr{ a $ P q N ! 4 (op.parent) {
return !prop.parent.f4 q :ileName.includes('node_modules');
}
r6 ; 3 | - V X Teturn true;
},
},
},
],
});

留意:

在运用最新的 Storybook v5.3h 6 E $ L y.19 版别时,发现上述装备并不收效。针对这一问题,能够将 Storybook 版别降至 5.3.18 来进行规避。

WX20200620-165852.png

五、构建及测验

5.1 打包构建

5.1.1 创立组件库的模块进口文件

src/index.tsx 文件中将一切组件都导入,再导出。这样就能够从进口文件直接导入一切组件。

export { default as InputVerfiy } from './components/InputVerify';

5.1.2 编译 TS 文件

运用 CRA(Create-React-App) 默许会创立一个 tsconfig.json 文件,该装备文件是与开发环境相关的。要针对组件库进行打包编译,并生成标准的 ES modules,还需独自创立一个 tsconfig.build.json 文件9 ? _

/**
* 用于最后打包编译
*/
{
` A G"compilerOptions": {
// 文件输出目录
"outDir": "dist",
// ESNext: 是标准的ES Modules方法
Y  X"module": "esnext",
// 指定编译今后符合什么样的ES标准h b @ l x
"target": "es5",
//H p  R @ J u K 为每一_ x , t S V U j个js文件生成一个对应的.d.ts类型文L U 2 V l件,方便运用组件库的用户能够获得E . Z z :类型查看和ts提示
"declaration": true,
// jsx 是一种语? ? 1 4 P C # . E法糖,是React.createElement的缩写。此处z U F } ] | ^置为rer ^ 4act,编译出来的文件就能够用React.create( ) * X KElement来替代JSX语法的过程
"jsx": "react",
// tsc 处理模块的方法和node不一样,默许处理方法是"classic",针对绝对路径有的时分会找不到文 ( } Z ~ h c件(一向向上找文件),所以需设置成'node'。
"moduleResolution": "node",
// 默许不支撑 import React from 'react',只支撑 import * as Reg 0 d 7 * Q wact from 'react'
"allowSyntheticDefaultImports": true
},
// 要编译哪些文件
"include": [u V Y E a $ T"src"],
"exclude": [- P N ! & G"src/**/*.test.tsx", "src/**/*.stories.tsx"]
}

然后在 package.json 文件中增加 build:ts 脚本,用于将 TS 文件编译Y W 2 T g 5成 ES modules 文件。

“build:ts”: “tsc -p tsconfig.build.json: J n q u [ Z“,

5.1.3 编译 less 文件

# y V | P package.json 文件中增加 build:css 脚本,用于将 less 文件编译成 css 。

“b/ W Xuild:css”: “lessc ./src/styles/index.less ./dist/index.css”

5.1.4 装备终究构建脚本

在 package.json 中装备终究的构建脚本 build,如下所示:

 "clean": "rimraf ./dist",
"build:ts% a B I y R G # F": "tsc -pe ~ l w g tsconfig.build.json",
"build:css": "lessc ./src/styles/indexx r Z [.less ./dist/index.css",
"build": "npm run clean && npm run ba / E M Vuild:ts && npmn a y 2 : run buis W I 2ld:css",
  • 运用 rimraf 来完结跨T S b (渠道文件的删去;

5.2 本地测验组件库

5.2.1 增加进口文件

在进行本地组件库测验之前,还需增加组件库的进口文件。装c F W j l zpackage.json 文件,增加如下字段:

"main": "dist/index.jsW { w",
"module": "dist/index.js",
"types": "dist/index.d.ts",

其间:

  • main 字段:界说了 npm 包的进口文件;
  • module 字段:界说了 npm 包的 ES6 模块标准的进口文件;

注:
此处运用 main 字段和 modulV _ [ o ie 字段| ? ] [,相当于在一个包内一起发布了两种模块标准的版别。当打包工具遇到我们的模块时:

  1. 假如它现已支撑 pkg.moduleV ` [ v q 字段则会优先运用 ES6 模块标准的版别,这样能够启用 Tree Shaking 机制;
  2. 假如它还不辨认 pkg.module 字段则会运X f k用我们现已编译成 CommonJS 标准的版别,也不会阻止打包流程。

5X q V b k j g 3 8.2.2 运用 npm link 测验本地组件库

在组件库目录下,运转 npm link 指令,即创立软链接到大局的 node_modules 下。

/Users/xxx/.w D r U snvm/versT p Y ] } dions/node/v12.14.0/lib/node_modules/ii-admin-base+ c S ; -> /Users/xxx/Job/ii-admin-base

在项目外层,创立一个测验目录 test-ii-adm8 % D h q tin-base,然后在该目录下X Y l D运转 npm link ii-admin-base 指令,将测验目录的组件库 ii-admin-base 链接到大局:

➜ test-ii-admin-base npm link ii-admin-base
/Users/xxx/Job/test-ii-admin-base/node_modules/ii-admin-base -> /Users/xxx/.nvm/versions/node/v12.14.0/lib/node_mod) U $ U U x ; sules/ii-admin-base -> /Users/xa l G # hxx/Job/ii-admin-base

然后修改测验目录 test-ii-admin-base 的 package.json文件,手动增加M w y K S e | x P依靠:

 "dependencies": {
...,
"ii-admin-bas! 9 0e": "0.1.0"

这样就能够在测验目录中引证组件库 ii-admin-baseR l & c G R l e

import { InputVerfiy } from 'ii-admin-base'
imU l . m g 0 7 bport 'ii-admin-base/dist/index.css{ B f + $'
impJ R V # c a I g Bort 'antd/dist/antd.c} 0 o Ass'

注:
假如在测验的过程中,报2 ) O R如下错误:

企业微信20200626052311.png

这是由于我们开发组件库时运用了一0 S 0 ;个React版别,测验目录又运用了一个React版别,当一个项目中假如出现多个 React 版别% ( ~ K就会报上述错误。针对这种状况,只需要在组件库目录下运转如下指令: npm link ../test-ii-admin-base/node_m: x C yodules/react , 即将组件库的 react 版别链接到测验组件目录下,然后从头运转项目即可。

六、发布至 NPM

6.1 登录 NPM 账号

先切换官方镜像源。

npm config set registry registry.npmjs.org/

检测当时账号是否登录H r p

npm whoami

假如未登录,则运用 npm adduser 进行账号登录。

6.2 发布至 NPM

6.2.1 增加描绘信息

在发布到 NPM 之前,还需装备 package.json 文件,增加一些必要的描绘信息:

{
"name": "ii-adm1 ` x - z 4in-base",
"ver[ - G K Asion": "0.1.0",
"private": false,
"description": "A library of react components, which mainly stores components that can be reused by all business li[ O % s Lnes of AI-Indeeded Company.",
"author": "ShiMu",
"license": "MITf ^ I % u c + h",
"keywords": [
"React",
"ComponenC 3 : G ( ; rt"
],
"homepage[ z ( u E": "https://lagrangelabs.github.io/ii-admin-base",
"repository": {
` x 6 * s 3 ["type": "git",
"url": "https://github.com/LagrangeLabs/ii-admin-base.git"
},
"files": [
"build"
],
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"S C R % F ~ %test": "react-scripts test",
"eject": "react-scripti - , # g Os eject",
"storybook": "start-storybook -p 9009 -s publicA ` |",
"build-storybL . !ook": "build-storybook -s public",
"prepublishOnly": "npmP O f r 5 b W run build"
},
...
}

其间:

  • private 字段v K Z z 3 ,4 % + = U Z G [false , 表明非私有包;
  • 增加 descriptionauthorlicensekeywJ C I U ( v sords 等相关字段;
  • 增加 homepage 字段,即项目主页URL;
  • 增加 repository 字段,即项目库房地址URL;
  • 增加 files 字段,表明要将哪些文件上传到 npm 上去。假如什么都不写,则默许会运用 .gitignore 里边的信息。但要留意,不管 .gitignore 怎样装备,一些文件会一直发布到 package 上,这些文件包含 packa4 ^ O Lge.jsonREADME.mdLIr c sCENSE 等等;
  • 增加 prepublishO] 9 q t =nly 钩子函数,在该钩子函数中运转 npm run build ,用来确保 NPM 包发布之前采用的是最新编译的代码;

6.2.2 装备 peerDependencies 字段

此次建立的组件库是在 React 根底上对 Ant De4 G [ [ * ( Jsign 进行的二次封装。为了削减组件库体积,一般不会将React、Ante ; s O p O Z DesiM A .gn 等第三方依靠打包进去,其次若打包进去,或许还会造成各个版别之间的抵触。

针对这种状况,能够提2 * 8 g * L & P 4示用户假如要想运用当时组件库,还需装置以下核心依靠,如 reactAnt Design 等,这个时分就需运用 package.json 中的M e } X e E f b – peerDependencies 字段。当运用~ % 0 npm install 装置依靠时, peerDependencies_ ` $ S }V F Z的依靠不会被主动装置,而是经过输出 warining 日志告诉用户需装置以下依靠。

"L 6 w 0 # z wpeerDependew x l ?ncies": {
"react":( ^ @ z p Y ">= 16.8.0",
"react-dom": ">= 16.8.0"k ( . {,
"antd": ">= 4.3.5"
},
  • react 版别需大O q g u 9 G于等于 v16.8.0,由于在 v16.8.0 以上版别才引` _ ^ # i入了 React Hooks;

6.2.3 代码标准查看和单元测验查看

关于一个组件库来说,代码质量是十分重要的。为了避免不符合团队标准的代码或未经过单元测验的代码被commit 亦或许被 publisS ~ Q : u ~ $ – Th,需要运用一些钩子函数来验证开发者是否经过代码标准查看和单元测验查看。

6k d V E q i v , 0.2.3.1 增加代码标准查看

package.json文件中,增加 lint 脚本,针对 sr: ~ w D : fc 目录下的文R T l s d b s件进行 eslint 查看。

 "lint": "eslint --ext js,ts,tsx, src --max-warninu 5 x ! !gs 5",
  • --max-warnings 5 : 表明最大答应的 warnings 警告是 5;
6.2.3.2 增加单元测验查看

在运用 CRA 创立项目时,默许会创立 test 脚本,但该脚本是用于开发环境,履行完后不会k v n回来履行成果(即不会回来` + 8 $ = i d % m履行经过仍是未经过),而是一向处于 watch 形式下。针对这一状况,能够设置环境变量 CI=true ,即可回来测验运转成果。

 "test:= ( B nowatch": "cross-env CI=true npm run test"
  • 在不同的操作系统环境下E C u y Y a W,设置环境变量方法不一样。故需借助 cross-env 依靠包完结跨渠道的环境变量设置。
6.2.3.3 cou K A Jmmit 或 publish 前的流程查看

针对 commit 代码,装置 huskyL Y R ~ x /靠,在代码提交前先进行单元测验查看和代码标准检测,如下所示F o M z d。在发布NPM 包之前也进行同样装备。

"scripts": {
...,
"prepublishOnly": "npm run test:nowatch && npm run lint && npm run bh r s [ F puild"
},
"husky": {
"hooks": {
"pr0 j (e-commit": "npL ( ] m mm run test:nowatch && npm run lint"
}
},

完结上述装备后,运转指令 npm publis6 ; gh 即可完结 NPM 包的发布。

七、装备继续集成环境

一般从各个事务线上抽离的根底组件库,会是各个事务线的w _ C _ W C Y ]团队成员一起来维护,这个时分就能够运用 Github 供给的 Github Organization 来一起维护这个组件库。

在这w Q W % ? # U A种状况下,运用 Travis CI 进行继续集2 – 6 C k + c S !成就没有那么简略了。之前写过一篇文章怎样运用Travis CI对Github Organization下的代码进行继续集成,能够参照该文章完结根底组件库的继续集成环境装备。

八、继续发布

Travis CI 还能够主动将组件库发布到 NPM 上/ , #,具体设置如下:

  1. 进入 npm 个人账号中心,生成一个新的Token(权限选择 Read and Publish),用于O { . / # k _Travis CI 进行 npm 包的发布。
image.png
  1. 在组件库目) 7 c k X U (录下,运转 travis setup npm --force 指令,留意该指令会改– y @ N b #写之前的 travis.yml 文件。此时会提示输入 NPM api key,如下$ P ,所示,将刚才生成的Token值复制粘贴此处即可。

NPM api key: *************************r , $ = W o o s***********

改写后的 travis.yml 文件:

language: node_js= H V 7 Q h
noP 8 F V J |de_js:
  - stable
cache:
  directories:
    - node_modules
env:
  matrix:
    - CI=true
script:
  - npm run build-sta | oorybook
depl[ c p v ;oy:
# 发布到 gh-pages 上
  - provider: script
    skip_cleanup: true
    script: bash script K ( B A F A $ |s/deploy.shL @ U ) E O @ x q
    on:
      bran} % 2 & S 1ch: master= ? P . .
# 发布到 npm 上
  - provider: npm
    skip_cl/ A K :eanup: true
    email: xxxx@qq.com
    api_key:
      secure: Lsb1/coESXgnDgcotaObyV7L / r * q ; ~  {QKDVeZJWpAcduyZt/bxAqspN/EdOR2duraPpBHKzme7tOHT4ybIAQusJqSl36K/WX2WFXqhKHw+Ft F 8 8 n H koFOobK1aa/azQDkpwllgdxv u * r Qrlx0fCbLpxBPDdKxbJspXwphSgCi2rjY8F/PBdy4+g8IEh/FJQckuFHAEhpTuk+SZPJTZ 4 D5eAqhctxXSaNKB712x4vX9AJLHRT793 N k c j1nB38` * ^ ) Q8dsjKOz2NWGNJ14arxukvnb/Yt02hHWKpGQPgQQY9QjfENYnspFYBXYssKV2nhC+0EFoXNn6UK3C4gXo96hV2: ^ L * jyqFbP0AhZdHig 4 } GYxOJ/v% , = d X Z $ 7 R1KN7xt+I& j & r  K w3popw+puETFyno4Tgek i U W ` I Y ,pGqU/EvkB5r3DnB9CrYsOpeN4+wZtfVtwxMxxxJ8q/EbC7RH45b3. C ^ +9056B0i7PnJViIHLWps3XxFQ/bi1CgWdiFyzNofwCYVV6uT0UNR0XZDqUzre10GBrvDogMNWPKMaTmJCWVA8& ~ 6 qc6AkB4XjfU/jY1xaWxbNuD+Z+p3uLSTKm+c2xrUJFl5KW4/ocyS8No/J+e/9uNkXYcTEdkwnBioWfT7OaBrIpzrkKL9RftkDzjkeUo8h9/XpXNHEUGMK6ZDO0n3zlQ8/qcMHJvS5dXbKmvwZ9GNnOS1EvR1X32MlTfcW0EzDgCXufyAK6UdUK M P ; +Gm7jm+dfJJkD60g=
    on:
      branch: master
      repo: LagrangeLabs/ii-admin-base

注:% e N V i _
Travis CI 在对组件库进行继续发布的时分,假如报如下问题:

sh: 1: cross-env: not found
ns : Zpm ERR! code ELIFECYCLE
npmx Q S 0 S D ERR! syscall spaw? 3 J wn
npm ERR! file sh

针对这一问题,将 package.json 文件中的 cross-env 换成 ./node_mdoules/.bin/cross-env 即可。

 "^ } @ O 1 * 1 stest:nowatch": "./node_modules/.bin/cross-env CI=true npm run test"

九、小结F } L K l m e

至此,就完结了团队组件库的整个建立过程。

其他:

  • 库房代码
  • 预览地址
  • 了解我们团队