通过分析build日志,我们发现慢的地方三处
1. yarn install安装依赖慢
2. 缓存node_modules到gitlab和从gitlab拉取缓存的node_modules慢
3. yarn run build慢
解决问题的思路:
如果缓存node_modules会不会变快呢?我们每次执行yarn install时会从远程或从本地yarn缓存拉取依赖解压到node_modules,这个过程很慢很耗时,我们第一想到的是,如果我把node_modules缓存到gitlab每次执行CI时拉取这个node_modules,这样就会快一些,由于我们的runner节点有多个,这个node_modules要么缓存在集中缓存,要么每个节点都缓存一份,集中式缓存需要依赖像MinIO这类存储,续要单独部署,而redis我们是现成的可以直接用。当我们把node_modules缓存后发现yarn install过程虽然速度确实快了一些但还是有些慢,我们这时想到,如果我的yarn install过程不执行不就快了吗?我们通过redis记录yarn.lock的MD5值,如果当前yarn.lock的MD5值和redis存储的上次某个runner上该yarn.lock的MD5做比较,如果相等则不执行yarn install,此时直接执行的是编译步骤和cache步骤,这样确实发现缓存存在且yarn.lock文件没有变化时很快,基本就等于yarn run build 的时间,大部分时间yarn.lock基本不会变化,所以该方案凑合能解决问题。
该方案代码实例如下:
build:
stage: build
only:
- dev
- test
- master
tags:
- nodejs
before_script:
- |
REDIS_KEY=${CI_RUNNER_ID}:${CI_PROJECT_NAME}:$CI_BUILD_REF_NAME:yarn.lock
FILE_MD5=`md5sum yarn.lock|cut -d ' ' -f1`
REDIS_MD5=`redis-cli -h 172.16.x.x -p 6379 -a tangwan get $REDIS_KEY`
echo "redisKey:$REDIS_KEY ;fileMD5:$FILE_MD5 ;redisMD5:$REDIS_MD5"
script:
- |
if [[ "$REDIS_MD5"x != "$FILE_MD5"x ]]; then
echo "依赖安装开始时间:$(date +'%Y-%m-%d %H:%M:%S')"
yarn config set ignore-engines true
yarn config set cache-folder ~/.yarn
yarn install --prefer-offline --frozen-lockfile --mutex network --registry=https://registry.npmmirror.com
echo "依赖安装结束时间:$(date +'%Y-%m-%d %H:%M:%S')"
fi
export NODE_OPTIONS=--max_old_space_size=8192
echo "打包开始时间:$(date +'%Y-%m-%d %H:%M:%S')"
yarn run build
echo "打包结束时间:$(date +'%Y-%m-%d %H:%M:%S')"
redis-cli -h 172.16.x.x -p 6379 -a tangwan set $REDIS_KEY $FILE_MD5
artifacts:
paths:
- dist/
after_script:
- rm -rf node_modules/.cache
cache:
key: $CI_PROJECT_NAME_$CI_BUILD_REF_NAME
policy: pull-push
paths:
- node_modules/
- src/.umi
- src/.umi-production
上面方案存在的问题:
yarn.lock对于依赖自己的node_modues这种需要频繁修改版本的场景不适用,绕不开yarn install步骤cache node_modules和pull cache node_modules时非常耗时,对于大一点的项目,node_modules几个G这种情况尤其明显。
继续分析,有没有办法不删除这个node_modules?当我们查看gitlab pipeline日志时发现,gitlab默认每次运行时都会先清理工作空间,删除未受版本控制的目录和文件,保持工作目录和代码库一致。而删除node_modules这一步其实也会消耗一定时间,而且我们确实不需要每次清理node_modules,使用npm ci和yarn install就会保持当前node_modules和package.json和yarn.lock文件声明的依赖一致,我们本地开发时也不见得每次都把node_modules删除后再build。所以我也不打算删除这个node_modules了。
最终的方案:
# gitlab执行git clean时的参数
variables:
GIT_CLEAN_FLAGS: -ffdx -e node_modules/
stages:
- build
build:
stage: build
only:
- dev
- test
- master
tags:
- nodejs
script:
- |
ls -al
echo "依赖安装开始时间:$(date +'%Y-%m-%d %H:%M:%S')"
yarn config set ignore-engines true
yarn config set cache-folder ~/.yarn
yarn install --prefer-offline --frozen-lockfile --mutex network --registry=https://registry.npmmirror.com
echo "依赖安装结束时间:$(date +'%Y-%m-%d %H:%M:%S')"
export NODE_OPTIONS=--max_old_space_size=8192
echo "打包开始时间:$(date +'%Y-%m-%d %H:%M:%S')"
yarn run build
echo "打包结束时间:$(date +'%Y-%m-%d %H:%M:%S')"
artifacts:
paths:
- dist/
果然,使用方式二速度就提上来了,yarn install在没修改依赖的情况下就只需要几秒,修改了依赖也只安装变更的依赖,也是秒级别完成,此方案也避免了node_modules缓存和拉取缓存的过程(压缩打包和解压)耗时问题,只剩下yarn run build耗时了,至此web项目编译慢的问题算是解决了。
参考官方文档: