从0-1封装一个React组件
第一步:初始化与安装依赖
创建一个空文件夹并初始化:
mkdir my-button
cd my-button
pnpm init
接下来安装依赖。对于组件库,核心原则是:
-
react和react-dom应该是 Peer Dependencies(宿主环境提供),而不是打包进去。 - 构建工具和类型定义是 Dev Dependencies。
执行下面命令
# 1. 安装构建工具、node类型定义、TS、Sass、类型定义生成插件 和 自动注入样式
pnpm add -D vite@5 @types/node typescript sass vite-plugin-dts vite-plugin-lib-inject-css
# 2. 安装 React 的类型定义 (开发时需要用到类型提示)
pnpm add -D @types/react @types/react-dom
# 3. (可选) 如果你开发时需要用到 react 的具体代码提示,也可以装一下,但最终不会打包
pnpm add -D react react-dom
第二步:手动创建配置文件
1. 创建 tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler" /* Vite 5 推荐 */,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx" /* 关键:支持 React */,
"strict": true,
"declaration": true /* 生成类型文件 */,
"declarationDir": "dist"
},
"include": ["src"]
}
2. 创建 vite.config.ts
// 导入 Vite 配置函数和所需插件
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
import { libInjectCss } from "vite-plugin-lib-inject-css";
import { resolve } from "path";
// 使用 defineConfig 定义 Vite 构建配置
export default defineConfig({
// 配置使用的插件
plugins: [
// 注入 CSS 到库中的插件
libInjectCss(),
// 生成类型声明文件(.d.ts)的插件
dts({
include: ["src/**/*.ts", "src/**/*.tsx"], // 包含的 TypeScript 文件类型
outDir: "dist", // 输出目录
rollupTypes: true, // 使用 Rollup 打包类型
}),
],
// 构建配置
build: {
// 库模式配置
lib: {
entry: resolve(__dirname, "src/index.ts"), // 库的入口文件
name: "MyButton", // UMD 格式的全局变量名
fileName: (format) => `index.${format}.js`, // 输出文件名格式
},
// Rollup 打包配置
rollupOptions: {
// 外部化依赖,不打包进库
external: ["react", "react-dom", "react/jsx-runtime"],
output: {
// 配置 UMD 格式的全局变量名
globals: {
react: "React",
"react-dom": "ReactDOM",
},
},
},
sourcemap: true, // 生成 sourcemap 便于调试
emptyOutDir: true, // 构建前清空输出目录
},
});
第三步:构建目录结构与源码
my-button/
├── src/
│ ├── components/
│ │ └── MyButton/
│ │ ├── index.tsx
│ │ └── index.module.scss
│ └── index.ts <-- 统一出口
├── package.json
├── tsconfig.json
└── vite.config.ts
编写组件 src/components/Button/index.tsx
import styles from "./index.module.scss";
export interface MyButtonProps {
label: string;
onClick?: () => void;
}
export const MyButton = ({ label, onClick }: MyButtonProps) => {
return (
<button className={styles["my-btn"]} onClick={onClick}>
{label}
</button>
);
};
编写样式 src/components/Button/index.module.scss
@use "sass:color";
.my-btn {
background-color: #007bff;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: color.adjust(#007bff, $lightness: -10%);
}
}
编写入口 src/index.ts
export * from "./components/MyButton";
第四步:配置 package.json (关键)
{
"name": "my-button",
"version": "1.0.0",
"description": "A lightweight React component library",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.umd.js",
"types": "./dist/index.d.ts"
},
"./index.css": "./dist/index.css"
},
"sideEffects": [
"**/*.css"
],
"scripts": {
"build": "tsc && vite build"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"keywords": [
"my-button"
],
"author": "hql",
"license": "ISC",
"packageManager": "pnpm@10.14.0",
"devDependencies": {
// ... 这里是刚才 pnpm add -D 安装的那些
}
}
在项目代码中使用
import { Button } from 'my-button';
function App() {
return <Button label="点击我" onClick={() => alert('Works!')} />;
}
本地调试的时候可以在项目中,使用Vite Alias 映射
alias: {
// 关键配置:将包名映射到组件库的【源码入口】
'my-button': '系统路径/组件包的文件夹名称(my-button)/src/components/MyButton/index.tsx',
},
修改业务项目的 tsconfig.json (关键)
{
"compilerOptions": {
// ...其他配置
"baseUrl": ".", // 启用 paths 必须配置 baseUrl
"paths": {
"my-button": [
// 这里也要填绝对路径
"系统路径/组件包的文件夹名称(my-button)/src/index.ts"
]
}
}
}
效果
-
无需打包:不需要在组件库里运行
pnpm build。 -
实时热更:在
my-button里改了 SCSS 颜色,Ctrl+S 保存,my-app页面毫秒级自动刷新样式。 - 源码调试:在浏览器的 DevTools 里看到的源码是 TS 原文件,而不是打包后的 JS,断点调试非常方便。
- 避免 React 冲突:因为直接编译源码,组件库会直接使用业务项目的 React 实例,完美避开 "Invalid Hook Call" 问题。
第五步:打包与本地验证
- 打包
pnpm build - 本地模拟发布 (最稳妥的测试方式)
pnpm pack - 在业务项目中测试,找一个你本地其他的 React 项目(或者随便新建一个测试项目):
# 假设你的 tgz 文件路径是 /Users/xxx/code/my-button/my-button-1.0.0.tgz
pnpm add /绝对路径/my-button-1.0.0.tgz
在代码中使用
import { Button } from 'my-button';
function App() {
return <Button label="点击我" onClick={() => alert('Works!')} />;
}
第六阶段:发布到 NPM
1. 准备工作
确保 package.json 中的 name 是唯一的(去 npmjs.com 搜一下)。
确保没有私有配置(如 .npmrc 指向了公司私有源),发布需要指向官方源:
npm config set registry https://registry.npmjs.org/
2. 登录与发布
# 登录 npm (如果没有账号需先注册)
npm login
# 升级版本号 (patch: 1.0.0 -> 1.0.1)
npm version patch
# 发布
npm publish
3. 验证
发布成功后,在你的业务项目中把之前的本地引用改回 npm 引用:
pnpm remove my-button
pnpm add my-button