@NPM发包前置知识
- 为了发布一个React组件库,你需要先对NPM发包有一些基本了解
- 先好好读一虾 🍤 介篇文章↓↓↓
- NPM库的发布------诚意奉献,一文讲透
@创建一个库工程
shell
npm init vite@latest
- 后续模板选择React+TS
- 本例项目名为
sto-ui-react
@封装一些组件
- 此处对React组件封装细节不做过多讨论,源码在这里↓↓↓
- 石头UI------一款巨简单好使的React管理端组件库
- 大家可以学习一下其表格、分页器、筛选器、弹窗的封装思路
- 本例发布其中两个组件 石头弹窗
StoPopup
和 石头超级表格StoTableX
为一个名曰@steveouyang/sto-ui-react
的 NPM包
@认识一下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
shell
# 全局安装(不推荐)
npm i -g rollup@2
# 工程内安装(推荐,兼容性好)
npm i -D rollup@2
- Rollup的最新版本是4.0,但新版本稳定性未知且社区资源教程较少
- 此处笔者选择安装的版本为2.0
- 考虑到不同项目的兼容性,我们选择工程内安装
@安装依赖
package.json
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
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
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
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
ts
export * from "./types/StoTableX"
export * from "./types/StoPopup"
- 最后这个
index.d.ts
将在打包的最后阶段拷贝到dist目录中去 - 也就是说,将来当用户引入包时可以这样
js
import { StoPopup, StoTableX } from "@steveouyang/sto-ui-react";
@配置打包脚本
rollup/rollup.config.js
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
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"
@执行打包
- 执行打包
shell
cd <工程根目录>
npx rollup -c rollup/rollup.config.js
- 在 根目录/package.json 中配置一个快捷打包命令
json
"scripts": {
...
"build:lib":"npx rollup -c rollup/rollup.config.js"
},
shell
npm run build:lib
- 最终的打包产出
@发布NPM包
shell
# 没有安装nrm源管理工具时...
npm i -g nrm
# NPM全局切换到官方源
nrm use npm
# 登录steveouyang的NPM账号
npm login
# 来到dist目录下执行发包
cd dist
npm publish --access=public
- 此时NPM账户下能够看到实时发布效果了
- 查看本库的NPM主页:石头UI/React
@安装使用
安装石头UI
shell
# 没有安装nrm源管理工具时...
npm i -g nrm
# NPM全局切换到官方源
nrm use npm
# 安装石头UI/React
npm i @steveouyang/sto-ui-react
使用石头弹窗
tsx
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>
);
}
执行效果
使用石头表格
tsx
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>
);
}
执行效果