多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

前端工程化必备:从零搭建Jenkins自动化构建与部署流水线

前端工程化必备:从零搭建Jenkins自动化构建与部署流水线 1. 为什么说前端团队迟早要上 Jenkins1.1 前端工程化走到今天构建这件事早就不是本地打包了早年做前端开发完页面压缩一下丢到服务器就完事。现在不一样了一个中大型前端项目动辄几十个包、几百个路由Vite、Webpack、Turbopack 轮着上SSR、微前端、Monorepo 各种架构叠在一起。你本地npm run build能过不代表换一台机器、换一个 Node 版本、换一个网络环境还能过。更麻烦的是团队里每个人本地环境都不一样张三的 Node 是 18李四还在用 16打包出来的产物偶尔还不一致出了问题都说不清是谁的锅。这时候 Jenkins 的价值就体现出来了。它本质上是一个固定的、干净的、可重复的构建环境。所有代码提交之后都由 Jenkins 在同一个环境里完成依赖安装、代码检查、单元测试、打包构建最后再把产物推到测试服务器或者生产服务器。这个过程不会因为你电脑里多装了一个全局包、少配了一个环境变量就变得不可复现。对于前端团队来说Jenkins 解决的不是能不能打包的问题而是打包这个动作如何标准化、自动化、可追溯的问题。1.2 Jenkins 在整个链路里到底扮演什么角色如果用一个比喻Jenkins 就是一个流水线车间主任。代码仓库是原料库测试服务器是质检台生产环境是成品仓库而 Jenkins 负责把原料从原料库搬出来经过质检、加工、组装最后送到成品仓库。整个过程它来调度每一步有没有过、日志在哪、花了多长时间、是谁触发的全部留痕。在实际前端团队里Jenkins 最常见的接入方式有两种。一种是 Freestyle 自由风格项目界面点点点配置好仓库地址、构建命令、构建后操作简单直接适合三五个人、一两个页面的小团队。另一种是 Pipeline 流水线用 Jenkinsfile 把整个流程写成代码跟着项目仓库走适合需要精细控制、多环境部署、多分支并行开发的中大型团队。我的建议是直接学 Pipeline虽然上手曲线比 Freestyle 陡一点但一旦用起来你能把部署逻辑像代码一样版本化管理换人、加环境、加步骤都非常清晰。对于前端开发者来说Jenkins 还有一个容易被忽略的价值它把部署这件事从后端或者运维手里还给了前端。以前改个页面要等运维手动更新现在只要你把流水线写好推个代码、点个构建测试环境几分钟内就是最新版本。这个效率提升是实打实的。2. 前端场景里 Jenkins 最常见的几种用法2.1 自动构建与产物归档这是前端用 Jenkins 最基础、也是频率最高的用法。代码 push 到仓库后Webhook 触发 Jenkins 构建Jenkins 拉取最新代码执行pnpm install、pnpm build把生成的dist目录归档保存同时推送到目标服务器。这里有个细节很多人第一次会踩坑归档。Jenkins 里有个Archive the artifacts的能力可以把构建产物打包存到 Jenkins 服务器上形成一个带版本号的产物历史记录。这意味着什么意味着你随时可以回到三个月前的某一次构建把当时的dist包下载下来重新发布。线上出了诡异问题怀疑是某次发版引入的直接翻产物历史和 git commit 对照排查效率高很多。我建议前端团队把构建号 Git Commit双标记归档比如web-portal-248-4f2a1c3.tar.gz以后出问题凭这个文件名就能精确回溯到代码版本。2.2 质量门禁检查、单测、覆盖率一起过很多前端团队把 Jenkins 纯当成打包工具挺浪费的。其实它非常适合做质量门禁也就是 Quality Gate。流程可以设计成这样安装依赖跑 ESLint / Stylelint有 error 级别问题直接失败跑单元测试比如 Vitest / Jest失败就中断统计覆盖率低于设定的阈值比如 80%就中断全部通过才继续打包这样做的好处是强制把质量检查从人自觉变成机制兜底。我以前待过一个团队ESLint 配了一大堆规则但本地大家都不跑全靠 code review 人肉看。上了 Jenkins 质量门禁之后第一个月 40 多次构建里有十几次是因为 lint 没过被打回去的后面大家就学乖了提交前自己先跑一遍。单测也一样。前端单测覆盖率本来就不容易维持如果没人盯着很快会滑坡。Jenkins 配合 JaCoCo 或者 Istanbul 插件可以在构建页面直接展示覆盖率趋势图哪个模块下降了一眼就能看出来。2.3 多环境部署测试环境、预发、生产一条流水线前端项目一般都有好几套环境开发环境、测试环境、预发环境、生产环境。每套环境的接口地址、路由前缀、CDN 域名可能都不一样。传统的做法是手动改配置、手动打包、手动上传麻烦不说还容易把测试环境的包发到生产去。用 Jenkins 的参数化构建可以很好地解决这个问题。你在 Pipeline 里定义一个环境参数可选项有test、staging、prod。构建时让执行者选择目标环境流水线根据环境切换对应的.env.test、.env.staging、.env.prod配置来打包然后推送到对应的服务器或对象存储。这里有个关键设计生产环境必须加二次确认或者权限限制。我们团队的方案是生产环境的 Job 单独建一个不开放给所有人只有指定的核心成员有构建权限而且触发生产部署前 Jenkins 会停下来要求输入一个确认 token等于多了一道人为闸门。2.4 多分支并行开发的支持现在前端团队普遍用 Git 分支管理feature 分支开发新功能develop 分支集成测试main/master 分支上线。如果每个分支都要手动拉代码、手动构建累死人不偿命。Jenkins 的 Multibranch Pipeline多分支流水线就是干这个的。它会自动扫描仓库里所有分支每个分支有独立的 Jenkinsfile 就自动生成对应的任务。feature 分支提交代码自动构建到测试环境develop 分支提交代码自动构建到预发环境main 分支提交代码走完整上线流程。配合 Blue Ocean 界面你能很直观地看到每个分支最后一次构建是成功还是失败。哪个分支挂了打开一看就知道是哪个 stage 出的问题。3. 实操一套可直接参考的前端 Pipeline3.1 安装与初始化配置要点先说安装。Jenkins 最省心的跑法是 Docker 部署docker run -d \ --name jenkins \ --restartalways \ -p 8080:8080 \ -p 50000:50000 \ -v jenkins_home:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ jenkins/jenkins:lts注意我挂载了宿主机的 Docker socket这一步后面容器化构建要用。安装完之后浏览器访问http://服务器IP:8080用初始化密码解锁路径在容器里的/var/jenkins_home/secrets/initialAdminPassworddocker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword初始化时插件不要贪多前端团队我建议必装这几个插件作用Pipeline流水线核心支持必装Blue Ocean可视化流水线界面NodeJS在 Jenkins 里管理多版本 NodeCredentials Binding凭据管理操作服务器/仓库需要Publish Over SSH通过 SSH 推送构建产物到服务器Docker Pipeline支持流水线内调用 DockerLocalization: Chinese (Simplified)Jenkins 汉化插件安装插件最怕的就是版本冲突、下载卡住。系统管理里的插件管理页面可以选择升级站点把官方更新中心地址换成国内可访问的镜像地址这个在常见教程里都有具体说明。换完之后插件安装速度会快很多少踩很多坑。3.2 Jenkinsfile 核心示例我直接把一套经过实践验证的前端 Pipeline 贴出来然后逐段讲pipeline { agent any environment { // 这里的变量整个 Pipeline 都能用 NODE_VERSION 20.11.1 PNPM_VERSION 9.0.1 } options { timestamps() // 日志里显示时间排查构建慢很有用 disableConcurrentBuilds() // 同一个分支不允许并行构建 buildDiscarder(logRotator(numToKeepStr: 30, artifactNumToKeepStr: 10)) } parameters { choice( name: DEPLOY_ENV, choices: [test, staging, prod], description: 选择部署环境 ) } stages { stage(Checkout) { steps { checkout scm } } stage(Install Dependencies) { steps { // 使用 nodejs 插件配置好的 Node 环境 nodejs(nodeJSInstallationName: Node20) { sh corepack enable corepack prepare pnpm${PNPM_VERSION} --activate pnpm config set registry https://registry.npmmirror.com pnpm install --frozen-lockfile } } } stage(Lint Type Check) { steps { nodejs(nodeJSInstallationName: Node20) { sh pnpm lint sh pnpm typecheck } } } stage(Unit Test) { steps { nodejs(nodeJSInstallationName: Node20) { sh pnpm test -- --coverage } } } stage(Build) { steps { nodejs(nodeJSInstallationName: Node20) { sh pnpm build --mode ${params.DEPLOY_ENV} } } } stage(Archive) { steps { archiveArtifacts artifacts: dist/**, fingerprint: true } } stage(Deploy) { when { expression { params.DEPLOY_ENV test || params.DEPLOY_ENV staging } } steps { sshPublisher( publishers: [ sshPublisherDesc( configName: web-server, transfers: [ sshTransfer( sourceFiles: dist/**, removePrefix: dist, targetDirectory: /var/www/web/${params.DEPLOY_ENV} ) ] ) ] ) } } } }这段 Pipeline 干了几件关键的事environment里定义了 Node 和 pnpm 版本保证每次构建用的都是同一套工具链。parameters里的环境参数让执行者在点击构建时可以手动选择目标环境。Lint、typecheck、单测串在 Build 之前任何一个挂了流水线直接红。Archive 把产物存留到 Jenkins 服务器方便回溯。最后的 Deploy 通过 SSH 把dist推到部署目录。有个细节要提醒pnpm install --frozen-lockfile这个参数很值得养成习惯。它强制严格按照pnpm-lock.yaml安装锁文件没更新就直接失败避免大家装出来的依赖版本不一样这种最让人头疼的问题。3.3 环境变量与参数化构建玩法Jenkins 内置了一大堆环境变量前端开发者常用的有这么几个变量含义典型用途BUILD_NUMBER当前构建号给产物命名、展示版本号JOB_NAME任务名称区分不同项目的构建目录WORKSPACE工作目录路径定位项目根目录GIT_COMMIT当前构建对应的 Git 提交哈希打包进版本信息文件GIT_BRANCH当前构建的分支名自动判断部署到哪套环境BUILD_TIMESTAMP构建时间戳归档产物命名我个人强烈建议前端项目把版本信息打进前端应用里。做法很简单在构建阶段执行echo {\buildNumber\:\${BUILD_NUMBER}\,\gitCommit\:\${GIT_COMMIT}\,\buildTime\:\$(date %Y-%m-%d %H:%M:%S)\,\deployEnv\:\${params.DEPLOY_ENV}\} public/build-info.json这样页面上或接口里就能随时查应用是哪个 commit 构建出来的、哪个环境、哪个构建号。线上出问题先看版本信息再定位代码省太多时间。参数化构建除了选环境还可以拓展出是否跳过测试、是否回滚这类选项。回滚我推荐一个思路生产部署前先指定上一个构建号的产物路径构建时直接从归档里拉旧包发布相当于回滚按钮比从代码仓库重新构建快得多而且结果和线上那个出问题的版本保证完全一致。3.4 容器化执行环境怎么搭前端项目依赖 Node 环境但不同项目可能要求不同 Node 版本。如果 Jenkins 只装了一个 Node切换很痛苦。用容器化 agent 是最省心的方案。思路是Pipeline 的每个 stage 跑在一个独立的 Docker 容器里容器的镜像直接指定node:20-alpine这类官方镜像Jenkins 自动帮你拉镜像、起容器、执行命令、销毁容器。构建环境永远干净不存在上次装残留的问题。pipeline { agent { docker { image node:20-alpine args --user root -v /root/.pnpm-store:/root/.pnpm-store } } ... }用容器化 agent 之后本地 Jenkins 服务器只需要有 Docker 环境就行Node、pnpm 这些都不用装在宿主机上。团队项目多了每个项目声明各自的镜像版本互不干扰这是最推荐的前端 Jenkins 落地方式。需要注意如果 Jenkins 本身跑在容器里要在启动 Jenkins 容器时挂载 Docker socket就是我前面给出的那条启动命令否则 Jenkins 容器内部无法调用宿主机的 Docker 来创建 agent。这一步经常有人漏掉构建时会报 Docker 相关的权限错误。4. 常见问题与排查技巧实录4.1 我本地能跑Jenkins 上就报错这是入门前端 Jenkins 最经典的问题。本地构建一切正常一上 Jenkins 就各种奇怪的报错。常见原因依赖安装方式不一致。本地可能是旧的node_modules缓存Jenkins 是全新安装。处理方法是所有项目都上锁文件npm ci或pnpm install --frozen-lockfile并且不要手动升级 lockfile 版本。Node 版本不一致。项目要求 Node 18Jenkins 上跑的却是 20某些原生模块构建行为不一样。解决方法是 Jenkins 里用 NodeJS 插件统一指定版本项目能自行安装 nvm 就更好。环境变量缺了。比如项目里需要读取.env.local这个文件在你的.gitignore里根本没提交Jenkins 拉代码自然没有。应对方案是把环境配置纳入参数化构建用 Pipeline 动态写入.env而不是依赖开发机器上的本地文件。排查这类问题先看 Jenkins 构建日志的前几行基本能定位到是哪一步的环境出了问题。日志里加上timestamps()还能顺便看出哪一步耗时异常。4.2 Node 版本不一致导致的幽灵问题前端构建工具这几年对 Node 版本的敏感度越来越高。Vite 5 要求 Node 18某些老项目还在用 Webpack 4 Node 14。同一个 Jenkins 服务要服务各种项目Node 版本管理必须提早做。我试过几种方案。最简单的是在 Jenkins 里装 NodeJS 插件配好几个版本每个任务指定用哪个。但这样项目多了维护也麻烦。后来我改用 nvm 结合 Docker agent 的方式每个项目在 Jenkinsfile 里声明自己的 Node 镜像版本比如node:18.20.4-alpine完全不需要 Jenkins 预装 Node。镜像版本建议锁定到具体的小版本比如18.20.4不要用18-alpine这种模糊的标签。否则镜像更新了构建环境悄悄跟着变上次好好的代码突然挂了排查起来很痛苦。锁定版本之后只要不去主动升级镜像构建环境是确定性的。4.3 凭据、权限与没权限的坑前端流水线经常要连接 Git 仓库、SSH 到部署服务器这就要用到凭据。Jenkins 里凭据统一存在系统管理 - 凭据里流水线通过 credentialsId 引用密码不会明文出现在 Jenkinsfile 中。withCredentials([usernamePassword( credentialsId: deploy-server, usernameVariable: DEPLOY_USER, passwordVariable: DEPLOY_PASS )]) { sshpass -p $DEPLOY_PASS scp -r dist/* $DEPLOY_USERserver:/var/www/web }这里要提醒几个容易踩的坑。第一个是 SSH Host Key 问题没在 Jenkins 服务器上做过 key 扫描的话首次连接会提示确认指纹而 Jenkins 的非交互环境不会有人去敲 yes任务会挂住。解决方案是启动容器时挂载~/.ssh或者预先用ssh-keyscan把目标服务器指纹写进known_hosts。第二个是权限原则流水线使用的 Git 账号、服务器账号一定要用最小权限不要图省事直接给 root。构建服务器一旦被入侵权限越小损失越小。4.4 部署后页面白屏或资源 404前端构建产物部署到 Nginx 之后出现白屏或者 JS/CSS 404几乎每个团队都遇到过。问题大多出在静态资源配置上。如果你的项目是 history 路由比如vue-router的createWebHistory部署目录下没有对应的index.html兜底刷新二级路由就会 404。Nginx 需要配置location / { try_files $uri $uri/ /index.html; }如果你用的是 hash 路由反倒不用管这个但 URL 会带#不够美观。另一个高频问题是 CDN 路径。打包配置里base或publicPath写的是绝对路径/assets/但部署时是放在子目录所有资源引用全部错位页面自然白屏。解决方法是按环境区分base测试环境用相对路径或者固定子路径生产环境用 CDN 域名。我的习惯是统一在.env.xxx里定义VITE_PUBLIC_PATH构建脚本读取这个值不在代码里写死。还有浏览器缓存问题。文件名不带 hash 的旧资源被浏览器缓存发新版之后用户还是旧页面。现在主流打包工具都默认加 content hash但如果你们项目还在手动改文件名建议尽快统一到脚手架的标准流程上。4.5 Jenkins 汉化与界面配置小记Jenkins 默认英文界面对非英语环境的前端团队确实不太友好。汉化很简单在系统管理 - 插件管理 - 可选插件里搜Localization: Chinese (Simplified)安装后重启。装完进入系统管理 - 全局配置把界面语言切换成中文即可。不过说实话Jenkins 的很多核心术语就算汉化了也不一定好理解比如 workspace、stage、artifact我建议团队成员还是把 Pipeline 的基本概念先搞明白界面语言只是辅助。真正干活的时候Jenkinsfile 里的关键字都是英文跑构建看输出也基本是英文日志熟能生巧。界面配置里有一个值得做的优化给不同的 Job 加描述和标签。项目多了之后Jenkins 首页一大堆任务没有人知道哪个对应哪个项目。用文件夹把任务分组文件夹名用项目名任务描述写上xx 项目测试环境自动部署触发方式手动/Webhook团队成员一眼就能找到自己负责的流水线。4.6 Webhook 触发与构建失败的连带问题很多团队配好了 Webhook推代码自动触发构建但偶尔会出现我明明推了代码Jenkins 怎么没反应的情况。先在 Jenkins 任务里看一下最后一次构建时间和代码仓库的 commit 时间对不对得上。对不上检查 webhook 配置是否正常。Git 仓库的 webhook 地址要写成http://Jenkins服务器:8080/github-webhook/以 GitHub 为例注意末尾斜杠别漏。团队里还有一种高频场景多个 commit 连续推送Jenkins 排队构建但每一条都会跑一遍完整流程。项目大一点构建很耗时。这时候可以在 Pipeline 里加一个变更检测的 stage先比较这次构建和上一次构建的代码差异如果只改了某个模块就只构建模块对应的子任务。这一步对 Monorepo 项目尤其重要能省下大量机器资源。最后说一句Jenkins 这东西绕不开也没必要怕。它本质上就是把前端团队每天都做的那几件事——装依赖、跑检查、打包、上传——用一个可靠的执行器统一接管。真正跑通一次之后你会发现团队省下来的不只是时间还有信任。以前发个版要喊一圈人现在推代码、看流水线、等结果就完事了。
返回列表