• Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • Babel 插件通关秘籍
  • Git 原理详解及实用指南
  • Nest 通关秘籍
  • React 通关秘籍
  • TypeScript 全面进阶指南
  • TypeScript 类型体操通关秘籍
  • 现代CSS
  • React 通关秘籍

    • 1.关于本小册
    • 2.一网打尽组件常用Hook
    • 3.Hook的闭包陷阱的成因和解决方案
    • 4.React组件如何写TypeScript类型
    • 5.React组件如何调试
    • 6.受控模式VS非受控模式
    • 7.组件实战:迷你Calendar
    • 8.组件实战:Calendar日历组件(上)
    • 9.组件实战:Calendar日历组件(下)
    • 10.快速掌握Storybook
    • 11.React组件如何写单测
    • 12.深入理解Suspense和ErrorBoundary
    • 13.组件实战:Icon图标组件
    • 14.组件实战:Space间距组件
    • 15.React.Children和它的两种替代方案
    • 16.三个简单组件的封装
    • 17.浏览器的5种Observer
    • 18.组件实战:Watermark防删除水印组件
    • 19.手写react-lazyload
    • 20.图解网页的各种距离
    • 21.自定义hook练习
    • 22.自定义hook练习(二)
    • 23.用react-spring做弹簧动画
    • 24.react-spring结合use-gesture手势库实现交互动画
    • 25.用react-transition-group和react-spring做过渡动画
    • 26.快速掌握Tailwind:最流行的原子化CSS框架
    • 27.用CSSModules避免样式冲突
    • 28.CSSInJS:快速掌握styled-components
    • 29.react-spring实现滑入滑出的转场动画
    • 30.组件实战:Message全局提示组件
    • 31.组件实战:Popover气泡卡片组件
    • 32.项目里如何快速定位组件源码
    • 33.一次超爽的React调试体验
    • 34.组件实战:ColorPicker颜色选择器(一)
    • 35.组件实战:ColorPicker颜色选择器(二)
    • 36.组件实战:onBoarding漫游式引导组件
    • 37.组件实战:Upload拖拽上传
    • 38.组件实战:Form表单组件
    • 39.React组件库都是怎么构建的
    • 40.组件库实战:构建esm和cjs产物,发布到npm
    • 41.组件库实战:构建umd产物,通过unpkg访问
    • 42.数据不可变:immutable和immer
    • 43.基于ReactRouter实现keepalive
    • 44.Historyapi和ReactRouter实现原理
    • 45.ReactContext的实现原理和在antd里的应用
    • 46.ReactContext的性能缺点和解决方案
    • 47.手写一个Zustand
    • 48.原子化状态管理库Jotai
    • 49.用react-intl实现国际化
    • 50.国际化资源包如何通过Excel和GoogleSheet分享给产品经理
    • 51.基于react-dnd实现拖拽排序
    • 52.react-dnd实战:拖拽版TodoList
    • 53.ReactPlayground项目实战:需求分析、实现原理
    • 54.ReactPlayground项目实战:布局、代码编辑器
    • 55.ReactPlayground项目实战:多文件切换
    • 56.ReactPlayground项目实战:babel编译、iframe预览
    • 57.ReactPlayground项目实战:文件增删改
    • 58.ReactPlayground项目实战:错误显示、主题切换
    • 59.ReactPlayground项目实战:链接分享、代码下载
    • 60.ReactPlayground项目实战:WebWorker性能优化
    • 61.ReactPlayground项目实战:总结
    • 62.手写MiniReact:思路分析
    • 63.手写MiniReact:代码实现
    • 64.手写MiniReact:和真实React源码的对比
    • 65.React18的并发机制是怎么实现的
    • 66.Ref的实现原理
    • 67.低代码编辑器:核心数据结构、全局store
    • 68.低代码编辑器:拖拽组件到画布、拖拽编辑json
    • 69.低代码编辑器:画布区hover展示高亮框
    • 70.低代码编辑器:画布区click展示编辑框
    • 71.低代码编辑器:组件属性、样式编辑
    • 72.低代码编辑器:预览、大纲
    • 73.低代码编辑器:事件绑定
    • 74.低代码编辑器:动作弹窗
    • 75.低代码编辑器:自定义JS
    • 76.低代码编辑器:组件联动
    • 77.低代码编辑器:拖拽优化、Table组件
    • 78.低代码编辑器:Form组件、store持久化
    • 79.低代码编辑器:项目总结
    • 80.快速掌握ReactFlow画流程图
    • 81.ReactFlow振荡器调音:项目介绍
    • 82.ReactFlow振荡器调音:流程图绘制
    • 83.ReactFlow振荡器调音:合成声音
    • 84.AudioContext实现在线钢琴
    • 85.React服务端渲染:从SSR到hydrate
    • 86.小册总结

我们已经写了很多组件了,比如 Calendar、Watermark、OnBoarding 等,但都是用 cra 或者 vite 单独创建项目来写的。

这节我们把它们整合一下,加上构建脚本,发布到 npm,做成和 Ant Design 一样的组件库。

组件库的构建我们上节分析过,就是构建出 esm、commonjs、umd 3 种格式的代码,再加上 css 的构建就好了。

ant design、arco design、semi design 都是这样。

我们再看几个组件库:

mkdir tmp
cd tmp
npm init -y

安装 react-bootstrap:

pnpm install react-bootstrap

(用 pnpm 安装,node_modules 下目录比较简洁)

看下 node_modules/react-bootstrap 的 package.json

可以看到,它也有 main 和 module,也就是 commonjs 和 es module 两种入口。

当你 import 的时候,引入的是 esm 的代码。

当你 require 的时候,引入的是 commonjs 的代码。

看一下 esm 和 cjs 下的代码:

当然,它也是支持 umd 的:

看下 build 脚本:

就是分别用 babel 编译出 commonjs 和 esm 的代码就可以了:

用 tsc 也行。

umd 格式代码也同样是 webpack 打包的:

不同于 antd、arco design 和 semi design,它就没有用 gulp 来组织流程。

gulp 本来就不是必须的,可用可不用。

甚至连单独的脚本都不需要,直接 tsc 编译就行:

比如这个 blueprint 组件库:

之前总结的组件库的构建流程是没问题的:

然后我们新建一个项目来试一下:

npx create-vite guang-components

进入项目,复制几个之前的组件过来:

复制 Calendar、Watermark、Message 这三个组件:

然后安装下依赖:

npm install

npm install --save react-transition-group lodash-es dayjs classnames

npm i --save-dev @types/react-transition-group

然后去掉 Calendar 和 Message 组件里样式的引入,css 和 js 是分开编译的:

把这些没用的文件删掉:

加一个 index.ts 来导出组件:

import Calendar, { CalendarProps } from "./Calendar";
import Watermark, { WatermarkProps } from "./Watermark";
import { MessageProps, Position, MessageRef } from "./Message";
import { useMessage } from "./Message/useMessage";
import { ConfigProvider } from "./Message/ConfigProvider";

export { Calendar, Watermark, ConfigProvider, useMessage };

export type {
    CalendarProps,
    WatermarkProps,
    MessageProps,
    Position,
    MessageRef,
};

接下来加上 tsc 和 sass 的编译:

添加一个 tsconfig.build.json 的配置文件:

{
    "compilerOptions": {
        "declaration": true,
        "allowSyntheticDefaultImports": true,
        "target": "es2015",
        "lib": ["ES2020", "DOM", "DOM.Iterable"],
        "module": "ESNext",
        "skipLibCheck": true,
        "moduleResolution": "Node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "react-jsx",
        "allowImportingTsExtensions": null,
        "strict": true
    },
    "include": ["src"],
    "exclude": ["src/**/*.test.tsx", "src/**/*.stories.tsx"]
}

然后编译下:

npx tsc -p tsconfig.build.json --module ESNext --outDir dist/esm

npx tsc -p tsconfig.build.json --module commonjs --outDir dist/cjs

看下 dist 的产物:

没啥问题,esm 和 commonjs 格式的代码都生成了。

然后再编译下样式:

npx sass ./src/Calendar/index.scss ./dist/esm/Calendar/index.css

npx sass ./src/Calendar/index.scss ./dist/cjs/Calendar/index.css

npx sass ./src/Message/index.scss ./dist/esm/Message/index.css

npx sass ./src/Message/index.scss ./dist/cjs/Message/index.css

看下产物:

没问题。

当然,sass 文件多了以后你可以写个 node 脚本来自动查找所有 sass 文件然后编译。

接下来只要把这个 dist 目录发到 npm 仓库就可以了。

"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"files": [
    "dist",
    "package.json",
    "README.md"
],

main 和 module 分别是 commonjs 和 es module 的入口。

types 是 dts 的路径。

files 是哪些文件发布到 npm 仓库,没列出来的会被过滤掉。

并且,还需要把 private: true 和 type: module 的字段给去掉。

然后我们来发布 npm 包:

npm adduser

执行 npm adduser 命令,会打印一个链接让你登录或者注册:

登录后就可以 npm publish 了:

npm publish

发布完之后,在 https://www.npmjs.com 就可以搜索到:

我们新建个项目来用用看:

npx create-vite guang-test

安装依赖:

pnpm install

pnpm install guang-components

在 App.tsx 里用一下:

import { Watermark } from "guang-components";

const App = () => {
    return (
        <Watermark content={["测试水印", "神说要有光"]}>
            <div style={{ height: 800 }}>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
            </div>
        </Watermark>
    );
};

export default App;

跑下开发服务:

npm run dev

浏览器访问下:

再试下另外两个组件:

import dayjs from "dayjs";
import { Calendar } from "guang-components";
import "guang-components/dist/esm/Calendar/index.css";

function App() {
    return (
        <div>
            <Calendar value={dayjs("2024-07-01")}></Calendar>
        </div>
    );
}

export default App;

这里用到了 dayjs:

pnpm install dayjs

这里样式受 index.css 影响了,去掉就好了:

再来试下 Message 组件:

import { ConfigProvider, useMessage } from "guang-components";
import "guang-components/dist/esm/Message/index.css";

function Aaa() {
    const message = useMessage();

    return (
        <button
            onClick={() => {
                message.add({
                    content: "请求成功",
                });
            }}>
            成功
        </button>
    );
}

function App() {
    return (
        <ConfigProvider>
            <div>
                <Aaa></Aaa>
            </div>
        </ConfigProvider>
    );
}

export default App;

没啥问题。

然后我们优化下依赖:

其实用到 guang-components 的项目都会安装 react 和 react-dom 包,不需要把它放在 dependencies 里。

而是放在 peerDependencies 里:

它和 dependencies 一样,都是依赖,但是 dependencies 是子级,而 peerDependencies 是平级。

如果和其他 react 包的版本冲突时,dependencies 会保留一份副本,而 peerDependencies 会提示开发者去解决冲突,不会保留副本。

改下版本号,重新 npm publish:

这样,我们的组件库的 npm 包就发布成功了!

再测试下 commonjs 的代码。

用 cra 创建个项目:

npx create-react-app --template=typescript tmp4

进入项目,安装组件库:

npm install --save guang-components

在 App.tsx 用一下:

const { Watermark } = require("guang-components");

const App = () => {
    return (
        <Watermark content={["测试水印", "神说要有光"]}>
            <div style={{ height: 800 }}>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
                <p>
                    Lorem ipsum dolor, sit amet consectetur adipisicing elit.
                    Quos quod deserunt quidem quas in rem ipsam ut nesciunt
                    asperiores dignissimos recusandae minus, eaque, harum
                    exercitationem esse sapiente? Eveniet, id provident!
                </p>
            </div>
        </Watermark>
    );
};

export default App;

注意,这次用 require 引入代码。

然后把开发服务跑起来:

npm run start

浏览器里看一下:

没啥问题。

案例代码上传了小册仓库。

总结

今天我们把之前写过的部分组件封装成了组件库并发布到了 npm 仓库。

可以直接在项目里引入来用,和 antd 等组件库一样。

构建部分我们分析过很多组件库,都是一样的:

  • commonjs 和 esm 的代码通过 tsc 或者 babel 编译产生
  • umd 代码通过 webpack 打包产生
  • css 代码通过 sass 或者 less 等编译产生
  • dts 类型也是通过 tsc 编译产生

我们在 package.json 里配置了 main 和 module,分别声明 commonjs 和 es module 的入口,配置了 types 指定类型的入口。

然后通过 npm adduser 登录,之后 npm publish 发布到 npm。

这样,react 项目里就可以引入这个组件库来用了,之前写过的所有组件都可以加到这个组件库里。

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
39.React组件库都是怎么构建的
Next
41.组件库实战:构建umd产物,通过unpkg访问