• 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.小册总结

上节做了 esm 和 commonjs、scss 代码的编译,并发布到 npm,在项目里使用没啥问题。

绝大多数情况下,这样就足够了。

umd 的打包做不做都行。

想一下,你是否用过 antd 的 umd 格式的代码?

是不是没用过?

但如果你做一个开源组件库,那还是要支持的。

这节我们就来做下 umd 的打包:

前面分析过,大多数组件库都用 webpack 来打包的。

我们先用下 antd 的 umd 的代码。

mkdir antd-umd-test
cd antd-umd-test
npm init -y

新建 index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
        <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
        <script src="https://unpkg.com/dayjs@1.11.11/dayjs.min.js"></script>
        <script src="https://unpkg.com/antd@5.19.0/dist/antd.min.js"></script>
    </head>
    <body></body>
</html>

antd 依赖的 react、react-dom、dayjs 包也得用 umd 引入。

跑个静态服务:

npx http-server .

浏览器访问下:

通过全局变量 antd 来访问各种组件。

我们渲染个 Table 组件试一下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
        <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
        <script src="https://unpkg.com/dayjs@1.11.11/dayjs.min.js"></script>
        <script src="https://unpkg.com/antd@5.19.0/dist/antd.min.js"></script>
    </head>
    <body>
        <div id="root"></div>
        <script>
            const dataSource = [
                {
                    key: "1",
                    name: "胡彦斌",
                    age: 32,
                    address: "西湖区湖底公园1号",
                },
                {
                    key: "2",
                    name: "胡彦祖",
                    age: 42,
                    address: "西湖区湖底公园1号",
                },
            ];

            const columns = [
                {
                    title: "姓名",
                    dataIndex: "name",
                    key: "name",
                },
                {
                    title: "年龄",
                    dataIndex: "age",
                    key: "age",
                },
                {
                    title: "住址",
                    dataIndex: "address",
                    key: "address",
                },
            ];

            const container = document.getElementById("root");
            const root = ReactDOM.createRoot(container);

            root.render(
                React.createElement(antd.Table, {
                    dataSource: dataSource,
                    columns: columns,
                })
            );
        </script>
    </body>
</html>

这里不能直接写 jsx,需要用 babel 或者 tsc 之类的编译一下:

浏览器看一下:

渲染成功!

这就是 umd 的方式如何使用组件库。

我们的组件库也支持下 umd:

加一下 webpack.config.js

const path = require("path");

/**
 * @type {import('webpack').Configuration}
 */
module.exports = {
    mode: "development",
    devtool: "source-map",
    entry: {
        index: ["./src/index.ts"],
    },
    output: {
        filename: "guang-components.js",
        path: path.join(__dirname, "dist/umd"),
        library: "Guang",
        libraryTarget: "umd",
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: "ts-loader",
                options: {
                    configFile: "tsconfig.build.json",
                },
            },
        ],
    },
    resolve: {
        extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
    },
    externals: {
        react: "React",
        "react-dom": "ReactDOM",
        dayjs: "dayjs",
    },
};

就是从 index.ts 入口开始打包,产物格式为 umd,文件名 guang-components.js,全局变量为 Guang。

用 ts-loader 来编译 ts 代码,指定配置文件为 tsconfig.build.json。

注意打包的时候不要把 react 和 react-dom、dayjs 包打进去,而是加在 external 配置里,也就是从全局变量来访问这些依赖。

安装依赖:

npm install --save-dev webpack-cli webpack ts-loader

这里的 jsdoc 注释是为了引入 ts 类型的,可以让 webpack.config.js 有类型提示:

js.png

对 jsdoc 感兴趣的话可以看我这篇文章:JSDoc 真能取代 TypeScript?

打包一下:

npx webpack

然后看下产物:

看起来没啥问题。

这三个模块也都是通过直接读取全局变量的方式引入,没有打包进去:

在 package.json 改下版本号,添加 unpkg 的入口,然后发布到 npm:

npm publish

在 unpkg 访问下:

访问 https://unpkg.com/guang-components 会自动重定向到最新版本的 umd 代码。

回到刚才的 antd-umd-test 项目,添加一个 index2.html,引入 guang-components

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
        <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
        <script src="https://unpkg.com/dayjs@1.11.11/dayjs.min.js"></script>
        <script src="https://unpkg.com/guang-components@0.0.7/dist/umd/guang-components.js"></script>
    </head>
    <body>
        <div id="root"></div>
        <script></script>
    </body>
</html>

浏览器访问下:

可以通过全局变量 Guang 来拿到组件。

css 也是通过 unpkg 来拿到:

然后我们渲染下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
        <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
        <script src="https://unpkg.com/dayjs@1.11.11/dayjs.min.js"></script>
        <script src="https://unpkg.com/guang-components@0.0.7/dist/umd/guang-components.js"></script>

        https://unpkg.com/guang-components@0.0.8/dist/esm/Calendar/index.css
    </head>
    <body>
        <div id="root"></div>
        <script>
            const container = document.getElementById("root");
            const root = ReactDOM.createRoot(container);

            root.render(
                React.createElement(Guang.Calendar, {
                    value: dayjs("2024-07-01"),
                })
            );
        </script>
    </body>
</html>

jsx 在 ts playground 编译:

浏览器访问下:

可以看到,umd 的组件库代码生效了。

但是控制台有个报错:

点进去可以看到是 _jsx 这个函数的问题:

react 我们通过 externals 的方式,从全局变量引入。

但是这个 react/jsx-runtime 不会。

这个 react/jsx-runtime 是干啥的呢?

同一份 jsx 代码:

你在 typescript playground 里把 jsx 编译选项切换为 react:

可以看到是不同的编译结果。

React 17 之前都是编译为 React.createElement,这需要运行的时候有 React 这个变量,所以之前每个组件都要加上 import React from 'react' 才行。

React 17 之后就加了下面的方式,直接编译为用 react/jsx-runtime 的 api 的方式。不再需要都加上 import React from 'react' 了。

我们组件库也是用的这种:

但现在打包 umd 代码的时候,这样有问题。

所以我们把 jsx 编译配置改一下就好了。

修改 jsx 为 react 之后,会有一些报错:

在每个报错的组件加一下 React 全局变量:

再次打包就好了:

改下版本号,重新发布一下:

npm publish

改下 index2.html 里用的组件库的版本号:

现在就没报错了:

这样,我们的组件库就支持了 umd。

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

总结

前面分析过,组件库基本都会提供 esm、commonjs、umd 三种格式的代码。

这节我们实现了 umd 的支持,通过 webpack 做了打包。

打包逻辑很简单:用 ts-loader 来编译 typescript 代码,然后 react、react-dom 等模块用 externals 的方式引入就好了。

再就是 react 通过 externals 的方式,会导致 react/jsx-runtime 引入有问题,所以我们修改了 tsconfig.json 的 jsx 的编译为 react,也就是编译成 React.createElement 的代码。

虽然 umd 的方式用的场景不多,但我们组件库还是要支持的。

上次更新: 6/21/25, 9:42 AM
贡献者: YNight
Prev
40.组件库实战:构建esm和cjs产物,发布到npm
Next
42.数据不可变:immutable和immer