React + Vite 搭建一个简单的组件库

本文介绍了如何使用Vite的库模式创建一个React+TypeScript的组件库,包括基本配置、组件编写、打包设置、发布到npm以及添加单元测试的流程。通过这个过程,开发者可以学习到如何利用Vite优化项目构建和测试效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近阅读了下 vite 的文档,发现它有个库模式用来打包挺方便的,因而写篇博客记录下折腾过程。

基本配置

执行如下命令创建一个 React + TypeScript 的项目

pnpm create vite

删除 src 和 public 文件夹,创建 example 和 packages 文件夹,其中 example 存放组件示例或者调试组件,packages 存放组件源码。另外别忘了修改根目录 index.html script路径。

├── node_modules
├── packages
├── example
├── index.html
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts

// index.html
<script type="module" src="/example/main.tsx"></script>
注:相关 eslint prettier tsconfig 的配置请自行查看末尾 git 仓库,这不是本文的重点。

下面我们打开 vite.config.ts,对打包进行配置(记得先安装下 @types/node )

import { readFileSync } from 'fs'
import path from 'path'
import { defineConfig } from 'vite'

import react from '@vitejs/plugin-react'

const packageJson = JSON.parse(
  readFileSync('./package.json', { encoding: 'utf-8' }),
)
const globals = {
  ...(packageJson?.dependencies || {}),
}

function resolve(str: string) {
  return path.resolve(__dirname, str)
}

export default defineConfig({
  plugins: [react()],
  build: {
    // 输出文件夹
    outDir: 'dist',
    lib: {
      // 组件库源码的入口文件
      entry: resolve('packages/index.tsx'),
      // 组件库名称
      name: 'demo-design',
      // 文件名称, 打包结果举例: suemor.cjs
      fileName: 'suemor',
      // 打包格式
      formats: ['es', 'cjs'],
    },
    rollupOptions: {
      //排除不相关的依赖
      external: ['react', 'react-dom', ...Object.keys(globals)],
    },
  },
})

此时你在 packages/index.tsx 文件夹中任意 export 些代码,他应该可以被正确打包成 CommonJS 与 ESM 了。

组件编写

为了简单起见,我们组件就编写一个有类型支持且可以切换颜色的 Tag。

安装依赖

pnpm i less clsx -D
下面这些 react 代码就不介绍了

编写 packages/Tag/interface.ts

import { CSSProperties, HTMLAttributes } from 'react'

/**
 * @title Tag
 */
export interface TagProps
  extends Omit<HTMLAttributes<HTMLDivElement>, 'className' | 'ref'> {
  style?: CSSProperties
  className?: string | string[]
  /**
   * @zh 设置标签背景颜色
   * @en The background color of Tag
   */
  color?: Colors
}

type Colors = 'red' | 'orange' | 'green' | 'blue'

编写packages/Tag/index.tsx

import clsx from 'clsx'
import { forwardRef } from 'react'
import './style'
import { TagProps } from './interface'

const Tag: React.ForwardRefRenderFunction<HTMLDivElement, TagProps> = (
  props,
  ref,
) => {
  const { className, style, children, color, ...rest } = props

  return (
    <div
      ref={ref}
      style={style}
      {...rest}
      className={clsx(className,'s-tag', `s-tag-${color}`)}
    >
      {children}
    </div>
  )
}

const TagComponent = forwardRef<unknown, TagProps>(Tag)

TagComponent.displayName = 'Tag'

export default TagComponent
export { TagProps }

编写 packages/Tag/style/index.less

@colors: red, orange, green, blue;

.s-tag {
  display: inline;
  padding: 2px 10px;
  each(@colors, {
    &-@{value} {
      background-color: @value;
      color: #fff;
    }
  });
}

编写 packages/Tag/style/index.ts

import './index.less';

编写 packages/index.tsx

export type { TagProps } from './Tag/interface'

export { default as Tag } from './Tag'

注意:此时如果我们进行打包会报错,因为我们没有安装 @rollup/plugin-typescript 插件,无法打包 ts 类型,生成 d.ts 。

pnpm i @rollup/plugin-typescript@8.5.0 -D    //这里最新版本似乎有些奇怪问题,所以我们先安装下 8.5.0 版本

去 vite.config.ts 引入插件

import typescript from '@rollup/plugin-typescript'

plugins: [
    react(),
    typescript({
      target: 'es5',
      rootDir: resolve('packages/'),
      declaration: true,
      declarationDir: resolve('dist'),
      exclude: resolve('node_modules/**'),
      allowSyntheticDefaultImports: true,
    }),
  ],

此时我们执行 pnpm build ,就完成了打包,生成如下目录

发布 npm

但此时我们把包发布到 npm 上,用户依旧是无法使用的,我们还需在 package.json上定义一下基础入口信息和类型声明:

{
  "name": "@suemor/demo-design",
  "version": "0.0.1",
  "type": "module",
  "main": "./dist/suemor.cjs",
  "module": "./dist/suemor.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "require": "./dist/suemor.cjs",
      "import": "./dist/suemor.js"
    },
    "./style": "./dist/style.css"
  },
  "publishConfig": {
    "access": "public"
  },
    //指定你要上传到 npm 上的文件夹
  "files": [
    "dist"
  ],
  ...
}

完成之后执行,即可发布到 npm 上。

npm publish

之后在你的其它项目中引入,即可正常显示,且具备 TypeScript 的类型提示。

import { Tag } from "@suemor/demo-design";
import '@suemor/demo-design/style'

const App = () => {
  return (
    <div>
      <Tag color="orange">我是标签</Tag>
    </div>
  );
};

export default App;

自此一个简单的组件库主体部分开发完毕(虽然很不完善),下面引入单元测试。

添加单元测试

我们使用 vitest 进行单元测试:

pnpm i vitest jsdom @testing-library/react -D

打开 vite.config.ts文件,在文件第一行添加类型声明,并在defineConfig加几行配置,让 rollup处理.test文件:

/// <reference types="vitest" />

test: {
    globals: true,
    environment: 'jsdom',
    coverage: {
      reporter: [ 'text', 'json', 'html' ]
    }
  }

再打开 package.json 添加 npm 命令:

  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "vitest"
  }

一般我们会把单测的代码放在 __test__ 文件夹下,所以新建 packages/Tag/__test__/index.test.tsx,代码如下:

import { describe, expect, it, vi } from 'vitest'

import { fireEvent, render, screen } from '@testing-library/react'

import { Tag, TagProps } from '../..'

const defineColor: Array<Pick<TagProps, 'color'> & { expected: string }> = [
  { color: 'red', expected: 's-tag-red' },
  { color: 'orange', expected: 's-tag-orange' },
  { color: 'green', expected: 's-tag-green' },
  { color: 'blue', expected: 's-tag-blue' },
]

const mountTag = (props: TagProps) => {
  return render(<Tag {...props}>Hello</Tag>)
}

describe('tag click', () => {
  const handleCallback = vi.fn()
  const tag = mountTag({ onClick: handleCallback })
  it('tag click event excuted correctly', () => {
    fireEvent.click(tag.container.firstChild as HTMLDivElement)
    expect(handleCallback).toHaveBeenCalled()
  })
})

describe.each(defineColor)('Tag color test', ({ color, expected }) => {
  it('tag color', () => {
    const tag = mountTag({ color })
    const element = tag.container.firstChild as HTMLDivElement
    expect(element.classList.contains(expected)).toBeTruthy()
  })
})

执行 pnpm test即可正常单元测试。

完整代码

完整代码仓库: https://github.com/suemor233/suemor-design-demo

<think>我们使用Vite搭建React + TypeScript项目,并集成Ant Design (antd)作为UI库。 步骤: 1. 使用Vite创建项目 2. 安装依赖(包括antd) 3. 配置antd(按需加载样式) 4. 修改App.tsx测试antd组件 具体操作如下: 1. 创建项目: npm create vite@latest my-react-app -- --template react-ts 2. 进入项目目录并安装依赖: cd my-react-app npm install npm install antd @ant-design/icons 3. 配置antd的按需引入(使用Vite的插件): 安装插件:npm install vite[email protected] -D 或者使用unplugin(推荐使用unplugin,因为vite-plugin-style-import可能更新不及时) 但是,antd v5推荐使用更简单的配置,因为v5支持按需引入,我们只需要在vite.config.ts中配置即可。 实际上,antd v5默认支持ES模块的按需引入,我们只需要在vite.config.ts中做如下配置(使用unplugin插件): 首先安装:npm i unplugin-import -D 但是,更简单的方式是使用`antd`的按需引入,我们可以通过直接引入组件的方式,因为antd默认支持ES模块树摇。 不过,为了样式的按需加载,我们需要一个插件来按需引入样式。我们可以使用`vite-plugin-imp`(但该插件已不再维护)或者使用`unplugin`系列。 这里我们使用`unplugin-auto-import`和`unplugin-vue-components`(针对Vue)不适用,针对React,我们可以使用`babel-plugin-import`,但在Vite中我们通常不使用Babel。 因此,我们采用手动引入组件和样式的方式,或者使用另一种方式:在Vite中配置CSS的预处理器。 实际上,antd v5使用CSS-in-JS,所以不需要额外的按需引入插件,只需要在项目中引入组件即可,样式会自动按需加载。但为了减少包体积,我们仍然可以手动引入组件。 例如:import Button from 'antd/es/button'; 这样只会引入Button组件和它的样式。但是这样写比较麻烦。 另一种方式是使用babel-plugin-import,但Vite默认使用esbuild,所以我们不使用babel。因此,我们可以选择手动引入,或者使用vite的插件。 目前,有一个vite插件`vite-plugin-imp`(但已不维护),所以我们可以考虑使用`unplugin-auto-import`和`unplugin-react-components`(这个插件可以自动为antd生成按需引入的代码)。 这里我们使用`unplugin-react-components`,它支持antd的自动按需引入。 安装: npm install unplugin-react-components -D 然后在vite.config.ts中配置: ```ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import Components from 'unplugin-react-components/vite' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ react(), Components({ // 配置选项 // 这里我们使用antd的解析器 resolvers: [ // 这里需要安装一个解析器,例如 `unplugin-react-components` 并没有内置antd解析器,所以我们需要使用另一个插件:`unplugin-antd5`? // 实际上,我们可以使用 `unplugin-react-components` 的antd解析器,但是查看文档,它似乎没有专门为antd提供解析器。 // 因此,我们可能需要换一种方式。 ], }), ], }) ``` 但是,经过调查,目前(2023)antd v5推荐的方式是直接使用ES模块导入,因为Tree Shaking已经足够好,所以我们可以不用插件,而是手动按需引入。 例如: import { Button } from 'antd'; 这样,生产构建时,Vite(使用rollup)会自动进行Tree Shaking,只打包用到的组件。 而样式方面,antd v5使用了CSS-in-JS,所以样式也会按需加载。因此,我们不需要额外的配置。 所以,我们只需要在项目中直接使用`import { Button } from 'antd';`即可。 4. 修改App.tsx,使用antd的Button组件测试。 5. 清理默认的App.css和index.css,避免影响antd的样式。 6. 在main.tsx中引入antd的全局样式(可选,如果需要重置样式等,但antd的组件样式是按需加载的,所以不需要全局引入所有样式)。但是,为了确保antd的样式正常,我们需要在main.tsx中引入antd的dist/reset.css(v5中不需要,因为CSS-in-JS会自动注入)。 7. 运行项目:npm run dev 但是,为了确保项目结构清晰,我们进行以下调整: - 创建views、components、utils等目录(按需) - 配置路径别名(可选) 配置路径别名(在vite.config.ts中): ```ts import path from 'path'; export default defineConfig({ // ...其他配置 resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, }) ``` 同时,在tsconfig.json中配置: ```json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } } ``` 8. 修改App.tsx测试: ```tsx import { Button } from 'antd'; function App() { return ( <div> <Button type="primary">Button</Button> </div> ); } export default App; ``` 9. 运行:npm run dev 如果出现按钮样式,则成功。 但是,antd v5默认使用dayjs,所以不需要额外安装moment。 总结步骤: 1. 创建项目:npm create vite@latest my-app -- --template react-ts 2. 进入项目:cd my-app 3. 安装依赖:npm install 4. 安装antd:npm install antd 5. 修改App.tsx,引入Button组件 6. 运行:npm run dev 注意:如果遇到样式问题,可以尝试在main.tsx中引入antd的reset样式(但v5不需要,因为每个组件都会注入自己的样式)。如果全局样式有冲突,可以引入一个重置样式表(例如normalize.css)。 另外,我们可以配置主题色等,通过antd的ConfigProvider,或者使用Vite的插件来修改less变量(因为antd v5使用CSS-in-JS,所以不再使用less,因此不需要配置less)。 因此,整个搭建过程非常简单。 但是,为了更好的开发体验,我们还可以配置eslint和prettier(可选)。 下面给出一个完整的vite.config.ts示例(包含路径别名):</think>以下是使用 React + Vite + TypeScript + Ant Design (antd) 搭建前端项目的完整步骤: ### 基础搭建步骤 1. **创建项目** ```bash npm create vite@latest my-app -- --template react-ts cd my-app ``` 2. **安装依赖** ```bash npm install antd @ant-design/icons npm install -D less vite-plugin-style-import ``` 3. **配置 `vite.config.ts`** ```ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import styleImport from 'vite-plugin-style-import' export default defineConfig({ plugins: [ react(), styleImport({ libs: [{ libraryName: 'antd', esModule: true, resolveStyle: (name) => `antd/es/${name}/style/index` }] }) ], css: { preprocessorOptions: { less: { javascriptEnabled: true, modifyVars: { 'primary-color': '#1890ff' // 自定义主题色 } } } } }) ``` 4. **配置 `tsconfig.json`** ```json { "compilerOptions": { "jsx": "react-jsx", "esModuleInterop": true, "types": ["vite/client"] } } ``` 5. **修改 `src/App.tsx`** (测试组件) ```tsx import { Button, DatePicker } from 'antd'; import { UserOutlined } from '@ant-design/icons'; function App() { return ( <div style={{ padding: 24 }}> <Button type="primary">按钮</Button> <DatePicker /> <UserOutlined style={{ fontSize: 24 }} /> </div> ); } export default App; ``` 6. **启动项目** ```bash npm run dev ``` ### 关键优化配置 1. **按需加载**:通过 `vite-plugin-style-import` 实现组件和样式的按需加载 2. **主题定制**:在 `less.modifyVars` 中修改 Ant Design 主题变量 3. **路径别名**(可选): ```ts // vite.config.ts resolve: { alias: { '@': path.resolve(__dirname, 'src') } } ``` ### 项目结构建议 ``` my-app/ ├── src/ │ ├── assets/ // 静态资源 │ ├── components/ // 通用组件 │ ├── pages/ // 页面组件 │ ├── utils/ // 工具函数 │ ├── App.tsx │ └── main.tsx ├── index.html └── vite.config.ts ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值