背景0
本地机器 MacBook Pro 芯片 Apple M1 Pro 线上机器 openjdk_java8_nodejs (不知道这个实际配置)
背景1
构建提速是 umi@4 的一个较大的特性,项目中使用 umi@4 构建,只需要12秒左右。
➜ nocode-1 git:(master) pnpm build:app
> @ build:app /Users/congxiaochen/Documents/nocode-1
> cross-env APP_ROOT=packages/app umi build
info - Using Main Path Plugin
info - Using Request Plugin
info - Using Dva Plugin
info - Using ClassNames Plugin
info - Using UseModel Plugin
info - Using Antd Plugin
event - Compiled successfully in 12091 ms (4646 modules)
info - Memory Usage: 501.55 MB (RSS: 1277.97 MB)
event - Build index.html
如果使用传统的交付方式,我本地打包之后,通过 ftp 文件上传部署,可能整个上线流程只要几分钟时间。但是传统的交付部署模式,其实存在着很多的不可信任的人为操作问题,比如说包同步之后,线上没有同步,你很难断言说是人员操作失误,还是静态资源容器缓存。
背景2
项目中使用 menorepo(多包管理),构建项目之前需要先构建本地的子包。 使用的是 yarn 和 lerna 的管理方式。
使用 build: lerna run build
构建 10 个子包,每个子包构建时长为 10s-43s 不等。
背景3
所以整个 zcm 构建流程就是 容器启动(24s)、拉取代码(11s)、执行依赖安装(82s)、执行子包编译(231s)、执行子包编译(231s)、执行项目编译(52.71s)、云构建流程,将要回写数据到zcm中(5s)、构建镜像(16s)、推送上线(11s)
#!/bin/bash
# 安装 yarn
# 避免重复执行
if [[ ! -h ~/.yarn/berry ]]; then
curl -SsLf http://gitlab.iwhalecloud.com/rlc/cicd/raw/master/scripts/react/install_yarn.sh | bash -
fi
log_info "Start installing packages..."
if [[ -f ".yarnrc.yml" ]]; then
yarn install --immutable
else
yarn install --frozen-lockfile
fi
log_info "Start building..."
yarn build
log_info "Start building app..."
# 执行产物编译
yarn build:app
解决思路
其实我一开始看到的是这个时间线
切换 npm 源
主要的时间是花在构建环节,和本地机器差异较大,所以我很想当然的以为是依赖安装问题,所以在本地切换了公司内部的源,并将锁定了源的 yarn.lock
上传到仓库中。这个对安装时间有提升,但是不明显。看了下构建日志,发现zcm 会自动把 npm 源设置到公司源。
继续跟踪日志,我发现子包编译过程执行了两次,并且在子包编译结束时程序没有很良好的推出编译进入下一个环节。本地构建时也会偶发 lerna run build
推出较慢的问题,(没有深入去跟踪问题)。
使用 pnpm
所以使用 pnpm 替换 lerna。
- "build": "lerna run build",
+ "build": "pnpm -r --filter ./packages run build",
然后修改构建脚本
#!/bin/bash
# 安装 pnpm
# 避免重复执行
if [[ ! -h ~/.pnpm-state/pnpm-state.json ]]; then
curl -fsSL https://get.pnpm.io/install.sh | PNPM_VERSION=7.0.0-beta.2 sh -
fi
log_info "Start installing packages..."
pnpm i
log_info "Start building..."
pnpm build
log_info "Start building app..."
# 执行产物编译
pnpm build:app
发现整个过程快了5分钟左右,有两方面的原因,一个是 pnpm 安装依赖更快,另一个是每一个构建流程完成之后都很顺利的进入下一个环节。
最终得到了一个 6分25秒 构建的时间线
使用 turbo
既然依赖安装部分尽力了,那构建环节是不是还可以更快呢?刚好最近在 umi 项目开发中引入了 turbo 编译速度有很大的提升,也是抱着尝试的心态,将 turbo 引入到项目中(从 umi 抄作业)。
1、只需要安装三个包
"esno": "^0.14.1",
"ts-node": "^10.7.0",
"turbo": "^1.1.9",
2、加一个配置文件 turbo.json
{
"$schema": "https://turborepo.org/schema.json",
"baseBranch": "origin/master",
"pipeline": {
"build": {
"dependsOn": ["^build"]
}
},
"globalDependencies": []
}
3、一个构建脚本 scripts/turbo.ts
import * as logger from '@umijs/utils/dist/logger';
import spawn from '@umijs/utils/compiled/cross-spawn';
import yArgs from '@umijs/utils/compiled/yargs-parser';
import { join } from 'path';
(async () => {
const args = yArgs(process.argv.slice(2));
const scope = args.scope || '!@example/*';
const extra = (args._ || []).join(' ');
await turbo({
cmd: args.cmd,
scope,
extra,
cache: args.cache,
parallel: args.parallel,
});
})();
/**
* Why not use zx ?
* - `zx` not support color stdin on subprocess
* - see https://github.com/google/zx/blob/main/docs/known-issues.md#colors-in-subprocess
* https://github.com/google/zx/issues/212
*/
async function cmd(command: string) {
const result = spawn.sync(command, {
stdio: 'inherit',
shell: true,
cwd: join(__dirname, '../'),
});
if (result.status !== 0) {
// sub package command don't stop when execute fail.
// display exit
logger.error(`Execute command error (${command})`);
process.exit(1);
}
return result;
}
async function turbo(opts: {
scope: string;
cmd: string;
extra?: string;
cache?: boolean;
parallel?: boolean;
}) {
const extraCmd = opts.extra ? `-- -- ${opts.extra}` : '';
const cacheCmd = opts.cache === false ? '--no-cache --force' : '';
const parallelCmd = opts.parallel ? '--parallel' : '';
const options = [
opts.cmd,
`--cache-dir=".turbo"`,
`--scope="${opts.scope}"`,
`--no-deps`,
`--include-dependencies`,
cacheCmd,
parallelCmd,
extraCmd,
]
.filter(Boolean)
.join(' ');
return cmd(`turbo run ${options}`);
}
4、更换一下执行命令
"build": "esno scripts/turbo.ts --cmd build",
最终得到一个我非常满意的 4分30秒 的时间线
并且二次构建是有缓存的,如
总结
1、使用yarn2替换yarn1
yarn2 会比 yarn1 要快,但是 yarn2 有些框架不支持,有些第三方包会存在异常。所以我本人不太喜欢 yarn2
2、使用 pnpm 替换 yarn + lerna 的组合
3、使用 turbo 执行构建脚本
备注
其实主要信息都在总结,但是每个项目用到的框架和构建脚本都不一样,提供一下我的完整思路,希望能够引导你对你当前所在项目的构建提升作出优化。