第二章:项目初始化——从零搭起组件库的骨架

说实话,很多同学做组件库,一上来就急着写代码。我见过太多项目,写到一半发现 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',清爽很多。
避坑指南: 我曾经在配置 paths 后,忘了在 vite.config.ts 里也配一遍别名,结果 Vite 打包时死活找不到模块。记住,tsconfig 和 vite.config 要同步配置。

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,把 distnode_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 lintnpm run format。如果没报错,说明配置成功了。如果报错,别慌,看看错误信息,八成是某个依赖版本不匹配。

总结一下: 项目初始化就像盖房子打地基。虽然看不见摸不着,但地基不稳,后面盖多高都白搭。花半小时把 ESLint、Prettier、TypeScript 配好,能省下后面几十个小时的调试时间。

嗯,这章就到这。下一章我们开始写第一个组件,到时候你会感谢现在认真配环境的自己。