2024年10月25日9 分钟

前端工程化实践:从规范到自动化的完整体系

系统性梳理前端工程化的核心实践,涵盖代码规范、构建工具、模块化、Monorepo、微前端等具体问题

什么是前端工程化

前端工程化是用软件工程的方法来管理前端项目,解决效率、质量、协作三大问题:

代码层面:规范 → 格式化 → 类型检查 → 测试
构建层面:打包 → 压缩 → 优化 → 产物分析
流程层面:Git 规范 → Code Review → CI/CD → 监控
架构层面:模块化 → 组件化 → Monorepo → 微前端

一、代码规范体系

ESLint + Prettier + TypeScript 三件套

ESLint:检查代码逻辑问题(未使用变量、不安全操作...)
Prettier:统一代码格式(缩进、引号、分号...)
TypeScript:类型安全

三者配合:
  Prettier → 管格式(怎么排版)
  ESLint → 管逻辑(怎么写才对)
  TypeScript → 管类型(数据结构对不对)
// .eslintrc.json
{
  "extends": [
    "next/core-web-vitals",
    "plugin:@typescript-eslint/recommended",
    "prettier"  // 关闭与 Prettier 冲突的 ESLint 规则
  ],
  "rules": {
    "no-console": "warn",
    "@typescript-eslint/no-unused-vars": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}
// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100
}

Git Hooks 自动化

# 安装 husky + lint-staged
npm install -D husky lint-staged

# 初始化 husky
npx husky init
// package.json
{
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,css}": [
      "prettier --write"
    ]
  }
}
# .husky/pre-commit
npx lint-staged

# .husky/commit-msg
npx commitlint --edit $1

效果:每次 git commit 时,自动对暂存文件执行 ESLint + Prettier,不通过则阻止提交。

Commit 规范

# commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', [
      'feat',     // 新功能
      'fix',      // 修复 bug
      'docs',     // 文档
      'style',    // 格式(不影响代码)
      'refactor', // 重构
      'perf',     // 性能优化
      'test',     // 测试
      'chore',    // 构建/工具
      'ci',       // CI 配置
    ]],
  },
}
# ✅ 合法
git commit -m "feat: add user authentication"
git commit -m "fix: resolve login timeout issue"

# ❌ 不合法(被 hook 拦截)
git commit -m "update code"
git commit -m "fix bug"

二、构建工具链

Webpack vs Vite vs Turbopack

Webpack(成熟稳定):
  ├── Bundle-based:先打包整个应用,再启动
  ├── 冷启动慢(大项目 30s+)
  ├── 生态最丰富(loader/plugin)
  └── Next.js 默认使用

Vite(开发快):
  ├── ESM-based:利用浏览器原生 ES Module
  ├── 冷启动快(毫秒级)
  ├── HMR 快(只更新变化的模块)
  └── 生产用 Rollup 打包

Turbopack(Next.js 新引擎):
  ├── Rust 编写,极致性能
  ├── 增量计算,只重新编译变化的部分
  └── Next.js 14+ 实验性支持

构建优化实战

// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer({
  // 1. SWC 编译(比 Babel 快 20 倍)
  // Next.js 默认使用,无需配置

  // 2. 图片优化
  images: {
    formats: ['image/avif', 'image/webp'],
  },

  // 3. 包导入优化
  experimental: {
    optimizePackageImports: ['lucide-react', 'date-fns'],
  },

  // 4. 压缩
  swcMinify: true,  // SWC 压缩(比 Terser 快 7 倍)
})

三、模块化方案

ESM vs CommonJS

// CommonJS(Node.js 传统方式)
const fs = require('fs')          // 运行时加载
module.exports = { readFile }     // 运行时导出
// 特点:同步加载、无法 Tree Shaking

// ES Module(现代标准)
import { readFile } from 'fs'     // 编译时静态分析
export { readFile }               // 编译时确定导出
// 特点:异步加载、支持 Tree Shaking、浏览器原生支持

Path Alias(路径别名)

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@/components/*": ["src/components/*"],
      "@/lib/*": ["src/lib/*"],
      "@/types/*": ["src/types/*"]
    }
  }
}
// ❌ 相对路径地狱
import { Button } from '../../../components/ui/Button'

// ✅ 路径别名
import { Button } from '@/components/ui/Button'

四、Monorepo

当项目变大、团队变多时,需要 Monorepo 来管理多个包:

my-project/
├── apps/
│   ├── web/           # Next.js 主站
│   ├── admin/         # 后台管理
│   └── mobile/        # React Native
│
├── packages/
│   ├── ui/            # 共享 UI 组件库
│   ├── utils/         # 共享工具函数
│   ├── config/        # 共享配置(ESLint、TS)
│   └── types/         # 共享类型定义
│
├── package.json       # 根 package.json
├── pnpm-workspace.yaml
└── turbo.json         # Turborepo 配置
# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
// turbo.json — 构建编排
{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],  // 先构建依赖的包
      "outputs": [".next/**", "dist/**"]
    },
    "lint": {},
    "test": {
      "dependsOn": ["build"]
    }
  }
}
Turborepo 的优势:
  1. 任务编排:自动分析依赖关系,并行构建
  2. 缓存:相同输入不重新构建
  3. 远程缓存:团队共享构建缓存

  首次构建: 全部 packages 编译 → 120s
  第二次(缓存命中): → 3s 🚀

五、微前端

当单体前端太大、需要多团队独立开发部署时:

                    主应用(基座)
                   ┌────────────────────────────────┐
                   │  Header  │  Sidebar             │
                   │──────────┤                      │
                   │          │  ┌──────────────────┐│
                   │  导航    │  │  子应用 A (React)  ││
                   │          │  │  团队 A 独立开发    ││
                   │          │  │  独立部署           ││
                   │          │  └──────────────────┘│
                   │          │  ┌──────────────────┐│
                   │          │  │  子应用 B (Vue)    ││
                   │          │  │  团队 B 独立开发    ││
                   │          │  └──────────────────┘│
                   └────────────────────────────────┘

主流方案

qiankun(蚂蚁):
  基于 single-spa,JS 沙箱 + 样式隔离
  适合:已有多个独立项目需要整合

Module Federation(Webpack 5):
  模块级共享,运行时加载远程模块
  适合:共享组件/模块,不是完整应用

iframe:
  最简单的隔离方案
  适合:对隔离性要求高、交互少的场景

Web Components:
  浏览器原生组件封装
  适合:跨框架的组件共享

六、环境管理

.env                  # 所有环境共享
.env.local            # 本地覆盖(不提交 git)
.env.development      # 开发环境
.env.production       # 生产环境
.env.staging          # 预发环境
# .env.development
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_ENV=development

# .env.production
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_ENV=production
// 类型安全的环境变量
// env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    NEXT_PUBLIC_API_URL: string
    NEXT_PUBLIC_ENV: 'development' | 'staging' | 'production'
    DATABASE_URL: string
    ANTHROPIC_API_KEY: string
  }
}

七、错误监控

// 全局错误捕获
// app/error.tsx(Next.js App Router 错误边界)
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  useEffect(() => {
    // 上报错误到监控平台(Sentry / 自建)
    reportError(error)
  }, [error])

  return (
    <div>
      <h2>出错了</h2>
      <button onClick={reset}>重试</button>
    </div>
  )
}
// 性能 + 错误统一上报
class Monitor {
  // 资源加载错误
  initResourceError() {
    window.addEventListener('error', (e) => {
      if (e.target instanceof HTMLElement) {
        this.report({
          type: 'resource_error',
          url: (e.target as any).src || (e.target as any).href,
          tagName: e.target.tagName,
        })
      }
    }, true)
  }

  // Promise 未捕获错误
  initUnhandledRejection() {
    window.addEventListener('unhandledrejection', (e) => {
      this.report({
        type: 'unhandled_rejection',
        message: e.reason?.message || String(e.reason),
      })
    })
  }

  // 接口错误
  initFetchError() {
    const originalFetch = window.fetch
    window.fetch = async (...args) => {
      const start = Date.now()
      try {
        const res = await originalFetch(...args)
        if (!res.ok) {
          this.report({
            type: 'api_error',
            url: args[0] as string,
            status: res.status,
            duration: Date.now() - start,
          })
        }
        return res
      } catch (err) {
        this.report({ type: 'network_error', url: args[0] as string })
        throw err
      }
    }
  }
}

工程化检查清单

代码规范:
  □ ESLint + Prettier 配置
  □ TypeScript strict 模式
  □ husky + lint-staged 自动检查
  □ commitlint 提交规范

构建:
  □ 代码分割 + Tree Shaking
  □ 图片/字体优化
  □ Bundle 体积分析
  □ 环境变量管理

流程:
  □ Git 分支策略
  □ Code Review 流程
  □ CI/CD 自动化
  □ 版本管理(Changelog)

质量:
  □ 单元测试覆盖率
  □ E2E 测试关键路径
  □ 错误监控上报
  □ 性能监控指标

总结

前端工程化的目标是让团队能够高效、高质量、可持续地交付产品。它不是一次性的配置,而是随着项目和团队的成长不断演进的体系。从代码规范起步,逐步完善构建、测试、监控,最终形成完整的工程化闭环。