前端构建怎么要四个小时啊?!
怎么可能?!对啊,笔者一开始想的也是怎么可能。
突然有一天DevOps老哥找到我说,某个分支的构建速度特别慢,四个小时都没法完成,直接导致构建任务失败。听到这个消息的时候心里想的就是:怎么可能?再怎么慢也不可能四个小时都没有完成吧。然后DevOps老哥就把Jenkins的构建日志拍在了我的脸上。明明白白若干个红的Failure然后时长为四个小时的任务。
而且老哥和我说就某个特定的分支有问题。那我内心的第一反应就是这分支是不是进了什么人神共愤的代码。这就得研究一下为啥了。定位问题很简单,这件事情是在某一个时间节点之后必现的,那么只要分析一下这个时间点前半天到一天的commit就可以了。然后笔者在自己的电脑上试了试,其中一个模块一般只要40秒就能完成构建的,现在居然要花整整十分钟,过于离谱。
分析了一波commit后发现,这段时间除了业务代码的更新之外只有我们使用的一个组件库b发生了更新。考虑到业务代码很难对构建时间有这种决定性的影响,这个时候笔者内心已经觉得问题大概率出在组件库了。
凡是诡异bug,必有弱智错误。凡是棘手难题,必有猥琐解法。
一般打包特别久的原因无非就是两个,一个是因为bundle太大,还有一个就是可能存在循环依赖。
我们的组件库的构建工具用的是vite,vite的构建一节是基于rollup的,那么就可以使用rollup-plugin-visualizer来分析JS Bundle大小与依赖:
1 | pnpm install rollup-plugin-visualizer -D -W |
在vite.config.ts
中作如下配置:
1 | export default defineConfig({ |
然后运行pnpm build
,构建完成后就会打开一个网页,可视化地展示JS Bundle中每个文件的大小。
这一看直接吓一跳,整个构建产物的文件大小居然来到了35.5MB!35.5MB啊,这可太离谱了。同时图片里面有个干净的大块吸引了我的注意,其中一个文件居然占了其中的35MB。那这个文件肯定全责了。
直接打开并分析了下commit记录后一句平平无奇的import
语句吸引了我的注意:
1 | import { a } from 'b' |
看上去没什么问题对吧,但是我们这个组件库的名字就是叫”b”,所以达成的效果就是在它自己里面依赖自己,就造成了循环引用,就造成了一个35.5MB大小的怪物JS Bundle。把引入路径改成相对路径之后,问题解决。我们再来看看claude-3.5-sonnet
的分析:
您遇到的这个问题很有趣,这是组件库开发中常见的一个陷阱。让我来解释一下可能的原因和解决方法:
原因分析:
当您使用 import {a} from “b” 这种方式导入时,Vite 会尝试解析 “b” 这个包。如果您的项目配置不当,它可能会回到项目根目录,然后再次进入您的源代码,造成循环依赖。
这种情况下,Vite 可能会将整个项目打包到这个组件中,导致bundle体积膨胀。
那么有没有办法在b这个包CICD的时候就避免这个问题呢?答案是size-limit。size-limit是一个用于检测npm包大小和限制大小的工具:
根据文档,我们的组件库b符合”Big Library”的标准
Big Libraries
JS libraries > 10 kB in size.
This preset includes headless Chrome, and will measure your lib’s execution time. You likely don’t need this overhead for a small 2 kB lib, but for larger ones the execution time is a more accurate and understandable metric that the size in bytes. Libraries like React are good examples for this preset.
我们做如下配置:
先安装size-limit:pnpm install size-limit @size-limit/preset-big-lib -D
给组件库b的package.json中配置,配置size
命令来运行size-limit,配置产物路径为dist/b.js
和dist/b.umd.cjs
告诉size-limit要检测的产物路径,limit为产物大小限制:
1 | { |
执行一下:
1 | dist/b.js |
可以看到,b的打包产物大小超过了300KB,所以size-limit会报错,就可以提前在子模块的CICD中发现问题,防止问题上升到项目级别。