.github/workflows/deploy.yml
前端工程化:从0到1搭建一个真实项目的工作流实践

去年我加入了一家快速扩张的创业公司,负责搭建一个新的SaaS产品前端系统。当时我们面临的现状是:团队人员来自五湖四海、技术栈各异、代码质量参差不齐、开发流程混乱、上线效率低下……这些都在直接影响产品交付节奏。
于是我就开始着手推动整个前端团队的“工程化”建设。说实话,刚开始的时候挺难的,很多同事觉得这些“虚头巴脑的东西”,不如赶紧写页面来得实在。但当我真的一步步把工具链跑起来、CI/CD流程跑通后,大家才发现:原来代码可以这么清爽,部署可以如此丝滑!
今天我想以一个开发者亲历的视角,分享我在那个项目中所做的一些前端工程化的尝试和经验,希望能帮助到同样在挣扎的你。
项目背景 & 遇到的问题
我们的项目是一个基于React的企业级数据看板系统,需要支持主流浏览器,并兼顾PC和移动端的访问。初期有6名前端工程师(包括我在内),团队成员背景差异很大,有全栈出身的、有刚毕业的新人,也有之前只做过传统jQuery项目的。
我们一开始的开发状态可以用一个字形容——乱。
比如:
- 每个人本地环境配置不一样,npm包版本经常冲突。
- 没有统一的构建流程,每次上线都要手动改几个config.js文件。
- 代码风格五花八门,ESLint基本没人搭理。
- 发布流程完全靠口头沟通,经常出错。
- 最关键的是,随着代码库越来越大,构建时间也变得越来越长……
这些问题直接导致我们上线频率低、线上 bug 多、排查困难,甚至连谁改动了什么都说不清楚。
我们的解决方案:打造规范化的前端工作流
我们意识到必须建立一套完整的开发&协作流程,目标很明确:
- 开发体验一致、降低新人上手成本
- 代码质量和规范可控
- 构建和发布自动化,避免人为失误
- 性能优化到位,保障用户体验
为此,我们围绕“工具链 + 协作规范 + 自动化”三大核心方向进行了重构。
一、统一的脚手架和代码规范
第一步,我用create-react-app做了个定制模板,再结合Prettier+ESLint制定了一套统一的编码规范,并集成了TypeScript、Ant Design等必要的依赖。然后将它包装成一个内部私有的CLI工具,名字叫 @company/create-app。安装命令如下:
npx @company/create-app my-project
同时,在代码风格上,我参考了Airbnb的开源规则,并做了部分简化。团队讨论通过后,我们强制执行如下规则:
{
"extends": ["react-app", "airbnb", "prettier"],
"rules": {
"import/no-unresolved": 0,
"no-console": [2, { allow: ["warn", "error"] }]
}
}
并配置了VS Code自动保存格式化,确保所有人写的代码风格是一致的。
二、引入Monorepo结构管理多模块
由于项目后续会拆分为多个子模块(例如登录页、仪表盘、报告中心等),于是我决定采用 Lerna + Nx 的组合来进行代码管理。
我们最终采用了 Lerna 的 useWorkspaces 方式进行集成,目录结构大致如下:
/packages/
common/ <-- 公共组件、工具函数
dashboard/ <-- 主体业务模块
auth/ <-- 登录相关
/tools/
scripts/ <-- 构建脚本、CI配置
/lerna.json
package.json
这样做的好处是:
- 同时开发不同模块时不用重复安装依赖
- 各模块之间可共享 typescript 类型、样式变量等
- 可统一打包、测试、部署
当然,这也带来了新的问题——比如如何统一构建各个子项目?这里我用了一个公共脚本封装的方式:
// tools/scripts/build.js
const fs = require('fs');
const path = require('path');
const execSync = require('child_process').execSync;
const packagesDir = path.resolve(__dirname, '../packages');
const dirs = fs.readdirSync(packagesDir).filter(dir => {
return fs.existsSync(path.join(packagesDir, dir, 'package.json'));
});
dirs.forEach((dir) => {
const pkgPath = path.join(packagesDir, dir);
console.log(`📦 Building package: ${dir}`);
execSync('npm run build', { cwd: pkgPath, stdio: 'inherit' });
});
构建完以后输出到 dist 目录,用于后续部署。
三、CI/CD自动化部署
为了解决人工发布容易出错的问题,我们搭建了一个完整的 CI/CD 流程,使用 GitHub Actions 实现:
name: Deploy App
on:
push:
branches:
- main
jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
- name: Deploy to S3
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
password: ${{ secrets.PASSWORD }}
port: 22
script: |
cd /var/www/app && \
rm -rf * && \
cp -r $GITHUB_WORKSPACE/dist/* .
这个流程解决了几个大问题:
- 提交即触发自动构建
- 所有人发布前都走同一份流程,不会遗漏步骤
- 出错了GitHub会立刻通知团队负责人
另外,我还接入了 Sentry 进行错误监控,以及 Lighthouse 做性能审计。这些都成为了每日构建的一部分。
踩坑记:那些年我们一起经历的崩溃时刻 😅
在整个过程中,我们也踩了不少坑,最有印象的有三个:
1. Babel 编译报错
在一次升级依赖之后,发现某些用户的IE11上报错说“Promise未定义”。检查构建后的代码发现一些 ES6 特性没有被正确转换。后来定位是因为我们在 browserslist 里少写了ie 11。
解决方式很简单,在 package.json 中添加:
"browserslist": {
"production": [
"ie 11",
"last 2 versions"
],
"development": [
"last 1 chrome version",
"last 1 firefox version"
]
}
2. Monorepo引用类型错误
我们在一个子模块中用了另一个模块中的 Typescript 类型,但在构建时报错找不到类型。原因是 TSC 默认只会编译当前包的源码,不会去解析 symlink 的其他包。
最终是通过给每个 package 添加 tsconfig.json 的 references 字段来解决的:
{
"references": [
{ "path": "../common" }
]
}
并在根目录开启 composite 模式:
{
"compilerOptions": {
"composite": true,
...
}
}
3. CI流程频繁超时
早期因为依赖太多,加上网络不稳定,GitHub Actions 的 Node 安装过程经常卡住。最后换成了 yarn,并利用缓存机制:
- name: Cache yarn packages
uses: actions/cache@v3
with:
path: ~/.cache/yarn
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
安装速度提升了好几倍。
成果与收益:看得见的改变
经过大概两个月的集中优化,整个开发流程发生了质的变化:
- 新人入职只需一条命令就能跑起本地项目,不再因环境问题耽误进度
- 代码Review效率大大提高,因为格式和规范已经统一了
- 上线变得简单可靠,每天早上打开电脑就知道昨天的更新是否成功
- 构建时间从原来的7~8分钟缩短到3分钟以内
最关键的是,产品的稳定性明显提升,用户反馈的前端BUG比以前减少了约60%。
给前端小伙伴的一些建议
如果你现在也在做类似的事情,以下几点是我亲身试过的建议,希望对你有帮助:
不要一开始就追求完美架构
工程化不是一蹴而就的事情,尤其是从零开始。先保证最小可行性方案跑通,再逐步迭代改进。代码规范要尽早落地
一旦代码风格失控,后期想改回来的成本非常高。建议在第一周就把 ESLint、Prettier 强制落地。工具链一定要文档化
比如你的 lint 规则怎么运行、脚手架怎么用、发布流程有哪些注意事项,这些最好有个简单的 README 或 Wiki 页面。持续集成要和开发流程结合
如果只是加了个 GitHub Action,但没人关心它的执行结果,那就等于没加。建议把 CI 结果对接 Slack 或企业微信,提高团队关注度。性能优化别落下
不管是首屏加载、图片懒加载还是资源压缩,用户对慢的忍耐度很低。可以配合 Lighthouse 做定期打分,养成优化习惯。
写在最后
前端工程化其实不是一个高冷的概念,它更像是我们作为开发者对自己职业素养的一种体现。当你开始重视构建流程、关注代码结构、思考协作方式的时候,就已经走上了一条更专业、可持续的成长道路。
我也曾经是个只会写页面的前端仔,直到工程化的思维逐渐渗透到我的日常工作中,我才真正体会到那种“掌控感”——你可以放心地写出新功能,而不担心埋雷;你可以大胆重构,因为你有一整套测试和流程在为你保驾护航。
希望这篇文章能让你少走点弯路,也希望你在自己的团队里,也能推起一场属于自己的“工程化革命”。
如果你有自己实践的经验或者碰到过相似的问题,欢迎留言交流!

评论 0