likes
comments
collection
share

React发布自定义组件库——高阶玩家必备~

作者站长头像
站长
· 阅读数 7

@NPM发包前置知识

@创建一个库工程

npm init vite@latest
  • 后续模板选择React+TS
  • 本例项目名为sto-ui-react

@封装一些组件

  • 此处对React组件封装细节不做过多讨论,源码在这里↓↓↓
  • 石头UI——一款巨简单好使的React管理端组件库
  • 大家可以学习一下其表格、分页器、筛选器、弹窗的封装思路
  • 本例发布其中两个组件 石头弹窗 StoPopup石头超级表格 StoTableX 为一个名曰 @steveouyang/sto-ui-reactNPM包

@认识一下Rollup

选择使用Rollup而不是Webpack来打包前端库的原因有以下几点:

  • 包大小和性能优化:Rollup以模块为单位进行打包,并将未使用的代码进行消除,从而生成更小、更高效的包。这对于前端库而言非常重要,因为减小包的大小可以提高加载速度,减少网络传输的成本,并且能够更好地支持Tree-shaking等优化技术。
  • 按需加载:Rollup支持按需加载,可以将代码拆分成多个块,并在需要的时候动态加载。这种按需加载的特性可以帮助优化前端库的加载速度和用户体验。
  • ES模块支持:Rollup天生支持ES模块的导入和导出语法,而Webpack则对ES模块的处理相对较慢。对于使用ES模块的前端库,使用Rollup能够更好地实现代码分割和按需加载,同时也更符合未来的标准。
  • 简单配置和使用:相对于Webpack,Rollup的配置更为简单明了,使用起来更加轻量级。Rollup更专注于打包库这个单一的功能,而Webpack则提供了更多的可定制化选项,适用于更复杂的应用场景。
  • 自动生成类型声明文件:不才在打包Vue的自定义指令和自定义hook库的时候没有写类型声明文件,是由Rollup的typescript组件自动生成的。

综上所述,选择Rollup作为前端库的打包工具主要是为了减小包大小、优化性能、支持按需加载和ES模块,并简化配置和 使用。然而,选择打包工具还是应根据具体的需求和场景来决定,如果你的应用场景较为复杂或者需要更多的可定制化选项,Webpack可能是更合适的选择。

@安装Rollup

# 全局安装(不推荐)
npm i -g rollup@2

# 工程内安装(推荐,兼容性好)
npm i -D rollup@2
  • Rollup的最新版本是4.0,但新版本稳定性未知且社区资源教程较少
  • 此处笔者选择安装的版本为2.0
  • 考虑到不同项目的兼容性,我们选择工程内安装

@安装依赖

package.json

  "dependencies": {
    "@rollup/plugin-typescript": "^11.1.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "rollup-plugin-babel": "^4.4.0"
  },
  "devDependencies": {
    "@rollup/plugin-babel": "^6.0.3",
    "@rollup/plugin-commonjs": "^25.0.3",
    "@rollup/plugin-node-resolve": "^15.1.0",
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "@vitejs/plugin-react": "^4.0.3",
    "eslint": "^8.45.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.3",
    "rollup": "^2.79.1",
    "rollup-plugin-auto-external": "^2.0.0",
    "rollup-plugin-copy": "^3.4.0",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.2",
    "sass": "^1.64.1",
    "tslib": "^2.6.1",
    "typescript": "^5.0.2",
    "vite": "^4.4.5"
  }

@准备包描述文件

注意这是组件库的包描述文件

rollup/package.json

{
  // 包名
  // 本例将包发布到NPM的笔者个人域名下
  "name": "@steveouyang/sto-ui-react",

  // 当前版本号 可升可降
  "version": "1.0.2",

  // 开源库 私有为false
  "private": false,

  // 包描述 一个好的包描述可以让你一夜爆红~~~
  "description": "一个狠好使的React+TS管理端组件库 / a React+TS based highly popular management component ui lib",
  
  // 包的入口文件(用户视角)
  "main": "bundle.es.js",

  // 测试脚本(本例暂时不涉及)
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },

  // 源码的Git仓库地址
  "repository": {
    "type": "git",
    "url": "https://gitee.com/steveouyang/sto-ui"
  },

  // 关键词
  "keywords": [
    "table",
    "form",
    "menu",
    "header",
    "popup",
    "btn-group",
    "uploader"
  ],

  // 作者
  "author": "steveouyang",

  // 开源证书
  "license": "ISC"
}

@准备类型声明文件

  • 大家根据组件的实际业务需求去声明即可
  • 本例中示范两个组件的类型声明

rollup/types/StoPopup.d.ts

type PopupProps = {
  children: ReactNode;
  closer: React.Dispatch<React.SetStateAction<boolean>>;
  title: string;
  onConfirm: () => void;
};

export const StoPopup: React.FC<PopupProps>

rollup/types/StoTableX.d.ts

type TableXProps = {
  data: Record<string, any>[];
  width?: number;
  formatters?: Record<string, (key: string, item: any) => any>;
  sortables?: string[];
  pageSize?: number;
  onSelectedItemsChanged?: (items: Set<Record<string, any>>) => void;
};

/* 组合组件:自带筛选器和分页器的超级表格 */
export const StoTableX: React.FC<TableXProps>

rollup/index.d.ts

export * from "./types/StoTableX"
export * from "./types/StoPopup"
  • 最后这个index.d.ts将在打包的最后阶段拷贝到dist目录中去
  • 也就是说,将来当用户引入包时可以这样
import { StoPopup, StoTableX } from "@steveouyang/sto-ui-react";

@配置打包脚本

rollup/rollup.config.js

/* 
引入所有可能需要的rollup插件 
@rollup/plugin-xxx 官方插件
rollup-plugin-xxx 野鸡插件(此处野鸡无贬义)
*/
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';
import typescript from '@rollup/plugin-typescript';
import postcss from 'rollup-plugin-postcss';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import autoExternal from 'rollup-plugin-auto-external';
import copy from 'rollup-plugin-copy';

/* 对外导出打包配置对象 */
export default {

  // 入口文件路径 开始分析依赖
  input: 'src/index.ts',

  /* 输出配置 */
  output: {
    file: 'dist/bundle.es.js', // 输出文件路径(ES格式)
    format: 'es', // 输出模块格式 组件库es就够了
    sourcemap: true, // 生成打包代码反查源代码位置的source-map文件 体积较大 如果对体积敏感可以设为false
  },

  /* 一堆插件 执行顺序按照配置的先后顺序执行 */
  plugins: [

    // 梳理优化同级依赖
    peerDepsExternal(),

    // 自动引入需要的外部依赖
    autoExternal(),

    // 解析node模块路径
    resolve(),

    // 将CommonJS模块转换为ES模块
    commonjs(),

    // 处理TypeScript
    typescript({
      declaration: true, // 输出类型声明文件
      declarationDir: 'dist', // 类型声明文件的输出目录
    }),

    // babel配置
    babel({
      babelHelpers: 'bundled',
      exclude: 'node_modules/**', // Babel转换要排除的文件夹
    }),

    // 处理CSS(默认添加浏览器兼容前缀)
    postcss(),

    /* 文件拷贝 */
    copy({
      
      targets: [
        // 拷贝包定义文件到dist
        { src: 'rollup/package.json', dest: 'dist' },

        // 拷贝README到dist 将来会显示在NPM包的主页上
        { src: 'rollup/README.md', dest: 'dist' },

        // 拷贝组件库的类型声明文件到dist
        { src: 'rollup/index.d.ts', dest: 'dist' },

        // 拷贝组件的类型声明文件到dist
        { src: 'rollup/types', dest: 'dist' },
      ],

      // 可选配置:
      // 如果设置为 true,则执行文件覆盖操作
      // 默认为 false,即不覆盖已存在的文件
      copyOnce: true,
    }),

  ],

};
  • 在入口文件中导出要打包的组件目录 src/index.ts
/* 导出需要打包的组件目录 */
export { default as StoFilter } from "./components/StoFilter"
export { default as StoPaginator } from "./components/StoPaginator"
export { default as StoPopup } from "./components/StoPopup"
export { default as StoTable } from "./components/StoTable2"
export { default as StoTableX } from "./components/StoTableX"

@执行打包

  • 执行打包
cd <工程根目录>
npx rollup -c rollup/rollup.config.js
  • 根目录/package.json 中配置一个快捷打包命令
  "scripts": {
    ...
    "build:lib":"npx rollup -c rollup/rollup.config.js"
  },
npm run build:lib
  • 最终的打包产出

React发布自定义组件库——高阶玩家必备~

@发布NPM包

# 没有安装nrm源管理工具时...
npm i -g nrm

# NPM全局切换到官方源
nrm use npm

# 登录steveouyang的NPM账号
npm login

# 来到dist目录下执行发包
cd dist
npm publish --access=public
  • 此时NPM账户下能够看到实时发布效果了 React发布自定义组件库——高阶玩家必备~
  • 查看本库的NPM主页:石头UI/React

@安装使用

安装石头UI

# 没有安装nrm源管理工具时...
npm i -g nrm

# NPM全局切换到官方源
nrm use npm

# 安装石头UI/React
npm i @steveouyang/sto-ui-react

使用石头弹窗

import React, { useState } from "react";

// 引入石头弹窗
import { StoPopup } from "@steveouyang/sto-ui-react";

export default function PopupDemo() {

  // 用一个state控制要不要显示弹窗
  const [showPopup, setShowPopup] = useState(false);

  return (
    <div>
      <h3>PopupDemo</h3>
      <button onClick={() => setShowPopup(true)}>显示弹窗</button>

      {/* 需要时显示弹窗显示弹窗 */}
      {showPopup && (

        // closer={setShowPopup} 告诉组件哪个state在控制弹窗的显隐 它好在必要时帮你关闭弹窗
        // title="我的弹窗" 弹窗标题
        // onConfirm={() => console.log("已确定")} 点击确定时的回调
        // <p>This is a modal content</p> 自定义弹窗内容
        <StoPopup
          closer={setShowPopup}
          title="我的弹窗"
          onConfirm={() => console.log("已确定")}
        >
          <p>This is a modal content</p>
        </StoPopup>
      )}

    </div>
  );
}

执行效果

React发布自定义组件库——高阶玩家必备~

使用石头表格

import React from "react";

// 引入石头超级表格
import { StoTableX } from "@steveouyang/sto-ui-react";

/* 造一堆测试数据 */
import { getStudents } from "../common/mockdata";
const students = getStudents(20, true);

/* 格式化数据字段 */
const formatters = {

  // 用一个a标签去显示学生姓名
  name: (key: string, item: any) => <a href={`/${item.id}`}>{item[key]}</a>,

  // 用a标签嵌套img显示学生头像
  avatar: (key: string, item: any) => (
    <a href={`/${item.id}`}>
      <img style={{ width: 40 }} src={`${item[key]}`} alt="" />
    </a>
  ),
};

/* 数据多选发生变化时回调 */
const onSelectedItemsChanged = (items: Set<Record<string, any>>) => {
  console.log("onSelectedItemsChanged", items);
};

/* 组合组件:自带筛选器和分页器的超级表格 */
export default function TableXDemo() {
  return (
    <div>
      TableXDemo

      {/* 
       data={students} 表格数据
       width={800}  设置表格宽度
       formatters={formatters} 字段的具体显示方式
       sortables={["name","age","score"]} 哪些字段支持排序
       pageSize={10} 一页显示多少条数据
       onSelectedItemsChanged={onSelectedItemsChanged} 多选发生变化时回调
      */}
      <StoTableX
        data={students}
        // width={800}
        formatters={formatters}
        sortables={["name","age","score"]}
        pageSize={10}
        onSelectedItemsChanged={onSelectedItemsChanged}
      ></StoTableX>

    </div>
  );
}

执行效果 React发布自定义组件库——高阶玩家必备~