函数库Rollup构建优化

news2024/12/22 20:29:36

本节涉及的内容源码可在vue-pro-components c7 分支[1]找到,欢迎 star 支持!

前言

本文是基于Vite+AntDesignVue打造业务组件库[2] 专栏第 8 篇文章【函数库Rollup构建优化】,在上一篇文章的基础上,聊聊在使用 Rollup 构建函数库的过程中还可以做哪些优化。

terser 压缩

在上篇文章中,我们掌握了怎么打包 ESM, CJS, UMD,还掌握了怎么生成类型声明文件d.ts,但是我们可以发现,我们生成的 UMD 文件dist/index.js并不知道没有经过压缩,我们可以尝试给它再压缩一下,这可以用到 Rollup 官方的插件 rollup-plugin-terser。

c943cf74cedf0224349e93207a792055.png

由于压缩版通常是直接通过script标签引入用在浏览器环境中,所以打包成 IIFE(立即执行函数表达式)格式就行。我们改造一下buildBundle函数。

export const buildBundle = async () => {
    const bundle = await rollup({
        input: resolve(UTILS_PATH, 'src/index.ts'),
        plugins: [rollupTypescript()],
    })

    await Promise.all([
        bundle.write({
            name: 'VpUtils',
            format: 'umd',
            file: resolve(UTILS_PATH, 'dist/index.js'),
            sourcemap: true,
        }),
        bundle.write({
            name: 'VpUtils',
            // 考虑到使用场景,输出 iife 格式即可
            format: 'iife',
            // 生成一个 dist/index.min.js 作为压缩版本
            file: resolve(UTILS_PATH, 'dist/index.min.js'),
            // 使用了 rollup-plugin-terser 插件
            plugins: [terser()]
        })
    ])
}

再次打包就会生成这种 IIFE 的压缩代码了。

3f897a6cc9a5bd46006b582959aee0b7.png

按需使用子模块时提供类型支持

我们已经支持了生成类型声明文件,所以正常使用@vue-pro-components/utils模块时,是有类型支持的。

9b1620692e3f731e4f042243bc420dc0.png

可以看到,上面的函数签名都是有的。

但是,当我们按需使用其中一个模块时,会发现 TypeScript 似乎找不到对应的类型声明。

adc634023dc30616d85fa02a95a5e3de.png

观察上图可以发现,当我们引用其中一个模块的完整路径时,TypeScript 报了错表示找不到类型声明文件。这是为什么呢?明明我们已经生成了d.ts,也配置了 package.json 文件中的types属性......

实际上,package.json 中的types属性只是为简单的包名引用提供了类型声明文件的路径,也就是说types只是让import { xxx } from '@vue-pro-components/utils'有了类型支持。对其他的路径下的模块引用并没有什么帮助。

不慌,在导入.js模块时,TypeScript 会自动加载与.js同名的.d.ts文件,以提供类型声明。我们可以在生产的es/fullscreen.js文件的相同目录中放置一个fullscreen.d.ts试试(从 types 目录抄过来即可)。

335529f7322089e904520794bed6993f.png

可以发现已经不报错了,那我们的思路就很清晰了,只要把 types 目录下生成的类型声明文件抄一份到 es 和 lib 目录,就可以保证按需使用模块时的类型支持了。

我们回忆一下整个流程,

5bac56439a81b0994db43b23522bfef6.png

不难想明白要抄一份类型声明文件到 es 和 lib 目录,最好的时机就是在并行任务结束之后,再补一个 copy dts 节点。copy 文件在 gulp 里是很容易实现的,不需要借助任何插件。通过 src 取得输入后,可以用两个 pipe + dest 分别 copy 到 es 和 lib 目录中。

export const copyDts = async () => {
    return src("types/**/*.d.ts", {
        cwd: UTILS_PATH,
    })
        .pipe(dest(resolve(UTILS_PATH, "es")))
        .pipe(dest(resolve(UTILS_PATH, "lib")))
}

然后改造一下入口函数startBuildUtils,在并行任务结束后,加一个 copyDts 节点。

export const startBuildUtils = series(
    parallel(buildModules, buildBundle, buildTypes),
    copyDts
)

效果如下:

fc4416aa58bd2975e5eb093af556ce2f.gif


基于此,我们按需使用任何一个子模块都能得到完备的类型支持了。

第三方依赖解析和打包问题

当函数库依赖第三方模块时,我们需要考虑打包问题。

比如:打包成 ESM / CJS / UMD / IIFE 模块时,第三方依赖是作为 external,还是将其代码直接打进产物里?

当依赖作为 external 处理时,就代表着函数库的构建产物中不包含对应依赖的代码,打包出来的大小也会相对小一点。

当依赖的代码直接打进产物中,很显然会增大构建产物的大小。

这就需要考虑第三方依赖的性质和大小。如果第三方依赖是某个运行时框架或者依赖的体积很大,那最好作为 external 处理,由调用方提供具体的依赖。反之可以酌情将依赖打进构建产物中,避免调用方在依赖问题花费太多的精力。

为了验证第三方依赖问题,我特意加了一个date-utils.ts,这是一个基于dayjs的日期函数集合。

针对 ESM / CJS 情况,最好将第三方依赖作为 external 处理,因为除了我的函数库会依赖dayjs,项目中也可能会依赖dayjs,在构建工具的帮助下,能在 Dependency Graph 中实现复用。

我们将buildModules改一改,

const bundle = await rollup({
    input,
    plugins: [rollupTypescript()],
    // 把依赖作为 external(dependencies 中包含 dayjs)
    external: Object.keys(pkgJson.dependencies),
})

重新打包会发现报了一个错,

'dayjs' is imported by packages/utils/src/date-utils.ts, but could not be resolved – treating it as an external dependency

因为 Rollup 默认的模块解析策略符合 ESM 规范,只有从相对路径上找得到的模块,才能被成功解析。

我们可能已经习惯了import { ref } from "vue"这种用法,就会想当然认为 Rollup 默认也能理解这种引用第三方依赖的行为,实际上并不能。我们熟悉的这种模块解析策略其实是遵从 Node Resolution Algorithm,它是 NodeJS 的默认行为,并不是 ESM 的默认行为。

362286c3a8ddbc6c1be5e5a9e69538bd.png

这个问题需要借助插件@rollup/plugin-node-resolve[3]来解决。

首先安装一下依赖,

yarn add -DW @rollup/plugin-node-resolve

然后在插件中引用它,

const bundle = await rollup({
    input,
    plugins: [rollupTypescript(), nodeResolve()],
    external: Object.keys(pkgJson.dependencies),
})

但我们继续打包还是会遇到一个问题:

9342c5bdfb8fdfcd5e0405e0037df5a8.png

关键信息是:

Error: 'default' is not exported by node_modules/dayjs/dayjs.min.js, imported by packages/utils/src/date-utils.ts

其实这是因为 dayjs 的 package.json 中只给出了main入口,而没有配置module入口,而main入口指定的不是符合 ESM 规范的文件,从而导致这个问题。我当时还给 dayjs 提了一个PR[4]说明了这个问题,希望增加module入口优化这个问题,不过 dayjs 团队似乎不太在意这个问题,关闭了这个 PR,建议我改用 v2 alpha 版本,实际上 v1 版本后面也一直在更新和发版。

不过没关系,即便有一些模块不符合 ESM 规范也是合情合理,毕竟 npm 生态中还有很多不支持 ESM 的包,Rollup 自然也考虑到了这一点,给出了插件@rollup/plugin-commonjs[5],那我们直接用上它就好了。

export const buildBundle = async () => {
    const bundle = await rollup({
        input: resolve(UTILS_PATH, 'src/index.ts'),
        plugins: [rollupTypescript(), nodeResolve(), commonjs()],
        // 如果你觉得第三方依赖体积很大,也可以用 external 拆出来,让调用方提供对应依赖,此时要配合 globals 一起用
        // external: Object.keys(pkgJson.dependencies),
    })

    // const globals = {
    //     dayjs: "dayjs",
    // }

    await Promise.all([
        bundle.write({
            name: 'VpUtils',
            format: 'umd',
            file: resolve(UTILS_PATH, 'dist/index.js'),
            sourcemap: true,
            // globals
        }),
        bundle.write({
            name: 'VpUtils',
            format: 'iife',
            file: resolve(UTILS_PATH, 'dist/index.min.js'),
            sourcemap: false,
            plugins: [terser()],
            // globals,
        })
    ])
}

如上面代码中注释所述,你可以根据实际情况选择是否将 dayjs 等依赖打进 bundle。

如果使用了 external,最好通过文档告知用户应该预先引入哪些依赖,降低用户的心智负担。

结语

本文主要介绍了函数库的构建过程中的一些优化方案和注意事项,希望对读者们有所帮助。如果您对我的专栏感兴趣,欢迎您订阅关注本专栏[6],接下来可以一同探讨和交流组件库开发过程中遇到的问题。

参考资料

[1]

vue-pro-components c7 分支: https://github.com/cumt-robin/vue-pro-components/tree/c7

[2]

基于Vite+AntDesignVue打造业务组件库: https://juejin.cn/column/7140103979697963045

[3]

@rollup/plugin-node-resolve: https://www.npmjs.com/package/@rollup/plugin-node-resolve

[4]

给 dayjs 提了一个PR: https://github.com/iamkun/dayjs/pull/2002

[5]

@rollup/plugin-commonjs: https://www.npmjs.com/package/@rollup/plugin-commonjs

[6]

订阅关注本专栏: https://juejin.cn/column/7140103979697963045

END

7a87a78851bd16b0fdbbc96a154e5c00.png

如果觉得这篇文章还不错

点击下面卡片关注我

来个【分享、点赞、在看】三连支持一下吧15739e3151a9e83b51ae20ff50a33b28.png

   “分享、点赞、在看” 支持一波 8625aa947a558e0c3f8fea34dcb1fe0f.png 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/372277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【从零开始制作 bt 下载器】一、了解 torrent 文件

【从零开始制作 bt 下载器】一、了解 torrent 文件写作背景了解 torrent 文件认识 bencodepython 解析 torrent 文件解密 torrent 文件结尾写作背景 最先开始是朋友向我诉说使用某雷下载结果显示因为版权无法下载,找其他的下载器有次数限制,于是来询问我…

Redis学习笔记(二)Redis基础(基于5.0.5版本)

一、Redis定位与特性 Redis是一个速度非常快的非关系数据库(non-relational database),用 Key-Value 的形式来存储数据。数据主要存储在内存中,所以Redis的速度非常快,另外Redis也可以将内存中的数据持久化到硬盘上。…

[SSD综述 1.3] SSD及固态存储技术半个世纪发展史

在我们今天看来,SSD已不再是个新鲜事物。这多亏了存储行业的前辈们却摸爬滚打了将近半个世纪,才有了SSD的繁荣, 可惜很多前辈都没有机会看到。所有重大的技术革新都是这样,需要长期的技术积累,一代一代的工程师们默默的…

华为OD机试用Python实现 -【狼羊过河 or 羊、狼、农夫过河】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲狼羊过河 or 羊、狼、农夫过河题目描述输入描述输出描述说明示例一输入输出说明Python 代码实现代码实现思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址…

学到贫血之-贫血模型和充血模型

学习自:设计模式之美 1 基于贫血模型的传统开发模式 // ControllerVO(View Object) public class UserController {private UserService userService; //通过构造函数或者IOC框架注入public UserVo getUserById(Long userId) {UserBo userBo userService.getUser…

【华为OD机试模拟题】用 C++ 实现 - 相对开音节(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 获得完美走位(2023.Q1) 文章目录 最近更新的博客使用说明相对开音节题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高…

【云原生】搭建k8s高可用集群—20230225

文章目录多master(高可用)介绍高可用集群使用技术介绍搭建高可用k8s集群步骤1. 准备环境-系统初始化2. 在所有master节点上部署keepalived3.1 安装相关包3.2 配置master节点3.3 部署haproxy错误解决3. 所有节点安装Docker/kubeadm/kubelet4. 部署Kuberne…

《痞子衡嵌入式半月刊》 第 72 期

痞子衡嵌入式半月刊: 第 72 期 这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期。 本期刊是开源项目(GitHub: JayHeng/pzh-mcu-bi-weekly),欢迎提交 issue&#xff0c…

【华为OD机试模拟题】用 C++ 实现 - 求解连续数列+和最大子矩阵(2023.Q1 双倍快乐)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 获得完美走位(2023.Q1) 文章目录 最近更新的博客使用说明求解连续数列题目输入输出描述示例一输入输出Code和最大子矩阵题目输入输出示例一输入输出说明

CMU15-445 Project.0总结

在线测试 本地测试 Project #0 - C Primer 以下是Project #0的网址,2022FALL的Project #0本质上是实现一棵字典树,关于字典树的相关内容可以参考C实现字典树。 在本题中,为了存储对应着字符串的任意类型值,题目设计了一个Tri…

CV——day79 读论文:基于小目标检测的扩展特征金字塔网络

Extended Feature Pyramid Network for Small Object DetectionI. INTRODUCTIONII. RELATED WORKA. 深层物体探测器B. 跨尺度特征C. 目标检测中的超分辨率III. OUR APPROACHA. 扩展特征金字塔网络B. 特征纹理传输C. 交叉分辨蒸馏IV. EXPERIMENTSA. Experimental Settings1&…

SEATA是什么?它的四种分布式事务模式

一、SEATA是什么? Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 在继续学习使用SEATA之前,对s…

电子科技大学数据库与软件工程实验五

适用于网工和物联网专业 期末考试会考 目录 一、实验目的 二、实验内容 三、实验软件 四、实验步骤及数据记录 1. 权限管理 2. 数据库备份 3. 生成 AWR 报告 五、实验结论及思考题 六、总结及心得体会 七、对本实验过程及方法、手段的改进建议 一、实验目的 1、掌握…

工作九年的机器人讲师收入如何

如下都是个人情况,供各位朋友参考,仅为个人情况,只代表我个人。为何写这样一篇博客:看了最近的热点我在一所普普通通的不知名高校工作了九年。如果问及我存款……我实在是非常非常惭愧的。真实数值如下(组合贷&#xf…

【数据库】第八章 数据库编程

第八章 数据库编程 8.1 嵌入式SQL&#xff08;C语言版&#xff09; 被嵌入的语言&#xff08;java ,C)等被称为宿主语言&#xff0c;简称主语言 当主语言 为 C 语言的时候 ​ 语法格式为 EXEC SQL <SQL语句>当主语言为java的时候 格式为 #SQL {<SQL语句>}以下讲解…

MATLAB | 如何解决实验数据散点图重叠问题(overlap)

本期部分实验效果&#xff1a; 这期讲一下如果数据重合严重该咋办(overlap)&#xff0c;事先说明&#xff0c;本文中的绘图均使用一个几行的简单小代码进行了修饰&#xff1a; function defualtAxes axgca;hold on;box on ax.XGridon; ax.YGridon; ax.XMinorTickon; ax.YMinor…

Redis 之企业级解决方案

文章目录一、缓存预热二、缓存雪崩三、缓存击穿四、缓存穿透五、性能指标监控5.1 监控指标5.2 监控方式&#x1f34c;benchmark&#x1f34c;monitor&#x1f34c;slowlog提示&#xff1a;以下是本篇文章正文内容&#xff0c;Redis系列学习将会持续更新 一、缓存预热 1.1 现象…

云服务器产生背景与历史演进

云服务器产生背景  业务量爆发和衰退周期考验后端服务器性能匹配 在传统IT架构中&#xff0c;需要提前预估业务爆发时间和业务量&#xff0c;提前部署服务器以支撑业务。但是&#xff0c;往往预估与实际结果是有差距的&#xff0c;预估量过大会造成服务器采购成本高&#x…

阿里巴巴内网 Java 面试 2000 题解析(2023 最新版)

前言 这份面试清单是今年 1 月份之后开始收集的&#xff0c;一方面是给公司招聘用&#xff0c;另一方面是想用它来挖掘在 Java 技术栈中&#xff0c;还有一些知识点是我还在探索的&#xff0c;我想找到这些技术盲点&#xff0c;然后修复它&#xff0c;以此来提高自己的技术水平…

2016年chatGPT之父Altman与马斯克的深度对话(值得一看)

2016年9月&#xff0c;现今OpenAI CEO&#xff0c;ChatGPT之父&#xff0c;时任创投公司Y Combinator的总裁Sam Altman在特斯拉加州弗里蒙特工厂采访了埃隆马斯克。马斯克阐述了创建OpenAI的初衷&#xff0c;以及就他而言&#xff0c;对于未来最为重要的五件事。这是OpenAI的两…