.github/workflows/deploy.yml

云端小木屋
2025-06-13 19:37
阅读 354

前端工程化:从0到1搭建一个真实项目的工作流实践

前端工程化:从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.jsonreferences 字段来解决的:

{
  "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%。


给前端小伙伴的一些建议

如果你现在也在做类似的事情,以下几点是我亲身试过的建议,希望对你有帮助:

  1. 不要一开始就追求完美架构
    工程化不是一蹴而就的事情,尤其是从零开始。先保证最小可行性方案跑通,再逐步迭代改进。

  2. 代码规范要尽早落地
    一旦代码风格失控,后期想改回来的成本非常高。建议在第一周就把 ESLint、Prettier 强制落地。

  3. 工具链一定要文档化
    比如你的 lint 规则怎么运行、脚手架怎么用、发布流程有哪些注意事项,这些最好有个简单的 README 或 Wiki 页面。

  4. 持续集成要和开发流程结合
    如果只是加了个 GitHub Action,但没人关心它的执行结果,那就等于没加。建议把 CI 结果对接 Slack 或企业微信,提高团队关注度。

  5. 性能优化别落下
    不管是首屏加载、图片懒加载还是资源压缩,用户对慢的忍耐度很低。可以配合 Lighthouse 做定期打分,养成优化习惯。


写在最后

前端工程化其实不是一个高冷的概念,它更像是我们作为开发者对自己职业素养的一种体现。当你开始重视构建流程、关注代码结构、思考协作方式的时候,就已经走上了一条更专业、可持续的成长道路。

我也曾经是个只会写页面的前端仔,直到工程化的思维逐渐渗透到我的日常工作中,我才真正体会到那种“掌控感”——你可以放心地写出新功能,而不担心埋雷;你可以大胆重构,因为你有一整套测试和流程在为你保驾护航。

希望这篇文章能让你少走点弯路,也希望你在自己的团队里,也能推起一场属于自己的“工程化革命”。

如果你有自己实践的经验或者碰到过相似的问题,欢迎留言交流!

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝