第二章:项目初始化——从零搭起组件库的骨架
说实话,很多同学做组件库,一上来就急着写代码。我见过太多项目,写到一半发现 TypeScript 报错满天飞,ESLint 和 Prettier 打架打得不可开交。嗯,这章我们就先把地基打牢。
2.1 使用脚手架创建项目
我个人习惯用 Vite 来初始化项目。为什么选 Vite?因为它快,真的快。我在项目中经历过 Webpack 构建等 3 分钟的痛苦,换 Vite 后基本秒开。
# 创建项目
npm create vite@latest my-component-library -- --template react-ts
# 进入目录
cd my-component-library
# 安装依赖
npm install
这里要注意,模板选 react-ts,它会帮你配好 React + TypeScript 的基础环境。你想想看,如果自己手动配,光 Webpack 的 loader 就能折腾半天。
package.json 里的 name 字段改成 @your-scope/component-library 这种格式。这样后续发布 npm 包时,scope 命名空间会清晰很多。
2.2 目录结构设计
目录结构这事,说白了就是「怎么放代码才不打架」。我在项目中踩过不少坑,比如把组件和工具函数混在一起,最后找文件找到崩溃。下面是我总结的一套结构:
my-component-library/
├── src/
│ ├── components/ # 组件源码
│ │ ├── Button/
│ │ │ ├── index.tsx
│ │ │ ├── style.css
│ │ │ └── types.ts
│ │ └── index.ts # 统一导出
│ ├── hooks/ # 自定义 Hooks
│ ├── utils/ # 工具函数
│ ├── styles/ # 全局样式
│ └── types/ # 全局类型定义
├── docs/ # 文档
├── tests/ # 测试
├── dist/ # 构建输出
├── package.json
├── tsconfig.json
└── vite.config.ts
为什么每个组件要单独建文件夹?我曾经把 Button 的样式和逻辑写在一个文件里,后来要加个变体,改得我头皮发麻。每个组件独立文件夹,方便后续扩展和维护。
2.3 配置 TypeScript
TypeScript 配置,说白了就是告诉编译器「你该怎么检查我的代码」。我见过最坑的配置是 strict: false,那还不如不用 TS。
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"declaration": true,
"declarationDir": "./dist/types",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
这里有几个关键点:
- strict: true —— 必须开。不开 strict 的 TS 项目,我建议直接删掉 TS。
- declaration: true —— 生成类型声明文件。组件库没有类型声明,别人用起来会骂娘的。
- paths 别名 —— 配置
@/指向src/,这样引入组件时写import Button from '@/components/Button',清爽很多。
2.4 配置 ESLint 与 Prettier
ESLint 和 Prettier 的关系,就像警察和城管。ESLint 管代码质量(比如不能用 var),Prettier 管代码格式(比如缩进几个空格)。
先装依赖:
npm install -D eslint prettier eslint-config-prettier eslint-plugin-react @typescript-eslint/eslint-plugin @typescript-eslint/parser
然后配 ESLint:
// .eslintrc.cjs
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'prettier', // 让 Prettier 接管格式
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['@typescript-eslint', 'react'],
rules: {
'react/react-in-jsx-scope': 'off', // React 17+ 不需要引入 React
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
settings: {
react: {
version: 'detect',
},
},
};
再配 Prettier:
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always"
}
package.json 里加两个脚本:
"lint": "eslint src --ext .ts,.tsx",
"format": "prettier --write src/**/*.{ts,tsx,css}"
每次提交代码前跑一遍,能省去很多 review 时的口水战。
最后,别忘了加个 .eslintignore 和 .prettierignore,把 dist、node_modules 这些目录排除掉。我曾经忘了加,结果 ESLint 检查 node_modules,卡了整整 5 分钟。
2.5 验证配置是否生效
配置完了,怎么知道好不好使?写个简单的 Button 组件试试:
// src/components/Button/index.tsx
import React from 'react';
interface ButtonProps {
label: string;
onClick?: () => void;
variant?: 'primary' | 'secondary';
}
const Button: React.FC<ButtonProps> = ({ label, onClick, variant = 'primary' }) => {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{label}
</button>
);
};
export default Button;
然后跑 npm run lint 和 npm run format。如果没报错,说明配置成功了。如果报错,别慌,看看错误信息,八成是某个依赖版本不匹配。
嗯,这章就到这。下一章我们开始写第一个组件,到时候你会感谢现在认真配环境的自己。