如何搭建一个公司私有React UI组件库
初始化工程项目
本项目以 vite 作为构建基础工具
pnpm create vite
选择 React + TS 模板 删除 index.html/tsconfig.app.json/tsconfig.node.json 文件 删除 src/App.tsx src/App.css src/index.css 文件
创建 src/components/ 目录 创建 src/components/index.tsx src/components/index.d.ts 文件 创建 src/components/Button.tsx src/components/index.tsx src/components/type.ts 文件
项目配置
- tsconfig.json 配置 指定ts编译配置
{
"compilerOptions": {
"outDir": "dist",
"module": "ESNext",
"target": "ES5",
"declaration": true,
"jsx": "react",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["src/components"],
"exclude": [
"node_modules",
"src/**/*.test.ts",
"src/**/*.test.tsx",
"src/**/*.stories.ts",
"src/**/*.stories.tsx"
]
}
- vite.config.ts 配置 需要修改 rollup 配置,新增以下 build 项配置内容
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: ["./src/components/index.ts"]
},
rollupOptions: {
external: ["react", "react-dom", "antd"],
experimentalLogSideEffects: false,
treeshake: true,
output: [
{
format: "cjs",
dir: "dist/cjs",
exports: "named",
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
assetFileNames: "[name].[ext]",
},
{
format: "es",
dir: "dist/es/",
exports: "named",
entryFileNames: "[name].js",
chunkFileNames: "[name].js",
assetFileNames: "[name].[ext]",
}
]
}
},
});
- package.json 配置 指定包入口文件和声明文件
"main": "dist/index.js",
"module": "dist/es/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
]
代码编写
- src/components/Button.tsx 组件内容
import React from "react";
import { Button as AntButton } from "antd";
import { ButtonProps } from "./type";
const Button = (props: ButtonProps) => {
const localProps = {
...{
primary: false,
size: "middle",
backgroundColor: "blue",
label: "Button",
onClick: () => {},
},
...props,
} satisfies ButtonProps;
return (
<AntButton {...localProps}>
{localProps.children ?? localProps.label}
</AntButton>
);
};
export default Button;
- src/components/Button/type.ts
用于定义组件的属性类型,也可直接写在组件内部,但为了更好的代码复用性,建议定义在单独的文件中。
export interface ButtonProps {
/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: "small" | "middle" | "large";
/**
* Button contents
*/
label: string;
/**
* Optional click handler
*/
onClick?: () => void;
/**
* Button contents
*/
children?: React.ReactNode;
}
- src/components/Button/index.tsx 组件出口文件,导出组件
import Button from './button';
export default Button;
- src/components/index.ts 所有组件出口文件,导出所有组件
export { default as XlButton } from "./Button";
// export { default as XlAlert } from "./Alert";
打包构建
基于vite.config.ts配置,和通常项目构建一致 执行
npm run build
打包输出资源目录dist,其结构如下 dist目录结构
dist
|-- Button
|-- button.d.ts
|-- button.js
|-- index.d.ts
|-- index.js
|-- OtherComponent
|-- OtherComponent.d.ts
|-- OtherComponent.js
|-- index.d.ts
|-- index.js
|-- index.d.ts
|-- index.js
测试
组件库需要充分测试后才打包构建发布至库管理平台 使用 npm link 命令可以实现包在本地的测试 在组件项目根目录执行 npm link 命令,将组件链接到本机全局 在测试项目根目录执行 npm link 组件名称 命令,将组件链接到本地,然后引入需要测试的组件,进行调试
发布
构建好的包需要发布之包管理平台
发布至npm
npm publish
发布至私有仓库
- 创建一个私有仓库 以 verdaccio 为例,根据 verdaccio 官网配置一个私有仓库,启动
- 配置npm的registry 在组件库的package.json中添加
"publishConfig": {
// 私有仓库地址
"registry": "http://localhost:4873"
}
- 添加npm用户
npm adduser --registry http://localhost:4873
- 发布
npm publish
注:包发布需要将private设置为false, package.json中 files 字段指定需要发布的文件。
文档生成
常用文档生成工具有 docz、storybook、dumi 等等 本项目采用 storybook 生成文档 初始化 storybook
npx storybook@latest init
以上命令会生成一个 .storybook 配置,然后执行 npm run storybook 命令即可启动 storybook 服务。
文档编写 如:在 src/components/Button/button.stories.ts 中添加如下代码
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import Button from './button';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Example/Button',
component: Button,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
backgroundColor: { control: 'color' },
},
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onClick: fn() },
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
};
export const Secondary: Story = {
args: {
label: 'Button',
},
};
export const Large: Story = {
args: {
size: 'large',
label: 'Button',
},
};
export const Small: Story = {
args: {
size: 'small',
label: 'Button',
},
};