背景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 执行构建脚本
备注
其实主要信息都在总结,但是每个项目用到的框架和构建脚本都不一样,提供一下我的完整思路,希望能够引导你对你当前所在项目的构建提升作出优化。