使用 pnpm 和 turbo 构建提速从12分到4分钟

4,428 阅读4分钟

背景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

解决思路

其实我一开始看到的是这个时间线

11111111.png

切换 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秒 构建的时间线

2222222222222222.png

使用 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秒 的时间线

33333.png

并且二次构建是有缓存的,如

444444.png

总结

1、使用yarn2替换yarn1

yarn2 会比 yarn1 要快,但是 yarn2 有些框架不支持,有些第三方包会存在异常。所以我本人不太喜欢 yarn2

2、使用 pnpm 替换 yarn + lerna 的组合

3、使用 turbo 执行构建脚本

备注

其实主要信息都在总结,但是每个项目用到的框架和构建脚本都不一样,提供一下我的完整思路,希望能够引导你对你当前所在项目的构建提升作出优化。