凌晨两点半,我对着屏幕敲下最后一行部署脚本
——一个异地奶爸的前端工程化血泪史
一、那个差点让我崩溃的周五晚上
去年十一月的一个周五晚上,大概十一点四十,我终于把两岁半的儿子哄睡了。小家伙那天闹腾得厉害,非要听三遍《孤勇者》才肯闭眼。我轻手轻脚地从儿童房退出来,顺手把门带上,客厅里老婆视频电话刚好打过来。
"又加班?"她声音里带着疲惫。她在成都,我在杭州,隔着1700公里。
"没,刚把豆子哄睡。"我压低声音,走到阳台上,点了根烟。其实我去年就戒了,但异地这半年又捡起来了,一个月大概抽三四包,算下来一个月多花小一千块。
"下个月房贷6800,豆子托儿费3200,杭州这边房租3500……"她掰着手指头算,"你那边工资到账没?"
"到了,18500。"我吐了口烟,"还完信用卡大概能剩个八九千。"
电话那头沉默了几秒。我知道她在想什么。我们在成都那边还有套房子,月供4200,加上杭州的开销,两个人加起来月入三万多,听起来不少,但真正能攒下的没几个子儿。
"早点睡吧,周末我带豆子去动物园,给你拍视频。"她说。
"好。"
挂了电话,我本来应该去睡觉的。但那天晚上,我失眠了。
起因是下午组里出了个P0级事故。我们负责的一个ToB后台系统,因为前端构建产物体积太大,CDN节点分发超时,导致华东区用户大面积白屏。事故持续了47分钟,老板在群里连发了三个"?"。
说实话,那个项目的代码是我去年刚入职时接手的"祖传代码"。上一任前端老哥离职前留下的Webpack配置,简直是灾难现场——30多个loader,十几个plugin,构建一次要等将近8分钟。我当时想着"先跑起来再说",结果这个"再说"就拖了一年。
那天晚上我躺在床上,翻来覆去睡不着。脑子里全是那个构建日志里红彤彤的WARNING。我起身走到书房,打开电脑,鬼使神差地开始搜"前端工程化最佳实践"。
也就是从那天晚上开始,我花了大概三个月时间,把整个项目的工具链和部署流程从头到尾重构了一遍。今天这篇文章,就是想把这段经历记录下来,也算给自己一个交代。
二、先说说我接手的那个"屎山"
在聊具体方案之前,我得先吐槽一下我接手的那个项目,给各位打个预防针。
我们组做的是个企业级SaaS后台,技术栈是React 17 + TypeScript + Ant Design,听起来挺主流的,对吧?但问题出在工程化层面:
构建工具: Webpack 4,配置文件1200多行,里面各种require和module.exports混着写,连个注释都没有。我后来用Vite替换的时候,光是理清依赖关系就花了两天。
代码规范: 没有ESLint,没有Prettier,没有Husky。代码风格全靠"自觉"。组里五个前端,每个人写出来的JSX缩进都不一样,有人用两个空格,有人用四个,还有人用Tab。我有一次Code Review,看到一个组件里混着三种引号——单引号、双引号、还有反引号用来表示普通字符串。我当时的心情,怎么形容呢,就像你打开冰箱发现有人把榴莲和蛋糕放在一起。
Git流程: 没有分支规范,所有人都在master上直接push。Commit message全靠心情,"fix bug"、"update"、"111"、"asdf"……我翻git log的时候一度怀疑自己是不是在看什么行为艺术。
部署流程: 这个最离谱。每次上线,需要前端手动执行npm run build,然后把dist目录打成zip包,通过SSH传到服务器上,再手动解压到Nginx对应目录。有一次同事把测试环境的包传到了生产环境,差点酿成事故。
"这都2023年了,你们还在用这种上古时代的部署方式?"我当时听到之后整个人都不好了。
但吐槽归吐槽,活儿还是得干。老板给的deadline是年底前完成重构,而且不能影响业务迭代。也就是说,我得一边开着飞机一边换引擎。
三、工具链重构:从Webpack到Vite的"无痛"迁移
3.1 为什么选Vite?
其实在选型的时候,我也纠结过。Webpack 5 + Module Federation、Turbopack、Farm……选项很多。但最终选Vite,原因很实在:
第一,快。我们项目有大概400多个组件,Webpack冷启动要50多秒,Vite只需要3秒。这不仅仅是体验问题,一天构建十几次,每次省一分钟,一个月下来就是好几个小时。对于我这种晚上才有时间搞基建的人来说,时间就是命。
第二,生态成熟。Vite到2023年已经非常稳了,React支持完善,插件生态也够用。我们需要的功能基本都有现成方案。
第三,迁移成本低。Vite底层用的是Rollup做生产构建,和Webpack的Tree Shaking逻辑类似,迁移过来不需要大改业务代码。
但"迁移成本低"这句话,我说了是要打引号的。实际上,从Webpack 4迁到Vite,我踩了不少坑。
3.2 迁移过程中的几个坑
坑一:CommonJS和ESM的混用问题。 老项目里有些第三方库只支持CJS格式,Vite默认走ESM,直接报错。我花了一整天排查,最后通过在vite.config.ts里配置optimizeDeps.include把这些库预构建解决了。
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: [
'some-old-library',
'another-legacy-package'
]
}
})
坑二:环境变量。 Webpack用的是process.env和DefinePlugin,Vite用的是import.meta.env。项目里有大概30多处用到了环境变量,我得一个个改。好在有GPT-4o帮忙——对,就是那个OpenAI家的大模型。我把整个src目录下的文件内容喂给它,让它帮我批量替换,效率提升了不止十倍。
这里多说一句,我现在日常开发已经深度依赖GPT-4o了。不仅仅是写代码,包括看文档、排查问题、写单测、甚至写技术方案,都会先让它给个初稿,然后我再修改。有人觉得这是"偷懒",但我觉得这叫"工具调用"——善用工具本身就是一种能力。就像你用IDE的代码补全,没人会说你"不配当程序员"对吧?
坑三:CSS Modules的处理。 老项目里CSS Modules的命名规则和Vite默认的不一样,导致样式全乱了。我花了半天时间写了一个PostCSS插件来兼容旧命名。
整个迁移过程大概花了两周,每天晚上下班后搞两三个小时,周末集中攻坚。那段时间基本没怎么跟老婆视频,因为她一打过来我就在debug,经常敷衍两句就挂了。有一次她直接发火了:"你是不是在外面有人了?"我哭笑不得,解释半天她才信我是在搞技术重构。
3.3 构建速度对比
迁移完成后,我做了个简单的性能对比:
| 指标 | Webpack 4 | Vite | 提升 |
|---|---|---|---|
| 冷启动 | 52s | 2.8s | 18倍 |
| 热更新 | 3-5s | 0.3s | 10倍+ |
| 生产构建 | 7min 42s | 1min 15s | 6倍 |
| 产物体积 | 4.2MB | 2.8MB | 33% |
看到那个数字的时候,我长舒了一口气。7分42秒变成1分15秒,这意味着每次上线前我不用再泡杯咖啡干等着了。
四、代码规范:让机器来当"恶人"
工具链搞定了,接下来是代码规范。这块我采取的策略是:能用工具解决的,绝不靠人自觉。
4.1 ESLint + Prettier
我选了@antfu/eslint-config——这是Anthony Fu大佬维护的一套ESLint配置,开箱即用,对React + TypeScript支持很好。配合Prettier做格式化,基本覆盖了所有常见的代码风格问题。
4.2 Husky + lint-staged
光有规则不行,还得有执行机制。我配了Husky,在pre-commit钩子里跑lint-staged,只检查本次提交修改的文件。这样不会拖慢提交流程,又能保证每次进仓库的代码都是干净的。
// .lintstagedrc.json
{
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,less,scss}": ["prettier --write"]
}
4.3 Commitlint
配合@commitlint/config-conventional,强制要求Commit Message遵循Conventional Commits规范。第一次配完跑起来的时候,组里同事怨声载道:"以前想写啥写啥,现在连提交个代码都要写小作文?"
但两个月后,大家发现好处了——changelog可以自动生成了,版本回溯也清晰了。那个之前写"asdf"当commit message的同事,现在写得比谁都规范。人性就是这样,你给他自由他就摆烂,你给他规则他反而能做好。
五、部署流程:从"刀耕火种"到CI/CD
这是整个重构里我最满意的部分,也是改动最大、收益最高的部分。
5.1 之前的流程(回顾一下,感受一下痛苦)
手动npm run build → 手动打zip包 → SSH登录服务器 → 手动上传 → 手动解压 → 手动重启Nginx
每次上线大概需要20-30分钟,而且容易出错。前面说的把测试包传到生产环境的事故,就是这么来的。
5.2 现在的流程
我选了GitHub Actions做CI,配合阿里云的OSS + CDN做静态资源托管,Nginx做反向代理。整个流程如下:
代码Push → GitHub Actions自动触发 → Lint + Test → Build → 上传OSS → 刷新CDN缓存 → 自动通知钉钉群
全流程自动化,从代码提交到线上生效,大概3-5分钟。而且每一步都有日志,出了问题可以秒级定位。
这里分享一个我觉得很实用的细节:我在CI流程里加了一个"产物体积检查"的步骤。如果构建产物超过阈值(我们设的是3MB),会自动在PR上留一个Warning评论。这样可以在代码审查阶段就发现体积问题,而不是等到线上才发现。
# .github/workflows/check-bundle-size.yml
- name: Check bundle size
run: |
SIZE=$(du -sm dist | cut -f1)
if [ $SIZE -gt 3 ]; then
echo "::warning::Bundle size exceeds 3MB!"
fi
5.3 多环境管理
我们有三套环境:开发、预发、生产。每套环境对应不同的API地址和配置。之前是通过手动改代码里的常量来切换,现在全部用环境变量 + .env文件管理,CI流程里根据分支自动选择对应的环境配置。
main分支 → 生产环境
staging分支 → 预发环境
feature/*分支 → 开发环境
这个改动看起来简单,但解决了之前最大的一个痛点——"我本地是好的啊"。现在所有环境配置都是代码化管理,谁改了什么一目了然。
六、一些数据和成果
重构完成后三个月,我做了个简单的数据统计:
- 构建时间: 日均构建次数从8次增加到15次(因为快了,大家更愿意频繁构建了),但总构建时间反而减少了60%
- 线上事故: 部署相关的事故从月均2.3次降到0次
- 代码质量: ESLint报错从上线初期的日均47个降到个位数
- 上线效率: 从每次30分钟降到5分钟,而且不需要专人盯着
老板在季度review的时候专门提了这个事,说"工程化水平有明显提升"。虽然没涨工资(苦笑),但在绩效上给了个A,算是个安慰。
七、写在最后:一些真心话
写到这里,已经是凌晨一点多了。豆子刚才又醒了一次,我起来哄了二十分钟才重新睡下。现在坐在书房里,听着窗外偶尔驶过的车声,突然有些感慨。
做前端工程化这件事,说实话,在很多团队里是不受重视的。老板觉得"又不能直接带来业务价值",同事觉得"又不能吃不能喝,瞎折腾什么"。但只有真正经历过"凌晨三点被叫起来排查部署事故"的人,才知道这些东西有多重要。
我想给同行们几点建议,都是我自己踩坑踩出来的:
第一,不要追求完美,先跑起来。 我一开始想一步到位搞个"终极方案",结果越搞越复杂,最后发现很多功能根本用不上。工程化是个渐进的过程,先解决最痛的问题,其他的慢慢迭代。
第二,善用AI工具。 现在GPT-4o这种大模型已经很强了,写配置文件、排查报错、生成模板代码,都能帮你省大量时间。我这次重构,大概有30%的代码是AI辅助生成的。这不是偷懒,这是"工具调用",是效率的体现。
第三,文档比代码更重要。 你写得再好的配置,如果没有文档,下一个人接手还是抓瞎。我现在每做一个改动,都会同步更新README和内部Wiki。虽然多花点时间,但长期来看是省事的。
第四,关注人,不只是关注技术。 工具链再好,如果团队不愿意用,也是白搭。我每次引入新工具,都会先做个小分享,讲清楚"为什么用"和"怎么用",而不是直接甩一个配置文件让大家自己看。
最后,说点私人的话。
异地这件事,真的很磨人。上个月发工资,我算了算,扣完各种开销,卡里只剩了4700块。当时坐在阳台上抽了半包烟,心里想着"这日子什么时候是个头"。但转头看到手机里豆子昨天发的视频——他摇摇晃晃地走着,突然回头冲镜头笑了一下——我就觉得,还得继续扛。
技术这条路,说难也难,说简单也简单。难的是坚持,简单的是——你只要一直在学,一直在进步,日子总会好起来的。
下个月我打算申请调回成都的办公室,虽然薪资可能要降个两三千,但能天天看到老婆孩子,值了。到时候把这套工程化方案在 new 团队也落地一遍,争取把构建时间再压一压。
好了,烟抽完了,该去睡觉了。明天还要早起给豆子做辅食,他最近爱吃南瓜粥,得提前把南瓜蒸好。
晚安,各位打工人。
如果这篇文章对你有帮助,点个赞或者收藏,算是给我加个鸡腿。有什么问题欢迎评论区交流,我尽量每条都回。
—— 2024年1月12日 凌晨1:47 于杭州


评论 0