第五章:工程化实践 - 第二节 - Tailwind CSS 组件库开发实战

devtools/2025/2/27 4:37:47/

开发一个基于 Tailwind CSS 的组件库不仅能提高团队开发效率,还能确保产品的设计一致性。本节将详细介绍如何从零开始构建一个专业的组件库。

!!! note
我们使用 React 来做项目相关的演示。
!!!

项目初始化

基础配置

# 创建项目
mkdir my-component-library
cd my-component-library
pnpm init# 安装依赖
pnpm add -D tailwindcss postcss autoprefixer typescript
pnpm add -D @types/react @types/react-dom
pnpm add -D vite @vitejs/plugin-react
pnpm add -D tsup# 安装 peer 依赖
pnpm add -D react react-dom

项目结构

src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.test.tsx
│   │   └── index.ts
│   ├── Input/
│   └── Select/
├── hooks/
│   └── useTheme.ts
├── styles/
│   ├── base.css
│   └── themes/
├── utils/
│   └── className.ts
└── index.ts

组件开发规范

组件基础模板

// src/components/Button/Button.tsx
import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';const buttonVariants = cva(// 基础样式'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',{variants: {variant: {default: 'bg-primary text-primary-foreground hover:bg-primary/90',destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',outline: 'border border-input hover:bg-accent hover:text-accent-foreground',secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',ghost: 'hover:bg-accent hover:text-accent-foreground',link: 'underline-offset-4 hover:underline text-primary',},size: {default: 'h-10 py-2 px-4',sm: 'h-9 px-3 rounded-md',lg: 'h-11 px-8 rounded-md',},},defaultVariants: {variant: 'default',size: 'default',},}
);export interface ButtonPropsextends React.ButtonHTMLAttributes<HTMLButtonElement>,VariantProps<typeof buttonVariants> {}const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, ...props }, ref) => {return (<buttonclassName={buttonVariants({ variant, size, className })}ref={ref}{...props}/>);}
);Button.displayName = 'Button';export { Button, buttonVariants };

类型定义

// src/types/components.ts
export type Variant = 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
export type Size = 'default' | 'sm' | 'lg';export interface BaseProps {className?: string;children?: React.ReactNode;
}export interface WithVariants {variant?: Variant;size?: Size;
}

样式系统

主题配置

// src/styles/theme.ts
export const theme = {colors: {primary: {DEFAULT: 'hsl(222.2, 47.4%, 11.2%)',foreground: 'hsl(210, 40%, 98%)',},secondary: {DEFAULT: 'hsl(210, 40%, 96.1%)',foreground: 'hsl(222.2, 47.4%, 11.2%)',},destructive: {DEFAULT: 'hsl(0, 84.2%, 60.2%)',foreground: 'hsl(210, 40%, 98%)',},// ...其他颜色},spacing: {// ...间距配置},borderRadius: {// ...圆角配置},
};

样式工具

// src/utils/className.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));
}// 使用示例
const className = cn('base-style',variant === 'primary' && 'primary-style',className
);

组件文档

Storybook 配置

// .storybook/main.js
module.exports = {stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],addons: ['@storybook/addon-links','@storybook/addon-essentials','@storybook/addon-interactions','@storybook/addon-a11y',],framework: {name: '@storybook/react-vite',options: {},},
};

组件文档示例

// src/components/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';const meta = {title: 'Components/Button',component: Button,parameters: {layout: 'centered',},tags: ['autodocs'],
} satisfies Meta<typeof Button>;export default meta;
type Story = StoryObj<typeof meta>;export const Primary: Story = {args: {children: 'Button',variant: 'default',},
};export const Secondary: Story = {args: {children: 'Button',variant: 'secondary',},
};

测试规范

单元测试配置

// src/components/Button/Button.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';describe('Button', () => {it('renders correctly', () => {render(<Button>Click me</Button>);expect(screen.getByRole('button')).toHaveTextContent('Click me');});it('handles click events', async () => {const handleClick = jest.fn();render(<Button onClick={handleClick}>Click me</Button>);await userEvent.click(screen.getByRole('button'));expect(handleClick).toHaveBeenCalled();});it('applies variant styles correctly', () => {render(<Button variant="destructive">Delete</Button>);expect(screen.getByRole('button')).toHaveClass('bg-destructive');});
});

构建和发布

构建配置

// tsup.config.ts
import { defineConfig } from 'tsup';export default defineConfig({entry: ['src/index.ts'],format: ['cjs', 'esm'],dts: true,splitting: false,sourcemap: true,clean: true,external: ['react', 'react-dom'],injectStyle: false,
});

包配置

{"name": "@your-org/components","version": "1.0.0","main": "./dist/index.js","module": "./dist/index.mjs","types": "./dist/index.d.ts","sideEffects": false,"files": ["dist/**"],"scripts": {"build": "tsup","dev": "tsup --watch","lint": "eslint src/","test": "jest","storybook": "storybook dev -p 6006","build-storybook": "storybook build"},"peerDependencies": {"react": "^18.0.0","react-dom": "^18.0.0"}
}

CI/CD 配置

GitHub Actions

# .github/workflows/ci.yml
name: CIon:push:branches: [main]pull_request:branches: [main]jobs:test:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v2- uses: pnpm/action-setup@v2- uses: actions/setup-node@v2with:node-version: '18'cache: 'pnpm'- name: Install dependenciesrun: pnpm install --frozen-lockfile- name: Lintrun: pnpm lint- name: Testrun: pnpm test- name: Buildrun: pnpm build

版本管理

Changesets 配置

// .changeset/config.json
{"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json","changelog": "@changesets/cli/changelog","commit": false,"fixed": [],"linked": [],"access": "restricted","baseBranch": "main","updateInternalDependencies": "patch","ignore": []
}

最佳实践

  1. 组件设计原则

    • 组件职责单一
    • 接口设计合理
    • 样式可定制
    • 可访问性支持
  2. 开发流程

    • 文档先行
    • TDD 开发
    • 代码审查
    • 持续集成
  3. 性能优化

    • 按需加载
    • Tree-shaking 支持
    • 样式优化
    • 包体积控制
  4. 维护策略

    • 版本控制
    • 更新日志
    • 问题跟踪
    • 文档更新

http://www.ppmy.cn/devtools/162964.html

相关文章

PortSwigger——Web LLMs attacks

文章目录 一、简介二、Exploiting LLM APIs, functions, and pluginsLab: Exploiting LLM APIs with excessive agencyLab: Exploiting vulnerabilities in LLM APIs 三、Indirect prompt injection&#xff08;间接提示注入&#xff09;Lab: Indirect prompt injection 四、Le…

matlab 七自由度车辆模型轮毂电机驱动电动汽车的振动分析

1、内容简介 matlab153-七自由度车辆模型轮毂电机驱动电动汽车的振动分析 可以交流、咨询、答疑 2、内容说明 略 3、仿真分析 略 4、参考论文 略

剑指offer - 面试题11 旋转数组的最小数字

题目链接&#xff1a;旋转数组的最小数字 第一种&#xff1a;正确写法&#xff08;num[m]和nums[r]比较&#xff09; class Solution { public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param nums int整型v…

快手弹幕 websocket 分析

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 逆向分析 import timeimport requests…

Fisher信息矩阵(Fisher Information Matrix, FIM)与自然梯度下降:机器学习中的优化利器

Fisher信息矩阵与自然梯度下降&#xff1a;机器学习中的优化利器 在机器学习尤其是深度学习中&#xff0c;优化模型参数是一个核心任务。我们通常依赖梯度下降&#xff08;Gradient Descent&#xff09;来调整参数&#xff0c;但普通的梯度下降有时会显得“笨拙”&#xff0c;…

解释SSR(服务器端渲染)和CSR(客户端渲染)的区别

在现代 Web 开发中&#xff0c;SSR&#xff08;服务器端渲染&#xff09;和 CSR&#xff08;客户端渲染&#xff09;是两种主要的渲染方式。它们各自具有独特的特性、优缺点和适用场景。本文将详细探讨这两者的概念、优缺点、适用场景以及在实际开发中的应用。 1. 概念定义 1…

算法-数据结构-图的构建(邻接矩阵表示)

数据定义 //邻接矩阵表示图 //1.无向图是对称的 //2.有权的把a,到b 对应的位置换成权的值/*** 无向图* A B* A 0 1* B 1 0*/ /*** 有向图* A B* A 0 1* B 0 0*/import java.util.ArrayList; import java.util.List;/*** 带权图* A B* A 0 1* B 0 0*/ p…

muduo源码阅读:linux timefd定时器

⭐timerfd timerfd 是Linux一个定时器接口&#xff0c;它基于文件描述符工作&#xff0c;并通过该文件描述符的可读事件进行超时通知。可以方便地与select、poll和epoll等I/O多路复用机制集成&#xff0c;从而在没有处理事件时阻塞程序执行&#xff0c;实现高效的零轮询编程模…