前端工程化实战:从本地开发到线上部署的完整链路
上周五晚上十一点,我还在公司改一个 React 项目的构建配置。产品经理在群里@我说“明天演示要用新功能”,而我们的 CI/CD 流水线又莫名其妙挂了。那一刻我真的想砸电脑——但转念一想,这不就是我们三线城市小厂技术负责人的日常吗?
我是老张,在这家做本地生活服务的互联网公司带前端团队快两年了。说“带”其实有点夸张,毕竟整个前端组就仨人,其中一个还是实习生。但麻雀虽小,五脏俱全,该踩的坑一个没少。去年双11期间,我们因为构建缓存没清理干净,导致上线后首页白屏,用户投诉电话直接打到了CEO那儿。从那以后,我就下定决心把前端工程化搞明白。
最近我也在偷偷学AI,不是为了转行(虽然确实有点心动),主要是看到GitHub上那些AI辅助编程的工具越来越香,想着能不能用到我们流程里。不过今天先不聊AI,聊聊更接地气的——怎么让前端开发不那么“玄学”。
工具链:别再手动 npm install 了
很多新人入职第一天,都会被我们复杂的依赖安装流程劝退。以前我们也像大多数小团队一样,直接 npm install 完事。直到有一次,一个同事用了新版 Node.js,结果本地跑得好好的代码,一推到测试环境就报错:“Cannot find module 'react-dom/server'”。
后来我们统一了几个关键点:
- 锁定 Node 版本:用
.nvmrc文件指定版本 - 包管理器统一:强制使用 pnpm(比 yarn 快,比 npm 省空间)
- 依赖分层:区分 devDependencies、dependencies 和 peerDependencies
# .nvmrc
18.17.0
# package.json 关键配置
{
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"preinstall": "node ./scripts/check-engine.js"
}
}
那个 check-engine.js 脚本很简单,就是检查当前 Node 版本是否匹配。别小看这一步,自从加了它,新人入职第一天就能跑起来项目,再也不用在群里问“为啥我本地报错了”。
说到新人,前两天面试了一个应届生,简历上写着“熟悉前端工程化”。我让他现场解释一下 Webpack 和 Vite 的区别,结果他支支吾吾说不清。其实我不指望应届生懂多深,但至少得知道怎么配个 alias 吧?求职市场卷成这样,连基础工具链都不了解,真的很难拿到 offer。
构建优化:React 项目也能秒开
我们的主应用是 React + TypeScript 写的,页面数不多但功能杂。最开始用 Create React App,简单是简单,但打包速度慢得像蜗牛。随着组件越来越多,每次 npm run build 都要等两分钟,开发体验极差。
后来我们迁移到 Vite,配合一些自定义配置:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
// 避免相对路径地狱
}
},
build: {
sourcemap: true,
rollupOptions: {
output: {
manualChunks(id) {
// 将第三方库单独打包
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
}
}
}
})
这个 manualChunks 配置特别有用。之前所有第三方库都打在一个 vendor.js 里,有 2MB 大小。现在按包名拆分,首屏加载时间从 4s 降到 1.2s。而且缓存策略也更好做了——业务代码经常变,但 lodash、moment 这些工具库很少更新。
当然,Vite 也不是万能的。有次我们用了一个比较冷门的 UI 库,Vite 的 HMR(热更新)直接失效,改一行代码就要全量刷新。查了半天才发现是那个库用了动态 require。这种时候只能默默加个 /* @vite-ignore */ 注释,然后祈祷别再遇到类似问题。
自动化测试:别让爬虫毁了你的数据
说到测试,很多人觉得前端不需要。但我们在做 SEO 优化时吃过亏。为了让搜索引擎能抓取内容,我们上了 SSR(服务端渲染)。结果某天发现,百度爬虫访问时页面全是 loading 状态。
原因很简单:SSR 渲染时,有些数据请求依赖客户端的 cookie 或 localStorage,而爬虫根本没有这些。后来我们加了一套自动化测试:
- 用 Puppeteer 模拟爬虫访问
- 检查关键内容是否渲染出来
- 监控首屏加载性能
// tests/crawler.test.js
test('首页应包含核心内容,即使无用户上下文', async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 模拟爬虫 UA
await page.setUserAgent('Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)');
await page.goto('http://localhost:3000');
const content = await page.$eval('.main-content', el => el.textContent);
expect(content).toContain('本地生活服务');
await browser.close();
});
这套测试集成到 CI 流程里,每次 PR 都会跑一遍。虽然增加了几分钟构建时间,但避免了线上事故,值了。
其实做这些测试还有一个私心:我们团队最近在招人,我想在 GitHub 仓库里展示一些工程化实践,让求职者看到我们不是那种“写页面就行”的作坊式团队。毕竟现在好点的前端工程师,都会看公司技术栈和开发流程。
部署流程:告别手动 FTP
两年前,我们的部署流程是这样的:开发完功能 → 手动打包 → 用 FileZilla 上传到服务器 → SSH 登录重启服务。听起来是不是很原始?但这就是很多小公司的现状。
后来我们搭了 GitLab CI,流程变成了:
- 提交代码到 feature 分支
- 创建 MR(Merge Request)
- 自动运行 lint + test
- 合并到 develop 分支后,自动部署到测试环境
- QA 通过后,合并到 main 分支,自动部署到生产环境
关键配置如下:
# .gitlab-ci.yml
stages:
- build
- test
- deploy
build_app:
stage: build
script:
- pnpm install
- pnpm run build
artifacts:
paths:
- dist/
test_crawler:
stage: test
script:
- pnpm run test:crawler
deploy_production:
stage: deploy
script:
- rsync -avz --delete dist/ user@prod-server:/var/www/html/
- ssh user@prod-server "pm2 reload app"
only:
- main
这里有个小技巧:我们用 rsync 而不是直接 scp 整个目录。它只会传输变化的文件,速度快很多。而且 --delete 参数能确保删除已移除的文件,避免旧文件残留。
不过最让我自豪的不是这些自动化脚本,而是我们建立的回滚机制。每次部署前,CI 会自动备份当前版本。万一线上出问题,运维同事(其实是我自己)可以在 30 秒内回滚到上一个稳定版本。去年春节大促前,我们就靠这个救了场——当时新版本有个内存泄漏,流量一上来服务器直接 OOM。回滚后,我一边喝着咖啡一边修 Bug,心态稳得一批。
总结:工程化不是炫技,是生存必需
回头看这两年,前端工程化给我们带来的最大改变不是技术上的,而是心态上的。以前总觉得“小公司没必要搞这么复杂”,结果每次加班都是因为流程混乱。现在虽然前期投入多,但长期来看省下了大量救火时间。
最近我还尝试把 AI 工具集成到流程里。比如用 GitHub Copilot 写单元测试,或者用一些 AI linter 检查代码风格。效果嘛……只能说辅助作用,不能太依赖。但至少让重复工作少了一些,可以多花点时间思考架构问题。
如果你也在一个小团队,我的建议是:不要追求一步到位,但一定要有演进意识。今天解决一个问题,明天优化一个环节,积少成多。毕竟我们不是 FAANG,资源有限,但也不能躺平。
对了,上周那个周五晚上的构建问题,最后发现是 GitHub Actions 的缓存没清理。清掉缓存重新跑,一分钟搞定。我当时就想,要是早点把这套流程文档写好,新人也不用半夜被叫起来排查问题了。
所以写下这篇文章,既是总结,也是给团队留个文档。希望下次招人的时候,能让求职者看到:虽然是三线城市的小公司,但我们认真对待每一行代码。

评论 0