一 概述
为了公司项目系统风格统一,需要对部分公共组件进行封装。此文便是为了记录自己在探索过程中的步骤和注意点。这个例子主要是根据UI设计的样式封装一个antd中的弹窗组件。
二 了解NPM
NPM(Node Package Manager)是一个 JavaScript 包管理工具,也是 Node.js 的默认包管理器(是 Node.js 自带的包管理工具,只要安装了 Node.js,NPM 就会自动安装在系统中)。
1. 作用
用于安装、共享和管理项目依赖的代码库(称为“包”或“模块”)。
2. 核心功能
3. 核心文件:package.json
4.重点要求
package.json中,除了dependencies、devDependencies,还有一个peerDependencies(开发包时候需要用到)。下面是这三者的区别,如下所示:
具体内容如下:
(1)dependencies(生产依赖)
作用:项目运行时必须的依赖(如 React、ReactDOM、Redux)。
安装命令:npm install xxxxxx 或 yarn add xxxxxx
特点:会被打包到最终的生产环境代码中(如 build/
目录);安装时递归安装子依赖。
(2)devDependencies(开发依赖)
作用:仅在开发阶段需要的工具(如测试库、打包工具、ESLint)。
安装命令:npm install xxxxxx--save-dev 或 yarn add xxxxxx -D
特点:不会打包到生产代码中;其他项目安装你的包时不会安装这些依赖
(3)peerDependencies(同伴依赖)
作用:声明你的包需要宿主环境提供的依赖(避免重复安装/版本冲突),常见于 React 组件库。
安装命令:npm install your -lib
特点:
用户安装你封装的库时,需手动安装 peerDependencies里面的内容,如果版本不匹配,会抛出警告。
如何定义(写入) :
npm install react --save-peer
或 yarn add react --peer
或 手动编辑package.json中的peerDependencies 对象
三 使用vite进行react项目创建。
运行效果如下:
至此,项目文件结构如下如下所示:
四 安装需要使用的依赖和注意事项
这里组件开发涉及到了 and、less等依赖包,因此需要进行手动安装。
注意事项:
这里将antd、react 和react-dom都需要安装在了开发依赖(npm install xxxxxx --save-dev)中。并且也在peerDependencies中手动新增(手动写上去)这几个依赖,dependencies中的依赖跟peerDependencies中的最低版本的依赖包最好相同,如react 在peerDependencies中为>=18.3.1,那么 dependencies 中版本为18.3.1。
原因:一般正常情况下,react、antd等依赖是需要安装在dependencies中。但是这个程序主要用于npm包的开发,如果写在dependencies中,那么,使用该组件程序中的 antd、react和react-dom的版本必须要和发布包中的antd、react和react-dom的版本一样,否则会报版本冲突的问题。这样就对使用程序限制了相关依赖版本,不太友好。
所以,在第二大点的第四小点介绍了peerDependencies的意义,这里就需要用到。然后,这里的依赖包版本也不能写死,需要写一个最低可使用版本即可。
因此,在代码中,我直接删除了package.json 中 dependencies 内容。将dependencies中的react和react-dom 重新安装到devDependencies 中。手动安装的依赖有:
npm install @ant-design/icons --save-dev
npm install antd --save-dev
npm install react --save-dev
npm install react-dom --save-dev
npm install less --save-dev
npm install vite-plugin-dts --save-dev
我的依赖文件如下所示(devDependencies对象里面没内容,package.json会自动删除,或者手动删除也可 ):
五 组件开发
1. 创建组件文件夹
在src下面新增一个components文件夹,并在里面创建一个自己的组件文件夹:CommonModal,并开始封装自己的组件。目录如下:
2. 创建组件代码
在index.tsx 里面开发自己的modal组件(如果想边开发边看效果,那么可以先把index.tsx搭一个框架后,根据下一步骤(步骤3)去预览,看到预览效果后过来继续开发,比较好调试)。我封装的弹窗代码如下:
index.tsx
import * as React from 'react';
import {CloseOutlined} from '@ant-design/icons';
import {Modal} from 'antd';
import styles from './index.module.less'
export interface CommonModalProps {
visible: boolean;
title?: string;
content?: React.ReactNode;
onCancel?: () => void;
width?: number;
}
export const CommonModal: React.FC<CommonModalProps> = ({
visible,
title,
content,
onCancel,
width,
}) => {
return (
<Modal
open={visible}
closeIcon={false}
onCancel={onCancel}
maskClosable={false}
width={width}
centered={true}
className={styles.customModal}
footer={null}
>
<div className={styles.modalHeader}>
<span className={styles.title}>{title}</span>
<CloseOutlined className={styles.close} onClick={onCancel}/>
</div>
<div className={styles.modalBody}>
{content}
</div>
</Modal>
);
};
注意:封装的组件和组件涉及到的ts类型都需要进行export导出。
index.module.less
.customModal {
:global {
.ant-modal-content {
padding: 0;
border-radius: 4px;
overflow: hidden;
}
}
.modalHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
background: cornflowerblue;
.title {
color: white;
font-weight: 700;
font-size: 20px;
}
.close {
cursor: pointer;
color: white;
font-size: 20px;
}
}
.modalBody {
padding: 20px 24px;
}
}
3. 组件效果预览
当组件开发了之后,要进行效果预览。这里就可以修改App.tsx里面的代码。App.css 里面的内容不用的话可以删除。不会影响组件的封装,只会影响本地样式。App.css直接为空也可。
而在App.tsx中,导出写的组件,直接写用法使用即可。我的App.tsx代码如下:
import {useState} from 'react'
import {CommonModal} from "./components/CommonModal";
import {Button} from "antd";
function App() {
const [visible, setVisible] = useState(false);
return (
<div>
<Button type={'primary'} onClick={() => setVisible(true)}>打开弹窗</Button>
<CommonModal visible={visible} title={'标题'}
onCancel={() => setVisible(false)}
content={<div>这是一个只展示的弹窗,不会展示确定按钮</div>}/>
</div>
)
}
export default App
程序运行后,效果如下:
至此,组要封装的组件就已经完毕,确定组件没问题后,就可以进行npm包的发布了。
六 发布包准备
进行NPM包的发布,需要对项目部分文件进行调整。
1. 新增包的入口文件
在src下面新增一个index.ts文件,并在里面导出封装的组件以及需要导出的样式文件。如:
import 'antd/dist/reset.css';
// 导出所有组件
export {CommonModal} from './components/CommonModal';
export type {CommonModalProps} from './components/CommonModal'
2. 新增vite-env.d.ts的内容
declare module '*.module.less' {
const classes: { readonly [key: string]: string };
export default classes;
}
3. 配置package.json文件
{
// 基础信息配置
"name": "my-test-lib", // 要发布的包名
"private": false,
"version": "1.0.0", // 包的版本号(每发布一次,都要修改,否则会发布失败)
"type": "module", // 声明项目使用 ES 模块(import/export 语法),而非 CommonJS。
// 下面内容是入口文件配置
"main": "./dist/index.es.js", // CommonJS/ESM 通用入口:当用户通过 require() 或 import 加载包时,默认指向此文件
"module": "./dist/index.es.js", // ES 模块专用入口:支持 ESM 的打包工具(如 Webpack、Rollup)会优先使用此路径,实现 Tree Shaking 优化
"types": "./dist/index.d.ts", // TypeScript 类型声明文件,提供类型支持(由 vite-plugin-dts 生成)
"style": "dist/style.css", // 全局样式入口,用户可通过 import 'wxf-component-library/style' 引入全量 CSS。
// 下面是发布配置
"files": [
"dist"
], // 发布到 npm 时仅包含 dist 目录(构建后的代码、类型声明和样式),忽略源码和非必要文件。
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview",
"prepublishOnly": "npm run build" // 在npm publish 前自动执行构建,确保发布的是最新产物
},
"peerDependencies": {
"antd": ">=5.24.1",
"react": ">=18.3.1",
"react-dom": ">=18.3.1"
},
"devDependencies": {
"@ant-design/icons": "^6.0.0",
"@eslint/js": "^9.33.0",
"@types/react": "^19.1.10",
"@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^5.0.0",
"antd": "^5.24.1",
"eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"less": "^4.4.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "~5.8.3",
"typescript-eslint": "^8.39.1",
"vite": "^7.1.2"
}
}
4. 修改 tsconfig.json 文件
{
"compilerOptions": {
"allowImportingTsExtensions": true, // 允许在导入语句中使用 TypeScript 特有的扩展名(如 .ts, .tsx)
"noEmit": true,
"declaration": true, // 生成 .d.ts 类型声明文件(即使 noEmit=true 也生效)、使组件库支持 TypeScript 类型提示
"declarationMap": true, // 为声明文件生成 Source Maps(.d.ts.map)、允许用户 "跳转到定义" 时直接查看源码而非声明文件
// "outDir": "./dist", // 输出目录(当有文件生成时)
// "declarationDir": "./dist", // 声明文件的专用输出目录
"jsx": "react-jsx", // 使用 React 17+ 的新 JSX 转换
"jsxImportSource": "react", // 指定 JSX 函数的导入来源为 react
"esModuleInterop": true, // 改进 ES 模块与 CommonJS 的互操作性,允许 import React from 'react' 代替 import * as React from 'react'
"skipLibCheck": true, // 跳过声明文件(.d.ts)的类型检查
"moduleResolution": "node" // 使用 Node.js 风格的模块解析策略
},
"include": ["src/**/*"], // 仅编译 src 目录下的所有文件
"exclude": ["node_modules", "dist"] // 排除 node_modules 和 dist 目录
}
5. 修改vite.config.ts文件
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import {resolve} from 'path'
import dts from "vite-plugin-dts";
export default defineConfig({
plugins: [
react(),
dts({
outDir: './dist',
insertTypesEntry: true, // 生成类型入口
include: ['src/**/*'],// 包含所有源文件
// 添加路径解析配置
compilerOptions: {
allowImportingTsExtensions: true
}
})
],
resolve: {
// 确保 Vite 能解析 .tsx 文件
extensions: ['.tsx', '.ts', '.js', '.jsx']
},
build: {
lib: {
entry: resolve('./src/index.ts'), // 组件库入口
name: 'MyTestLib', // 全局变量名
fileName: (format) => `index.${format}.js`
},
rollupOptions: {
// 外部化依赖,避免打包进库
external: ['react', 'react-dom', 'antd', '@ant-design/icons', 'react/jsx-runtime'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
antd: 'antd',
'@ant-design/icons': 'icons',
'react/jsx-runtime': 'jsxRuntime'
},
preserveModules: false
}
},
cssCodeSplit: true,
// sourcemap: true,
sourcemap: process.env.NODE_ENV !== 'production',
minify: false
},
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true
}
}
}
})
6. 文件结构如下
7. 发布打包测试
这里先进行发布前的打包测试。运行npm run build 。如果成功了,可以出现一个dist文件夹,里面有相关内容如下:
8. README.md
对于要发布的包,可以完善 README.md 文件,使用户可以更好的了解该组件以及其使用方式。示例如下:
七 开始发布
如果上面dist文件准确,则就可以进行npm发布了。步骤如下:
1. npm账号登录
发布包需要先登录npm账号,如果没有登录,会报403错误(报错后,点击提示信息中的登录网址也可以进行登录)。
(1)输入 npm login ,控制台会提示在浏览器进行登录,并赋予登录链接。
(2)点击链接,跳转浏览器登录
如果有账号,直接从邮箱里面获取一次性密码登录即可。如果没有,需要注册再登录。
如果登录成功,浏览器会显示:
并且控制台也会显示已登录字样
2. 开始发布
账号登录后,在该项目目录下运行npm publish 命令。
我这里首次发布报错 403,原因是:npm库已经有 my-test-lib 这个包了。所以继续发布是有问题的。因此需要改一下package.json和vite.config.ts 中的name,使包名唯一(无论该包是否被废弃还是有效)。
所以,我把包名改为了 teach-test-lib ,继续进行发布。就发布成功了。
发布成功后,在网页上登录自己的npm,会在上面看到已经发布的包。
3. 使用
发布包的使用方式跟npm上其他包的使用方式相同。直接使用npm或yarn安装即可。
注意:这里需要手动引入包的css样式。例子如下:
import { useState} from 'react';
import {Button} from 'antd'
import {CommonModal} from "teach-test-lib"; // 导入包
import "teach-test-lib/dist/index.css"; // 手动引入CSS样式
export default function Login() {
const [visible, setVisible] = useState(false);
return (
<div >
<Button type={"primary"} onClick={() => setVisible(true)}>打开弹窗</Button>
<CommonModal visible={visible} title={"标题"}
onCancel={() => setVisible(false)}
content={<div>这是引入的测试内容</div>}
/>
</div>
);
}
4. npm 包相关的其他命令
5. 发布403问题及解决方法
(1) 未登录:点击提示信息中的登录地址登录;
(2) 邮箱未验证:登录 npm 官网验证邮箱 ;
(3)包名已被占用 :用 npm view <包名>
检查,修改 package.json
中的 name;
(4)镜像源非官方(可能是自己的私服):切换回官方源:npm config set registry https://registry.npmjs.org;
(5) 版本号冲突:报错You cannot publish over previously published versions,
去修改版本号(不可重复)即可。