gitlab-ci编译构建打包速度优化

背景

随着业务越多,项目越大,项目编译构建耗时也越长,每次修改部署等待测试验证的时间也越久,这已经成为了开发过程中的一大痛点,急需解决优化。

目前对比了两个项目,由Vue框架主导的项目,编译打包文件耗时近1分钟,在gitlab-ci上整个构建打包发布流程耗时5分钟左右,甚至更久;由Taro框架主导的项目,编译打包文件耗时近1分钟,在gitlab-ci上整个构建打包发布流程耗时长达11分钟,甚至更久。

Vue框架主导的项目:


Vue-优化前gitlab-ci构建耗时

Taro框架主导的项目:


Taro-优化前gitlab-ci构建耗时

针对整个构建打包流程时间过久的问题,尝试进行分析及优化编译打包速度,主要讲述三个部分:

  1. 分析gitlab-ci构建打包流程耗时
  2. 合理使用gitlab-ci缓存,提升缓存优势
  3. 优化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:


gitlab-ci fetching changes

Checking cache,extracted cache:


gitlab-ci提取cache

Creating cache:


gitlab-ci存储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 策略,该策略可以更改缓存的上传和下载行为。

ci-cache存储路径

所以配置文件中设置了缓存node_modules目录下的所有文件,而我们知道node_modules下的文件是数以几十万计的(上图可以看到node_modules目录下文件数是29万多,未压缩前4GB多,压缩后1.6GB左右),所以导致每次存储文件以及提取缓存文件时都耗时居多,本来缓存是为了提升打包速度的,但这里却是背道而驰,时间浪费在存储之间。

为了提升构建速度,我们缓存node_modules给注释掉。

.default:
#   cache:
#     paths: #配置缓存目录,每次打包不删除node_modules,提升打包速度
#       - node_modules/

再次构建的耗时如下图,Taro框架项目构建速度由原来的11min53s->2min16s,构建速度明显提升了80%多。


Taro项目ci-不缓存cache后构建速度

去除node_modules的缓存后,在第3个步骤安装依赖包时耗时明显增加,这是因为需要重新根据项目的package-lock.json去安装依赖文件,重新安装依赖包比原先消耗更多下载的时间,如下图对比,但速度还可接受,因为package-lock.json本就提供了安装依赖的npm包依赖详情和npm包链接,起到安装提速的作用。

缓存node_modules缓存时执行npm install的时间耗时:


缓存node_modules缓存时执行npm install

不缓存node_modules缓存时执行npm install的时间耗时(实际看当时npm源下载速度,测试过大概在30s-80s之间):


不缓存node_modules缓存时执行npm install

以上是Taro项目的数据,Vue项目中去掉gitlab-ci的node_modules缓存后,npm install的安装速度由原来的10s->20s~30s,但是构建速度明显提升5min->3min左右,如下图:


Vue项目ci-不缓存cache后构建速度

但是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)
 }
}

输出效果:


Vue构建优化前

多核

一般来说,我们可以通过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-loader开启多核

缓存

通过命令vue inspect > output.js可以看到输出的webapck配置中,vue已经帮我们配置好了许多优化配置,像vue-loader、babel-loader都设置了cache-loader,terser-webpack-plugin 也是默认开启了缓存和多核的。

babel-loader设置cache-loader

TerserWebpackPlugin配置

缓存默认是保存在 node_moduls/.cache 中,如下图。因此在使用执行编译打包命令时,需要注意当前的打包环境是否能够将缓存保留下来,否则缓存配置无法带来速度优化效果。因此,gitlab-ci中,需要对 node_modules/.cache 目录做缓存处理。我这里定义的cache:key是按照git提交的分支名做唯一的识别键(默认的标识是default),同个项目中,使用相同缓存键的所有jobs使用相同的缓存(不同的项目不能共享cache)。

node_modules/.cache
.default:
  cache:
    key: "$CI_COMMIT_REF_SLUG"
    paths:
      - node_modules/.cache
ci-cache名称

设置了node_modules/.cache后,每次构建就会先从本地下载缓存cache,构建完成后再存储更新cache,因此这里会消耗gitlab-ci的一些构建时间,Vue项目缓存node_modules/.cache后webapck构建时间及gitlab-ci构建时间如下图,build构建时间为30多s,gitlab-ci的构建总时间为1min多,相比于优化前效率提升80%。:

vue-缓存.cache后

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左右。

Taro项目-缓存后的构建时间

总结

经过合理缓存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

你可能感兴趣的:(gitlab-ci编译构建打包速度优化)