前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >函数库Rollup构建优化

函数库Rollup构建优化

作者头像
程序员白彬
发布2023-03-02 14:54:45
1.2K0
发布2023-03-02 14:54:45
举报
文章被收录于专栏:前端全栈分享

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

前言

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

terser 压缩

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

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

代码语言:javascript
复制
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 的压缩代码了。

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

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

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

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

观察上图可以发现,当我们引用其中一个模块的完整路径时,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 目录抄过来即可)。

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

我们回忆一下整个流程,

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

代码语言:javascript
复制
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 节点。

代码语言:javascript
复制
export const startBuildUtils = series(
    parallel(buildModules, buildBundle, buildTypes),
    copyDts
)

效果如下:

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

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

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

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

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

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

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

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

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

我们将buildModules改一改,

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

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

代码语言:javascript
复制
'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 的默认行为。

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

首先安装一下依赖,

代码语言:javascript
复制
yarn add -DW @rollup/plugin-node-resolve

然后在插件中引用它,

代码语言:javascript
复制
const bundle = await rollup({
    input,
    plugins: [rollupTypescript(), nodeResolve()],
    external: Object.keys(pkgJson.dependencies),
})

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

关键信息是:

代码语言:javascript
复制
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],那我们直接用上它就好了。

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 bin不懂二进制 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • terser 压缩
  • 按需使用子模块时提供类型支持
  • 第三方依赖解析和打包问题
  • 结语
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档