背景
随着业务越多,项目越大,项目编译构建耗时也越长,每次修改部署等待测试验证的时间也越久,这已经成为了开发过程中的一大痛点,急需解决优化。
目前对比了两个项目,由Vue框架主导的项目,编译打包文件耗时近1分钟,在gitlab-ci上整个构建打包发布流程耗时5分钟左右,甚至更久;由Taro框架主导的项目,编译打包文件耗时近1分钟,在gitlab-ci上整个构建打包发布流程耗时长达11分钟,甚至更久。
Vue框架主导的项目:
Taro框架主导的项目:
针对整个构建打包流程时间过久的问题,尝试进行分析及优化编译打包速度,主要讲述三个部分:
- 分析gitlab-ci构建打包流程耗时
- 合理使用gitlab-ci缓存,提升缓存优势
- 优化webpack配置,将编译打包速度提升80%
分析gitlab-ci构建打包流程耗时
以Taro框架项目进行分析,在gitlab-ci中,每次git push提交都会触发一次pipeline,每个pipeline定义了jobs,所有jobs定义在.gitlab-ci.yml
中,ci通过读取.gitlab-ci.yml
的配置进行构建流程,从配置文件看大致分为三步:
步骤 | 说明 |
---|---|
1. npm install | 安装npm依赖包 |
2. npm run build | 执行构建打包命令 |
3. upload-file | 上传打包好的压缩包到发布平台发布 |
但实际上,我们观察gitlan-ci的pipeline构建过程控制台,可以看到,在上述三个步骤前后还有几个步骤。
Fetching changes:
Checking cache,extracted cache:
Creating cache:
分析这七个步骤的耗时(耗时为估算的平均耗时,在每次构建中各步骤耗时可能会因上传或下载速度等原因有所差异,所以每次打包的总耗时也不是一成不变的):
步骤 | 说明 | 耗时 |
---|---|---|
1. Fetching changes | 初始化打包环境,获取差异化文件,删除git中除项目本身文件外上一次打包创建的dist,node_modules,zip包等文件,拉取最新git仓库代码 | 20s |
2. Checking cache,extracted cache | 查找ci配置中的缓存文件,读取并抽离缓存,以便后续步骤使用 | 3min |
3. npm install | 安装npm依赖包 | 14.5s |
4. npm run build | 执行构建打包命令 | 55s |
5. upload-file | 上传打包好的压缩包到发布平台发布 | 5s |
6. Creating cache | 创建缓存上传到共享缓存服务器中,如果没有提供服务器链接则保留在本地缓存中 | 6min |
可以看出2、4、6这几个步骤耗时比较多,那么可以从这几个步骤做突破口。
合理使用gitlab-ci缓存
我们已经分析了耗时的主要因素,那就是gitlab-ci缓存的存和取(第2、6点),造成这么大的耗时是因为.gitlab-ci.yml
的配置中设置了node_modules
的缓存(caching-in-gitlab-cicd)。
.default:
cache:
paths: #配置缓存目录,每次打包不删除node_modules,提升打包速度
- node_modules/
在 GitLab CI/CD 中,根据不同的 runner executor,缓存存储在不同的路径下。普通的 cache 机制,其 cache 均以cache.zip的形式存储在机器本地。默认情况下,如果有 cache 的配置,那么每个 job 会在开始执行前将对应路径的文件下载下来,并在任务结束前重新上传,不管文件是否有变化都会如此操作。这个默认的配置是 cache:policy 中的 pull-push 策略,该策略可以更改缓存的上传和下载行为。
所以配置文件中设置了缓存node_modules目录下的所有文件,而我们知道node_modules下的文件是数以几十万计的(上图可以看到node_modules目录下文件数是29万多,未压缩前4GB多,压缩后1.6GB左右),所以导致每次存储文件以及提取缓存文件时都耗时居多,本来缓存是为了提升打包速度的,但这里却是背道而驰,时间浪费在存储之间。
为了提升构建速度,我们缓存node_modules给注释掉。
.default:
# cache:
# paths: #配置缓存目录,每次打包不删除node_modules,提升打包速度
# - node_modules/
再次构建的耗时如下图,Taro框架项目构建速度由原来的11min53s->2min16s,构建速度明显提升了80%多。
去除node_modules的缓存后,在第3个步骤安装依赖包时耗时明显增加,这是因为需要重新根据项目的package-lock.json
去安装依赖文件,重新安装依赖包比原先消耗更多下载的时间,如下图对比,但速度还可接受,因为package-lock.json
本就提供了安装依赖的npm包依赖详情和npm包链接,起到安装提速的作用。
缓存node_modules缓存时执行npm install的时间耗时:
不缓存node_modules缓存时执行npm install的时间耗时(实际看当时npm源下载速度,测试过大概在30s-80s之间):
以上是Taro项目的数据,Vue项目中去掉gitlab-ci的node_modules缓存后,npm install的安装速度由原来的10s->20s~30s,但是构建速度明显提升5min->3min左右,如下图:
但是gitlab-ci的cache并不是无用武之地,它可以用来缓存一些编译内容,可以继续往后看。
提高webapck打包速度
去除gitlab-ci的缓存后,构建速度大幅度提升,我们再分析第4点,该步骤是通过构建工具如webpack等进行处理后输出最终代码,那么可以从webapck构建上优化,优化的策略可以选择多核与缓存
。
Vue框架项目优化
全局安装了npm install -g @vue/cli
后,可以通过vue inspect > output.js
输出webapck的所有配置。
为了分析build构建时间,安装speed-measure-webpack-plugin
插件可以在build的时候看到webpack的loader和plugin所用的时间,配置如下:
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin')
// vue.config.js
module.exports = {
chainWebpack: config => {
config.plugin('speed')
.use(SpeedMeasureWebpackPlugin)
}
}
输出效果:
多核
一般来说,我们可以通过happack-plugin
或者thread-loader
开启多线程打包。vue-cli使用的是thread-loader,vue.config.js的可配置parallel属性,其含义是:是否为 Babel 或 TypeScript 使用 thread-loader,默认值为cpu的内核数。当然自己也可以自定义配置,如下图为vue-loader开启多线程处理。
config.module.rule('vue')
.use('thread-loader')
.loader('thread-loader')
.before('vue-loader')
vue-loader开启多核后,构建速度提升了20%左右:
缓存
通过命令vue inspect > output.js
可以看到输出的webapck配置中,vue已经帮我们配置好了许多优化配置,像vue-loader、babel-loader都设置了cache-loader,terser-webpack-plugin 也是默认开启了缓存和多核的。
缓存默认是保存在 node_moduls/.cache
中,如下图。因此在使用执行编译打包命令时,需要注意当前的打包环境是否能够将缓存保留下来,否则缓存配置无法带来速度优化效果。因此,gitlab-ci中,需要对 node_modules/.cache
目录做缓存处理。我这里定义的cache:key
是按照git提交的分支名做唯一的识别键(默认的标识是default
),同个项目中,使用相同缓存键的所有jobs使用相同的缓存(不同的项目不能共享cache)。
.default:
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/.cache
设置了node_modules/.cache
后,每次构建就会先从本地下载缓存cache,构建完成后再存储更新cache,因此这里会消耗gitlab-ci的一些构建时间,Vue项目缓存node_modules/.cache
后webapck构建时间及gitlab-ci构建时间如下图,build构建时间为30多s,gitlab-ci的构建总时间为1min多,相比于优化前效率提升80%。:
Taro框架项目优化
Taro框架项目中官方有推荐的编译优化插件,只需要本地安装依赖并引入插件即可。其本质也是使用多核与缓存两个方向进行优化(详情)。
安装依赖
npm install --save-dev thread-loader cache-loader taro-plugin-compiler-optimization
使用插件
// config/index.js
config = {
// ...
plugins: ['taro-plugin-compiler-optimization']
}
同Vue项目的缓存处理,这里也需要设置gitlab-ci缓存node_modules/.cache
,这样打包才能加速:
.default:
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- node_modules/.cache
gitlab-ci开启缓存node_modules/.cache
和缓存、多核加速打包后,最后build构建时间为17s左右,gitlab-ci的构建总时间为2min左右。
总结
经过合理缓存gitlab-ci的cache及优化webpack打包配置,两个项目的构建速度优化前后对比如下表:
项目 | 优化前gitlab-ci构建速度 | 优化后gitlab-ci构建速度 | 效率提升 |
---|---|---|---|
Taro项目 | 11min+ | 2min左右 | 80%+ |
Vue项目 | 5min+ | 1min+ | 80%+ |
除了上述两个主要的优化方向,在其他方面,比如webpack的优化上还是有许多可优化的空间,其次,如果项目依赖包比较少,直接取缓存比直接安装所需时间更少的话,还是可以考虑缓存node_modules,或者寻求更优的缓存存取方式。
最后,更多优化想法也可多多讨论交流。
更多阅读
- ci-cache
- package-lock.json
- webpack-chain