技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTETC

前端工程化实践

所属馆藏
Other
文件格式
Markdown
原始路径
Other/13-前端工程化实践
本文目录11 个章节
  1. 一、前端工程化到底解决什么问题
  2. 二、原文内容与今天的对应关系
  3. 三、先统一项目基础
  4. 四、代码质量:自动化约束,而不是口头约定
  5. 五、模块化、构建和体积控制
  6. 六、依赖管理与 Monorepo
  7. 七、测试体系:从便宜到昂贵
  8. 八、CI/CD 与预览环境
  9. 九、本地开发与团队协作
  10. 十、实施顺序建议
  11. 参考资料

前端工程化实践

Category(分类): Other Status: 已整理

本文在保留原文“从代码规范、构建、缓存、部署到 CI/CD 建立工程体系”这一主线的基础上,补充现代前端项目的依赖管理、测试、预览环境和可观测性实践。

原始链接(旧路径,站点可能已调整):前端工程化实践——山月行

可访问镜像:前端工程化实践

一、前端工程化到底解决什么问题

前端工程化不是把项目配置得越来越复杂,也不是工具越多越先进。它要解决的是一组长期、重复且容易出错的问题:

  • 多人协作时如何保持代码风格和接口约定一致;
  • 如何让依赖安装、开发、构建和发布可重复;
  • 如何在合并代码前尽早发现类型、逻辑、兼容性和安全问题;
  • 如何让每个分支都能被单独预览和验收;
  • 如何知道一次发布是否真的改善了用户体验,并能在出问题时回滚。

一个可落地的工程化闭环通常是:

需求与设计
  ↓
代码与分支协作
  ↓
本地开发与自动检查
  ↓
Pull Request / Merge Request
  ↓
CI:安装、检查、测试、构建、扫描
  ↓
预览环境 → 灰度/生产发布
  ↓
监控、反馈、回滚与持续改进

工程化的目标是降低变化成本,而不是阻止变化。小团队可以只建立最小闭环,大团队再逐步增加共享配置、组件库、预览环境和发布平台。

二、原文内容与今天的对应关系

原文工程化系列主要覆盖以下主题,这些方向今天仍然有效:

  • JavaScript 压缩、Tree Shaking 和打包体积分析;
  • Webpack 等构建工具及代码分割;
  • HTTP 缓存控制和 CDN;
  • ESLint、代码规范和提交约束;
  • 依赖安装速度、锁文件和 CI 缓存;
  • Docker、Nginx、CI/CD、特性分支环境和灰度发布;
  • 本地 HTTPS、图片处理、日志和异常监控。

需要更新的是:新项目通常优先使用框架官方方案、Vite、Rspack 或其他现代构建工具,不必为了追求“最新”迁移稳定的 Webpack 项目;npm inpm ci、pnpm 和 Yarn 也应根据团队统一的包管理策略使用,不能在同一个项目中随意混用。

三、先统一项目基础

1. 固定运行时和包管理器

团队应明确 Node.js、包管理器和构建工具的支持范围,并把约定写入仓库:

  • 使用 .nvmrc.node-version 或 CI 镜像固定 Node 主版本;
  • package.jsonengines 中声明支持范围;
  • 使用 packageManager 字段记录团队批准的包管理器和版本;
  • 提交且只维护一份锁文件,例如使用 pnpm 就提交 pnpm-lock.yaml
  • CI 使用不可变安装,例如 pnpm install --frozen-lockfilenpm ci
  • 升级 Node、包管理器和锁文件时通过 PR 评审,而不是让每个人本地自行升级。

示例:

{
  "engines": {
    "node": ">=20 <25"
  },
  "packageManager": "pnpm@<团队批准的固定版本>"
}

尖括号只是占位符,实际项目必须替换成已经验证过的具体版本。engines 是提示和约束工具是否检查它,并不能替代 CI 对运行时版本的固定。

2. 统一常用脚本

脚本名称应稳定、可发现,避免每个项目都使用不同命令:

{
  "scripts": {
    "dev": "vite",
    "lint": "eslint .",
    "typecheck": "vue-tsc --noEmit",
    "test": "vitest run",
    "test:e2e": "playwright test",
    "build": "vite build",
    "preview": "vite preview"
  }
}

不同框架的命令会有差异,例如 Nuxt、Next.js 和基于 Vite 的项目不一定使用完全相同的脚本。重要的是同一个脚本在本地、CI 和发布平台上含义一致。

3. 配置文件和环境变量

构建时环境变量会被替换进前端产物。只要变量会进入浏览器包、HTML 或公开运行时配置,就不能把它当作秘密。数据库密码、云服务私钥、JWT 签名密钥和第三方管理 Token 必须放在服务端或 CI Secret 中。

建议区分:

  • 构建时配置:构建时注入,通常会进入产物;
  • 运行时公开配置:通过服务端或同源配置接口提供给浏览器,只能放公开值;
  • 服务端秘密:只在服务端、Secret Manager 或 KMS 中使用。

对配置做 schema 校验比在业务代码中到处读取字符串更可靠。缺少必须配置时,应在启动或构建阶段失败,而不是上线后才出现隐蔽错误。

四、代码质量:自动化约束,而不是口头约定

1. ESLint、格式化和类型检查

现代 ESLint 项目优先采用 Flat Config(通常为 eslint.config.jseslint.config.mjseslint.config.ts),并按实际技术栈选择 Vue、React、TypeScript 等插件。不要把别人的规则文件完整复制过来;每条规则都应能说明它解决的风险。

通常可以组合:

  • ESLint:发现潜在错误和约定问题;
  • Prettier:统一格式,不承担复杂语义检查;
  • Stylelint:检查 CSS、SCSS 等样式;
  • TypeScript:检查类型关系和 API 契约;
  • vue-tsc 或框架官方类型检查命令:补充模板类型检查;
  • markdownlint:在团队需要时检查文档。

不要只在提交前运行全部检查,否则反馈可能太慢。编辑器即时提示、提交前轻量检查和 CI 完整检查应分层安排。

2. Git Hook 和提交规范

Husky、lint-staged、Commitlint 或 Lefthook 等工具可以在提交前运行格式化、Lint 或提交信息校验。它们只是开发体验工具,本地 Hook 可以被跳过,不能作为安全边界;必须阻断的检查应在 CI 和分支保护中再次执行。

提交信息可以采用 Conventional Commits,例如:

feat: 增加文章搜索
fix: 修复移动端图片溢出
chore: 升级构建依赖

提交规范不是目的。对于小团队,如果提交规则带来的维护成本超过收益,可以采用更简单的约定;对于需要自动生成变更日志、发布包的团队,规范化提交会更有价值。

3. 代码评审

PR/MR 不应只检查格式,还应关注:

  • 需求和异常状态是否覆盖;
  • 权限、输入校验和敏感数据是否安全;
  • 组件是否真的可复用,是否引入了不必要的抽象;
  • 大型依赖、重复代码和包体积是否合理;
  • 测试是否覆盖历史缺陷和关键路径;
  • 是否需要迁移说明、文档或监控指标。

把可自动化的内容交给工具,把业务判断和架构取舍留给评审者。

五、模块化、构建和体积控制

1. 构建工具的选择

Webpack、Vite、Rspack、Rollup、Parcel 以及框架自带的构建工具各有适用场景:

  • 应用项目需要开发服务器、热更新、代码分割和资源处理;
  • 库项目更关心 ESM/CJS 输出、类型声明、Tree Shaking 和兼容性;
  • SSR/SSG 项目应优先使用框架官方构建流程;
  • 已经稳定运行的老项目,迁移前应先测量构建时间、开发体验和产物变化。

不要把“压缩后代码看不懂”当成安全措施。压缩、混淆和 WebAssembly 都不能隐藏交付到浏览器的秘密,权限和核心规则必须在服务端校验。

2. 代码分割和资源加载

合理的代码分割可以降低首屏 JavaScript,但分割过度也会增加请求、解析和调度开销。应结合真实路由和用户行为决定:

  • 路由级懒加载;
  • 大型编辑器、图表和地图等重组件按需加载;
  • 对不会进入首屏的内容使用动态导入;
  • 通过构建分析工具检查重复依赖和初始包体积;
  • 对关键 CSS、字体和图片只做有依据的预加载。

Tree Shaking 依赖 ESM 的静态结构和正确的副作用声明,不能保证任意 CommonJS 包都被完美删除。应通过产物分析确认优化是否生效。

3. 资源缓存

静态资源使用内容哈希文件名时,可以设置较长的缓存时间:

Cache-Control: public, max-age=31536000, immutable

HTML 入口和运行时配置通常需要短缓存或重新验证,不能把 immutable 盲目加到所有响应。删除或修改文件时,应通过新的哈希文件名、发布清单和 CDN 缓存策略保证新旧版本切换可控。

ETagLast-ModifiedIf-None-Match 适合做缓存验证,但它们不是让资源“永远不过期”的办法。个性化响应应谨慎使用共享缓存,必要时使用 private

六、依赖管理与 Monorepo

1. 依赖不是越多越好

引入依赖前至少评估:

  • 是否真的减少了代码和维护成本;
  • 包的体积、模块格式和浏览器兼容性;
  • 维护活跃度、漏洞、许可证和供应链风险;
  • 是否有类型声明、文档和可替换方案;
  • 未来升级和出现问题时由谁负责。

不要为了统一而保留无人维护的内部工具,也不要在多个项目中复制一份稍有差异的配置而无人升级。

2. 工作区和共享包

pnpm、npm 和 Yarn 都提供 Workspace 能力。Monorepo 适合共享组件、设计令牌、ESLint 配置、API 类型和工具包,但它也会带来构建边界、发布版本、权限和 CI 缓存等复杂度。

共享包至少应有:

  • 清晰的入口和导出边界;
  • 类型声明、文档和测试;
  • 独立的版本与变更记录;
  • 明确的运行时和 peer dependency;
  • 消费者升级失败时的回滚方案。

可以使用 Changesets 等工具管理多包变更,但不要把 Monorepo 当作所有项目的默认答案。多个项目没有稳定共享关系时,独立仓库反而更简单。

3. 供应链安全

CI 中可以加入依赖漏洞、许可证、机密信息和恶意脚本扫描。锁文件能固定解析结果,但不能保证依赖永远安全;应关注维护者变更、发布包变化、安装脚本和 CI Token 权限。

生产构建尽量在干净环境完成,并保留构建日志、SBOM 或依赖清单。Source map 有助于定位线上错误,但可能包含源码和路径信息,应上传到受控的错误监控平台,不要默认公开。

七、测试体系:从便宜到昂贵

测试策略不应只追求覆盖率数字,而要保护用户最重要、最容易回归的行为:

层级适合检查的内容常见工具
静态检查语法、风格、类型和明显错误ESLint、TypeScript、Prettier
单元测试纯函数、状态转换和边界Vitest、Jest
组件测试交互、状态和可访问性Testing Library、Storybook
视觉测试主题和关键页面回归Storybook、Chromatic 等
端到端测试登录、搜索、支付等关键流程Playwright、Cypress
契约测试前后端接口兼容性OpenAPI、JSON Schema 等
线上监控真实错误、性能和转化RUM、错误监控、日志平台

测试应关注用户可见行为,避免大量断言私有实现细节。端到端测试不必覆盖所有页面,但应覆盖高价值路径和曾经出过问题的流程。

八、CI/CD 与预览环境

1. CI 的最小闭环

每个 PR/MR 至少可以执行:

  1. 使用固定 Node 和包管理器;
  2. 按锁文件安装依赖;
  3. 运行 Lint、类型检查和单元测试;
  4. 构建生产产物并检查体积;
  5. 执行依赖、许可证和机密扫描;
  6. 保存测试报告和必要的构建产物。

一个简化的 GitHub Actions 示例:

name: verify

on:
  pull_request:
  push:
    branches: [main]

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: <团队批准的固定版本>
      - uses: actions/setup-node@v4
        with:
          node-version: <团队批准的固定版本>
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm typecheck
      - run: pnpm test
      - run: pnpm build

示例中的 Node、pnpm、Action 版本和脚本名都需要按项目实际情况替换、验证;如果仓库没有 typechecktestbuild 脚本,应先补齐或改成真实命令。Actions、GitLab CI、Jenkins 等平台都可以实现同样的流程;平台不是重点,重点是检查可重复、权限最小和失败可诊断。

2. 特性分支和预览环境

原文提到的多特性分支环境今天仍然实用:每个 PR 生成一个临时预览地址,产品、设计、测试和后端可以在合并前验收。

实践中要注意:

  • 分支名必须经过安全的 slug 化,不能直接拼成域名或命令;
  • 预览环境应设置过期清理,避免资源无限增长;
  • 使用脱敏数据和最小权限,不要把生产 Token 带入预览环境;
  • 前后端接口、数据库和第三方服务要明确绑定关系;
  • 预览地址应有访问控制,避免把内部功能公开到互联网;
  • 每个预览构建应关联提交 SHA,方便定位和删除。

3. 发布、灰度和回滚

推荐“构建一次,按环境发布”:CI 生成不可变产物,预览、灰度和生产尽量使用同一份产物。运行时配置可以由服务端注入,但不要因此在每个环境重新编译一份不同代码。

发布流程应包含:

  • 制品校验和版本记录;
  • 预览环境和冒烟检查;
  • 分批、灰度或 feature flag;
  • 错误率、接口失败率、LCP、INP 和关键转化监控;
  • 保留上一份可用版本并能快速回滚;
  • 事故后记录原因、影响范围和改进事项。

九、本地开发与团队协作

本地环境应尽量接近 CI,但不必完全复制生产:

  • 使用 .env.example 说明配置,不提交真实秘密;
  • 使用 Mock、契约或本地服务减少对共享测试环境的依赖;
  • 本地需要 HTTPS 时使用受信任的开发证书,不要关闭浏览器安全校验;
  • 对跨域、Cookie、代理和环境变量写成文档;
  • README 说明首次安装、常用脚本、故障排查和发布流程。

好的工程化也包括沟通:需求、接口契约、设计稿、缺陷、技术决策和事故记录都应有明确入口。文档可以减少重复沟通,但不能成为拒绝必要讨论的借口。

十、实施顺序建议

第 1 阶段:先让项目可重复

  • 固定 Node、包管理器和锁文件;
  • 统一 dev/lint/test/build 脚本;
  • 建立 ESLint、格式化和类型检查;
  • PR 必须通过 CI;
  • 补充 README、环境变量和发布说明。

第 2 阶段:减少重复劳动

  • 共享配置和工具包;
  • 组件库、设计令牌和 Storybook;
  • OpenAPI/JSON Schema 契约;
  • 单元测试、关键流程端到端测试;
  • 预览环境和依赖安全扫描。

第 3 阶段:提升交付和运营能力

  • 构建产物、CDN 和缓存策略;
  • 灰度、feature flag 和自动回滚;
  • RUM、错误监控和性能预算;
  • Monorepo、多包版本和 Changesets;
  • 平台模板、脚手架和内部文档站。

每增加一个工具,都要回答三个问题:它解决了哪个真实问题?谁维护?失败时如何回滚?回答不清楚时,先不要引入。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS