vite+react+antd,封装公共组件并发布npm包

发布于:2025-08-19 ⋅ 阅读:(14) ⋅ 点赞:(0)

一 概述

        为了公司项目系统风格统一,需要对部分公共组件进行封装。此文便是为了记录自己在探索过程中的步骤和注意点。这个例子主要是根据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,去修改版本号(不可重复)即可。


网站公告

今日签到

点亮在社区的每一天
去签到