Jenkins Vue 前端流水线构建异常排查(pnpm 版本、husky、构建脚本拦截、OOM)
记录时间:2026-07-16
环境:Jenkins(K8s 动态 Slave,devops 命名空间)/ 共享库pipelinePublicLibrary/ node 容器gzeport-node-pnpm:20|22/ 项目gzeport-track-ui-xc(Vue3 + TS + Vite7,4353 模块)
一、问题现象
新接入的 Vue 前端项目 gzeport-track-ui-xc 首次走 vueDevopsByk8s 流水线,📦 前端构建 (pnpm) 阶段连续失败 4 次,每次报错各不相同:
- pnpm 启动即崩:
Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: node:sqlite pnpm install尾部报git command not found,后续构建命令未执行pnpm install尾部报ERR_PNPM_IGNORED_BUILDS: Ignored build scripts: esbuild@0.25.5, vue-demi@0.14.10 ...vite build进行到rendering chunks...时输出Killed,exit code 137
同域其他 Vue 项目(如 gzeport-report-ui-xc)用相同流水线构建正常。
二、排查过程
2.1 问题 1:pnpm 与 Node 版本不匹配
对比正常项目与异常项目的构建日志头部:
# 正常项目(gzeport-report-ui-xc)
node: v20.19.5 pnpm: 10.22.0
# 异常项目(gzeport-track-ui-xc)
warn: This version of pnpm requires at least Node.js v22.13
warn: The current version of Node.js is v20.19.5
Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: node:sqlite
根因:项目 package.json 绑定了 packageManager: pnpm@11.8.0,容器内 pnpm 自举拉起 11.8.0,而该版本依赖 Node 22 才有的内置模块 node:sqlite。构建参数 nodeVersion 默认 20,撑不起 pnpm 11。
注意:其他项目未绑定 pnpm 版本,直接使用容器自带的 pnpm 10.22.0,与 Node 20 兼容,所以只有该项目触发。
处理:构建参数 nodeVersion 改选 22。
2.2 问题 2:husky postinstall 中断 pnpm install
nodeVersion=22 后,pnpm install 包全部安装成功,但尾部:
$ husky
git command not found
Jenkins sh 块默认 set -e,husky(prepare 脚本)非零退出导致整个 sh 块中断,pnpm run build:uat 根本没执行。根因:node 构建容器内没有 git 命令,husky 初始化 git hooks 失败。
处理:业务 Jenkinsfile 注入 HUSKY=0(流水线的 stageEnvPrepare 会执行 map.each { k,v -> env."${k}" = "${v}" },map 字段自动进环境变量):
map.put("HUSKY","0")
husky 官方支持该变量,CI 中输出 HUSKY=0 skip install 并以 0 退出。
2.3 问题 3:pnpm 10+ 拦截依赖构建脚本
husky 跳过后,pnpm install 尾部仍非零退出:
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: @parcel/watcher@2.5.1, es5-ext@0.10.64, esbuild@0.25.5, typeit@8.8.7, vue-demi@0.14.10
pnpm 10 起默认拦截依赖包的 postinstall 构建脚本(防供应链攻击),且以非零码退出。
尝试 1(无效):pnpm install --config.onlyBuiltDependencies='*' —— * 被当作字面包名而非通配符,反而覆盖了默认允许列表。
尝试 2(撤销):pnpm config set onlyBuiltDependencies[] esbuild 等逐包写入 —— 该改动位于公共共享库,等于把单个项目的依赖清单固化进公共流水线;onlyBuiltDependencies 是排他性白名单,会静默跳过其他项目依赖的构建脚本,产生难排查的运行时问题。
最终方案:使用 pnpm 10.4+ 官方 CI 开关,放行所有构建脚本(等价 pnpm 9 及以前的默认行为):
// vars/vueDevopsByk8s.groovy
pnpm install --registry ${params.registryUrl} --dangerously-allow-all-builds
注意:该标志名称中的 “dangerously” 针对开发者本机场景;CI 容器为一次性隔离环境,构建完即销毁,放行构建脚本是该开关的设计用途。node20 容器 pnpm 10.22.0 与 node22 容器 pnpm 11.8.0 均支持;node24 容器未验证(要求 pnpm ≥ 10.4)。
2.4 问题 4:vite build OOM(exit 137)
依赖安装走通后,构建阶段:
vite v7.1.3 building for uat...
✓ 4353 modules transformed.
rendering chunks...
Killed
[ELIFECYCLE] Command failed with exit code 137.
exit 137 = SIGKILL,容器内存超限被 K8s OOMKill。将 node 容器 limits.memory 从 4Gi 提到 6Gi 后仍然 OOM,说明加内存方向不对。
三、根因分析
检查项目 package.json 构建脚本:
"build:uat": "vue-tsc --noEmit & vite build --mode uat"
关键在单个 &(后台执行),不是 &&(条件与):
vue-tsc(全量类型检查,大项目吃 2~3G)被扔到后台,与vite build(打包,2~3G)并行执行,两进程内存叠加冲破容器 limit;&后无wait,vue-tsc 的退出码被丢弃——类型检查失败构建照样成功,即该脚本中的 vue-tsc 在 CI 里本来就无拦截作用,只消耗内存;- 脚手架(create-vue)默认模板即如此,多个项目同样写法,小项目内存没顶到 limit 所以未暴露。
结论:继续加内存是为一个无产出的进程买单;正确方向是 CI 构建跳过 vue-tsc。
四、解决方案
共享库构建命令改为 Groovy 层拼装(避免 shell 多层 if 嵌套),支持 SKIP_TYPE_CHECK 开关:
// vars/vueDevopsByk8s.groovy 构建 stage
def pm = params.packageManager
def modeSuffix = params.profiles ? ":${params.profiles}" : ""
def buildCmd = "${pm} run build${modeSuffix}"
// SKIP_TYPE_CHECK=true:绕过 npm script 直接调 vite,跳过无拦截作用的 vue-tsc
if (map.SKIP_TYPE_CHECK == "true") {
def modeOpt = params.profiles ? " --mode ${params.profiles}" : ""
buildCmd = "${pm} exec vite build${modeOpt}"
}
业务 Jenkinsfile 声明:
map.put("SKIP_TYPE_CHECK","true")
方案要点:
| 要点 | 说明 |
|---|---|
| 环境切换不受影响 | --mode 仍取 profiles 构建参数(uat / prod-xc),未写死 |
| 包管理器不受影响 | 命令前缀取 packageManager 参数(pnpm / npm) |
| 其他项目零影响 | 未注入 SKIP_TYPE_CHECK 的项目走原有 run build:xxx 逻辑 |
| 无安全隐患 | 布尔开关而非任意命令注入,不使用 eval |
五、验证
# 构建参数:nodeVersion=22, profiles=uat, isDeploy=false
# 预期日志:
# 构建命令: pnpm exec vite build --mode uat
# pnpm install ... Done(含 esbuild 等 postinstall 正常执行)
# HUSKY=0 skip install
# vite v7.1.3 building for uat... ✓ built
截至记录时点:问题 1~3 已验证通过(依赖安装完整走通、构建脚本正常执行);问题 4 的 SKIP_TYPE_CHECK 方案已完成代码修改,待共享库 push 后触发构建做最终验证。
六、注意事项
共享库改动必须 push 到 GitLab main 才生效——Jenkins 每次构建重新拉取
pipelinePublicLibrary@main,本地修改不 push 等于没改。inline Job 与仓库归档副本需手动同步——Jenkins Job 的 Pipeline script 是运行实体,
ops-jenkinsfile仓库中的 Jenkinsfile 只是归档,改任何一侧都要同步另一侧。
- 项目绑定
packageManager版本时,先确认对应 Node 版本要求,再选构建参数nodeVersion - 改公共流水线(共享库)时,禁止固化单一项目的特定依赖清单或构建参数;通用开关 + 项目 Jenkinsfile 按需声明
- CI 资源类故障(OOM)先分析进程行为再考虑加资源;本例根因是无效并行进程,不是内存不足
- 若希望类型检查成为真正的 CI 门禁,应推动项目将脚本改为
vue-tsc --noEmit && vite build(&&串行),由前端团队决策;串行后内存不再叠加,但构建时间变长
七、参考资料
- pnpm settings: dangerouslyAllowAllBuilds
- husky How-To: CI server and Docker
- pnpm 10.0 发布说明(构建脚本默认拦截)
- 本仓库共享库:
ops-jenkinsfile/vars/vueDevopsByk8s.groovy