第 9 章 · 前端工程化

前九章是「写页面」,这一章是「做产品」:多人协作要版本管理,依赖几十个包要 npm,几百个模块要打包,上线要构建部署,风格统一要规范工具。本章按「依赖 → 打包 → 部署 → 规范」走完前端工程的完整链路,这也是本站自己(Next.js + npm)每天都在用的链路。

9.1 npm:依赖的家

npm install              # 按 package.json + lock 文件装全依赖
npm install vue          # 新增运行时依赖
npm run dev              # 执行 scripts 里定义的命令

package.json 是项目的身份证:dependencies(运行时要用的)与 devDependencies(只在构建/开发时用的,如 vite、ESLint)分册登记;版本号 ^3.4.0 是 semver 范围(允许 3.x 内升级),~ 只许补丁,写死版本是纪律。lock 文件必须提交:它锁定整棵依赖树的精确版本,保证「我的机器能跑」变成「所有人的机器都一样地跑」(没有 lock,团队各装各的,bug 复现变成玄学)。

包管理器三兄弟:npm(官方默认)、yarn、pnpm。pnpm 用硬链接 + 内容寻址把 node_modules 去重(100 个项目共享同一份 vue),磁盘和安装速度双杀,新项目我无脑推 pnpm。npm install 那句老话「删 node_modules 重装大法」之所以有效,正是因为依赖树状态复杂,lock 文件 + 统一包管理器是根治法。

9.2 打包:webpack 到 vite 的换代

浏览器只认少量文件类型,而你的源码是几百个 ESM 模块 + 各种资源,打包工具把它们编译、合并、压缩成高效的静态产物:

// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({ plugins: [vue()] });
webpack(上一代)vite(现主流)
开发期全量打包成 bundle 再启动不打包:原生 ESM 按需加载
冷启动项目大时几分钟秒级
热更新 HMR越大越慢只重编译改动的模块,毫秒级
生产构建自有体系底层用 Rollup

vite 快的原因正是第 7 章的模块知识兑现:开发时浏览器原生支持 ESM,vite 只按请求「现场翻译」你正在访问的那个模块(用 esbuild,Go 写的,快一个量级),没有全量打包这一步。第 7 章说「ESM 静态分析可 tree-shaking」,这里兑现:生产构建把没用到的导出摇掉,产物小一圈。

9.3 构建产物与部署

源码 ──build──> dist/ ──部署──> 静态服务器 / CDN

npm run build 产出的 dist 是纯静态文件,关键机制是内容哈希文件名(index.9f3a2b.css):内容不变文件名不变,配合第 6 章的强缓存,浏览器「改了才重新下载」;内容变了文件名变,天然缓存失效。这个「哈希文件名 + 长缓存 + 短缓存 HTML」组合是静态资源缓存的行业答案。

部署的本质就是「把 dist 放到可访问的 URL 下」:GitHub Pages / Vercel(推代码自动部署,个人项目首选)/ 云服务器的 Nginx 静态托管(本站就是 Node 进程跑的 Next.js,稍复杂但同理)。CI/CD 是它的自动化版:push 触发流水线,自动 install → lint → build → 部署,人只管写代码。

9.4 规范三件套:风格统一与提前抓错

  • ESLint:静态检查,抓未使用变量、危险用法(第 4 章 == 的 eqeqeq 规则就在这);
  • Prettier:格式化,团队不再为分号和缩进吵架(保存即格式化);
  • TypeScript:给 JS 加类型,编译期抓错(第 4 章「引用共享」这类 bug,TS 能在写的时候就标红),现在是大项目的默认选择。

三件套用 husky + lint-staged 挂在 git 提交钩子上,只检查本次改动文件,「不合格的代码进不了仓库」。这个「把规范自动化到提交环节」的思路,比「开会约定、靠自觉」有效一百倍。

常见错误

  • 不提交 lock 文件。团队环境漂移、CI 复现不了本地的 bug;lock 是源码的一部分。
  • node_modules 进了 git。几百 MB 的垃圾;.gitignore 里写好(node_modules、dist 必在名单)。
  • hash 文件名配错缓存策略。HTML 文件名不能 hash(入口要固定),CSS/JS 才 hash;HTML 用协商缓存,静态资源用强缓存,配反了要么永不更新要么每次全量拉。
  • 依赖升级全凭手滑。npm update 大版本直接破坏性变更;升级看 changelog、跑测试、小步走。

练习

  1. 动手:用 npm create vite@latest 起一个 Vue 或 React 项目,通读 package.json 和 vite.config,辨认 dependencies/devDependencies/scripts 的每个条目。
  2. 动手:npm run build 后进 dist/,观察文件名哈希、压缩后的 JS(格式化看源码对应关系);改一行源码重 build,对比哪些文件名变了(内容寻址的实证)。
  3. 动手:把项目推上 GitHub,接 Vercel 或 GitHub Pages,体验 push 即部署;再看仓库的 CI 日志,对照 9.3 的流水线。
  4. 思考:本站的 package.json 里 next 和 react 在 dependencies、eslint 在 devDependencies。为什么 Next.js 项目的产物不是纯静态(dist 而是 .next 服务端产物)?顺着这个问题理解「SSR(第 10 章)为什么让部署从『放文件』变成『跑进程』」。