2、项目初始化:使用 Vite + TypeScript 搭建 React 组件库项目骨架、配置 ESLint 与 Prettier

好,咱们正式开始动手了。

这一章,我会带你从零搭一个 React 组件库的项目骨架。说白了,就是先把「地基」打牢。我见过不少同学一上来就写组件,写到一半发现目录结构乱成一锅粥,ESLint 报错满天飞,代码风格全靠手调……嗯,那种体验,真的很痛苦。

所以,咱们先把工具链配好。磨刀不误砍柴工嘛。

2.1 为什么选 Vite + TypeScript?

你可能要问:现在脚手架那么多,为什么非要用 Vite?

我个人习惯是:开发体验第一。Vite 的冷启动速度,你用过就回不去了。以前用 Webpack 搭组件库,每次改个配置要等好几秒,甚至十几秒。Vite 基于 ES Module,几乎是秒启。我在项目中遇到过几次大型组件库的构建,Webpack 跑一次要 3 分钟,换成 Vite 后直接降到 20 秒以内。

至于 TypeScript,这年头写组件库不用 TS,说实话有点说不过去。类型提示、接口约束、自动补全……这些对组件库的消费者来说,是刚需。

核心原则:组件库是给其他人用的,类型定义就是你的「说明书」。TypeScript 能帮你把说明书写得清清楚楚。

2.2 初始化项目

打开终端,找个你喜欢的目录,执行:

npm create vite@latest my-component-library -- --template react-ts

这里 my-component-library 是你的项目名,你可以换成自己的。后面的 react-ts 模板会自动帮你配好 React + TypeScript 的基础环境。

跑完之后,你会看到这样的目录结构:

my-component-library/
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
├── src/
│   ├── App.tsx
│   ├── App.css
│   ├── main.tsx
│   └── vite-env.d.ts
└── public/
    └── vite.svg

嗯,这是 Vite 默认生成的。但我们要做的是组件库,不是普通应用。所以,得大刀阔斧地改一下。

2.3 重构目录结构

我个人习惯把组件库的源码放在 packagessrc 下,但为了后续可能的多包管理(monorepo),我建议一开始就按「库」的思维来组织。

咱们先删掉 src 里那些 App 相关的文件,然后新建这样的结构:

src/
├── components/       # 组件源码
│   ├── Button/
│   │   ├── index.tsx
│   │   └── style.css
│   └── index.ts      # 统一导出
├── hooks/            # 自定义 hooks
├── utils/            # 工具函数
├── styles/           # 全局样式、主题变量
└── index.ts          # 库的入口文件

为什么要这样分?

  • components/:每个组件一个文件夹,方便管理样式和类型
  • hooks/:组件逻辑抽离出来,复用性更高
  • utils/:纯函数,不依赖 React
  • styles/:主题、变量、全局 reset,统一管理

我在项目中遇到过最头疼的事,就是组件和样式混在一起,改一个按钮的样式要翻三个文件夹。所以,一开始就分好,后面会省很多事。

2.4 配置 TypeScript

Vite 生成的 tsconfig.json 基本够用,但我们要针对组件库做一些调整。打开 tsconfig.json,改成这样:

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "declaration": true,           // 生成 .d.ts 类型声明文件
    "declarationDir": "./dist",    // 声明文件输出目录
    "outDir": "./dist"             // 编译输出目录
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
小提示:declaration: true 这个配置很容易被忽略。没有它,别人用你的组件库时就拿不到类型提示,体验会大打折扣。

2.5 配置 ESLint

ESLint 是干嘛的?说白了,就是帮你抓代码里的低级错误。比如变量定义了没用、函数缺少返回值、用了 == 而不是 ===……

安装依赖:

npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks --save-dev

然后在项目根目录创建 .eslintrc.cjs

module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['dist', '.eslintrc.cjs'],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    ecmaFeatures: { jsx: true },
  },
  plugins: ['react', '@typescript-eslint'],
  settings: {
    react: { version: 'detect' },
  },
  rules: {
    'react/react-in-jsx-scope': 'off',  // React 17+ 不需要显式 import React
    'react/prop-types': 'off',          // 我们用 TypeScript,不用 PropTypes
    '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
  },
};
注意:react/react-in-jsx-scope 这个规则在 React 17+ 之后可以关掉。我曾经在一个老项目里忘了关,结果每个文件都要写 import React from 'react',烦得很。

配置好之后,在 package.jsonscripts 里加一条:

"lint": "eslint src --ext .ts,.tsx"

跑一下 npm run lint,看看有没有报错。如果有,按提示改就行。

2.6 配置 Prettier

ESLint 管的是「对不对」,Prettier 管的是「好不好看」。两者分工明确。

安装:

npm install prettier eslint-config-prettier eslint-plugin-prettier --save-dev

创建 .prettierrc

{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "printWidth": 100,
  "bracketSpacing": true,
  "arrowParens": "always"
}

我个人习惯用单引号、不加分号(但这里我设了 semi: true,因为团队里有人喜欢)。这个看你自己,没有绝对的对错。

然后,为了让 ESLint 和 Prettier 不打架,需要把 eslint-config-prettier 加到 ESLint 配置的 extends 里,放在最后:

extends: [
  'eslint:recommended',
  'plugin:@typescript-eslint/recommended',
  'plugin:react/recommended',
  'plugin:react-hooks/recommended',
  'prettier',  // 放在最后,覆盖前面的格式规则
],

再在 package.json 里加一条脚本:

"format": "prettier --write \"src/**/*.{ts,tsx,css,json}\""

跑一下 npm run format,所有文件都会被自动格式化。嗯,看着舒服多了。

2.7 配置 Vite 构建

组件库最终要打包成 dist 目录,供别人安装使用。所以 vite.config.ts 需要针对库模式做配置:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.ts'),
      name: 'MyComponentLibrary',
      formats: ['es', 'umd'],
      fileName: (format) => `my-component-library.${format}.js`,
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
        },
      },
    },
  },
});

这里有几个关键点:

  • lib.entry:指定库的入口文件,就是咱们之前建的 src/index.ts
  • external:把 React 和 ReactDOM 排除在打包之外。因为使用你组件库的人,自己会安装 React。如果你把 React 也打包进去,那体积就大了,还可能版本冲突。
  • formats:输出 ES Module 和 UMD 两种格式,兼容不同的使用场景。
避坑指南:我曾经把 React 打包进了组件库,结果用户的项目里有两个 React 实例,导致 hooks 报错「Invalid hook call」。排查了半天才发现是这个问题。所以,external 一定要配。

2.8 验证一下

src/index.ts 里写个简单的导出:

export { default as Button } from './components/Button';

然后在 src/components/Button/index.tsx 里写个最简单的按钮:

import React from 'react';

const Button: React.FC = () => {
  return <button>Click me</button>;
};

export default Button;

跑一下 npm run build,看看 dist 目录下有没有生成文件。如果有,说明咱们的骨架搭好了。

再跑一下 npm run lintnpm run format,确保没有报错。

2.9 本章小结

这一章,我们做了几件事:

  1. 用 Vite + TypeScript 初始化了项目
  2. 重构了目录结构,按组件库的思维组织代码
  3. 配置了 TypeScript,开启类型声明生成
  4. 配置了 ESLint,抓代码错误
  5. 配置了 Prettier,统一代码风格
  6. 配置了 Vite 的库模式构建

现在,你的项目骨架已经立起来了。下一章,我们会开始写第一个真正的组件——Button。到时候,这些配置就会派上用场。

嗯,先到这。去喝杯水,休息一下。