2、项目初始化:使用 Vite + TypeScript 搭建 React 组件库项目骨架、配置 ESLint 与 Prettier
好,咱们正式开始动手了。
这一章,我会带你从零搭一个 React 组件库的项目骨架。说白了,就是先把「地基」打牢。我见过不少同学一上来就写组件,写到一半发现目录结构乱成一锅粥,ESLint 报错满天飞,代码风格全靠手调……嗯,那种体验,真的很痛苦。
所以,咱们先把工具链配好。磨刀不误砍柴工嘛。
2.1 为什么选 Vite + TypeScript?
你可能要问:现在脚手架那么多,为什么非要用 Vite?
我个人习惯是:开发体验第一。Vite 的冷启动速度,你用过就回不去了。以前用 Webpack 搭组件库,每次改个配置要等好几秒,甚至十几秒。Vite 基于 ES Module,几乎是秒启。我在项目中遇到过几次大型组件库的构建,Webpack 跑一次要 3 分钟,换成 Vite 后直接降到 20 秒以内。
至于 TypeScript,这年头写组件库不用 TS,说实话有点说不过去。类型提示、接口约束、自动补全……这些对组件库的消费者来说,是刚需。
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 重构目录结构
我个人习惯把组件库的源码放在 packages 或 src 下,但为了后续可能的多包管理(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.json 的 scripts 里加一条:
"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 两种格式,兼容不同的使用场景。
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 lint 和 npm run format,确保没有报错。
2.9 本章小结
这一章,我们做了几件事:
- 用 Vite + TypeScript 初始化了项目
- 重构了目录结构,按组件库的思维组织代码
- 配置了 TypeScript,开启类型声明生成
- 配置了 ESLint,抓代码错误
- 配置了 Prettier,统一代码风格
- 配置了 Vite 的库模式构建
现在,你的项目骨架已经立起来了。下一章,我们会开始写第一个真正的组件——Button。到时候,这些配置就会派上用场。
嗯,先到这。去喝杯水,休息一下。