首页  |  学习总览  |  ← 上一章:浏览器与网络 阶段四 · 第 4 章,共 10 章
阶段四 · 预计 80 小时

工程化与构建 —— 现代开发的基建

学会写代码只是第一步,学会「工程化地写代码」才是职业分水岭。本章覆盖 Node 基础、包管理、Git、构建原理、Vite/Webpack、代码规范、TypeScript 与 CI/CD,让你从「能写」升级到「能产出可维护的工程」。

1Node.js:前端跑在服务器上的那个 JS

Node 让 JS 跳出浏览器,也带来了完整的前端工具链。

1.1 Node 是什么

  • Node = V8 引擎(Google 的 JS 引擎)+ 操作系统能力(文件、网络、进程)。
  • 它让 JS 能在服务器、命令行运行 → 前端工具(打包、测试、脚手架)全部用它。
  • Node 是单线程 + 事件驱动 + 非阻塞 I/O:擅长高并发 I/O(读写文件、网络请求),不擅长 CPU 密集计算。
// 最简单的 Node 脚本:node hello.js 运行
const fs = require("fs");           // 文件系统模块
const path = require("path");       // 路径模块
console.log("你好,Node!");
console.log(__dirname);             // 当前文件所在目录

1.2 常用内置模块

模块作用常用 API
fs文件读写readFile / writeFile / mkdir(都支持 Promise 版本)
path路径处理join / resolve / basename / extname
http / https发起 HTTP 请求前端一般用 fetch 或 axios 封装
os系统信息platform / cpus / totalmem
process进程信息env(环境变量)/ argv(命令行参数)
// 读取文件(现代写法:Promise)
const fs = require("fs/promises");
async function readConfig() {
  const data = await fs.readFile("./config.json", "utf-8");
  return JSON.parse(data);
}
2包管理:npm / pnpm / yarn

依赖管理是工程的命脉,版本策略与锁文件必须理解。

2.1 语义化版本(SemVer)

// 版本号格式:主版本.次版本.补丁版本(如 4.17.21)
// 主版本:不兼容的破坏性变更(升级要谨慎)
// 次版本:向后兼容的新功能
// 补丁版本:向后兼容的 bug 修复

// package.json 里的版本范围写法:
"react": "^18.2.0"     // ^:允许 18.x.x(次版本内的更新),不允许 19.x
"react": "~18.2.0"     // ~:只允许 18.2.x(补丁更新)
"react": "18.2.0"      // 精确锁定
"react": "*"           // 任意版本(千万别用)
💡 锁文件 package-lock.json / pnpm-lock.yaml 锁文件把每个依赖的精确版本固定下来,保证「任何人、任何时间安装都得到完全一致的依赖树」。必须提交到 Git!删了锁文件 = 给自己埋雷。

2.2 npm 常用命令

npm init -y              // 初始化项目(生成 package.json)
npm install // npm i          // 安装 package.json 里的所有依赖
npm i react              // 安装并写入 dependencies(生产依赖)
npm i -D typescript      // -D:写入 devDependencies(开发依赖,构建产物不包含)
npm run dev              // 运行 scripts 里的 dev 脚本
npm outdated             // 查看依赖是否有新版本
npm audit                // 检查依赖安全漏洞
⚠️ 包管理器的选择 新项目优先 pnpm:① 硬链接共享依赖,省磁盘;② 幽灵依赖隔离,杜绝「能用但没声明」的隐患;③ 安装快。团队统一用一个,别混用(混用会导致 node_modules 结构混乱)。
3Git:版本控制基本功

Git 是协作的基石,下面这些命令是生存必备。

3.1 日常工作流(每天重复)

// 拉取最新 → 建分支 → 改代码 → 提交 → 推送 → 提 PR
git pull                        // 拉取远程更新
git checkout -b feature/login   // 新建并切换分支(命名:feature/ 前缀)
git add .                       // 暂存改动
git commit -m "feat: 新增登录功能"  // 提交(遵循提交规范)
git push -u origin feature/login // 推送并建立关联
// 然后在远程(GitHub/GitLab)创建 Pull Request,走代码评审

3.2 分支模型与合并策略

命令效果适用
git merge把分支合并进来,保留合并记录默认、团队协作主流
git rebase把提交「搬到」目标分支顶部,历史线性整洁个人分支整理、保持历史清晰
git cherry-pick挑单个提交应用到当前分支紧急修复提版本
// 冲突解决:合并时双方改了同一处 → 打开文件找 <<<<<<< 标记,手工保留需要的部分
<<<<<<< HEAD
当前分支的内容
=======
被合并分支的内容
>>>>>>> feature/login
// 解决后:git add . && git commit(不要慌,冲突是常态)
💡 提交规范(Conventional Commits,团队必备) feat: 新功能、fix: 修复、docs: 文档、refactor: 重构、perf: 性能、test: 测试、chore: 杂务。格式:类型(范围): 描述,如 fix(login): 修复密码框回车不提交。配合 husky 可以在提交前自动校验。
4模块化演进与打包原理

理解「为什么需要打包」,才能理解 Vite 和 Webpack 的设计。

4.1 模块化的四代演进

时代方案问题
远古多个 script 标签,全局变量变量污染、依赖顺序靠人肉
第一代IIFE(立即执行函数)+ 全局挂载手动管理依赖,还是乱
第二代CommonJS(Node)/ AMD(浏览器,require.js)浏览器无法直接用 CommonJS
现代ES Modules(ESM)浏览器原生支持 + 静态分析 → 打包优化
⚠️ 为什么浏览器不能直接用 CommonJS? CommonJS 的 require 是运行时同步加载,浏览器加载本地文件是异步的,无法同步阻塞等待——所以浏览器端模块标准必须走 ESM(import 声明是静态的,浏览器可以预先解析)。打包器的职责之一就是把 ESM/CommonJS 统一处理成浏览器能跑的形式。

4.2 打包的核心流程(面试必答)

步骤做什么产出
① 入口分析从入口文件开始
② 依赖解析递归解析 import/require,生成依赖图依赖关系图
③ 转换编译loader 处理:TS→JS、JSX→JS、CSS、图片转换后的模块代码
④ 合并打包按规则合并成 chunkbundle 文件
⑤ 代码分割动态 import 拆成独立 chunk,按需加载多个小文件
⑥ Tree Shaking删掉「未被使用的导出」更小的体积
💡 Tree Shaking 的前提(背下来) ① 必须用 ESM(import/export 静态可分析,CommonJS 动态 require 分析不了);② 生产模式(开启压缩与副作用标记);③ 代码无副作用(或 package.json 里声明 "sideEffects": false)。
5Vite:新一代开发利器

Vite 为什么快?理解它的两大核心机制。

5.1 开发时:原生 ESM,按需编译

Webpack 开发时要把所有模块打包成一个 bundle 才能启动——项目越大越慢(「启动 30 秒」梗的来源)。Vite 的思路完全不同:

  • 开发服务器不打包:直接利用浏览器原生 ESM,你 import 谁,就只编译谁。
  • 按需编译:只有浏览器请求到某个模块时才编译它,所以启动几乎是瞬时的。
  • 依赖预构建:第三方库(几百个文件的大库)用 esbuild 预打包成 ESM,减少浏览器请求数。

5.2 热更新(HMR)的原理

  • 传统:修改一个文件 → 整个页面刷新(状态丢失,体验差)。
  • Vite:模块变化时,WebSocket 通知浏览器,只替换变化的模块,页面状态保留
  • 实现基础:ESM 的 import 是模块级依赖,可以精确找到「谁依赖了它」,只更新相关部分。
✅ 生产构建用 Rollup 的原因 Vite 开发用 esbuild(极快),但 esbuild 的产物优化(代码分割、Tree Shaking 精细度)不如 Rollup 成熟,所以生产构建交给 Rollup。这是「开发快 + 产物优」的组合拳。
6Webpack:老牌全能打包器

存量项目大量使用 Webpack,理解它的核心概念才能维护改造。

6.1 四大核心概念

概念作用例子
Entry入口,打包起点entry: './src/main.js'
Output产物输出配置output: { filename: '[name].[hash].js' }
Loader转换模块内容(文件级)babel-loader(JS)、ts-loader、css-loader、style-loader
Plugin打包全流程的扩展点(构建级)HtmlWebpackPlugin、MiniCssExtractPlugin
// webpack.config.js 最小示例
module.exports = {
  mode: "development",                    // development / production
  entry: "./src/index.js",
  output: { path: __dirname + "/dist", filename: "bundle.js" },
  module: {
    rules: [
      { test: /\.js$/, use: "babel-loader", exclude: /node_modules/ },
      { test: /\.css$/, use: ["style-loader", "css-loader"] },
    ],
  },
  plugins: [new HtmlWebpackPlugin({ template: "./index.html" })],
  devServer: { port: 3000, proxy: { "/api": "http://localhost:8080" } },
};
⚠️ loader 的执行顺序 loader 从后往前执行(数组的最后一个先跑):css-loader 先把 CSS 转成 JS 模块,style-loader 再把它注入页面。记不清就调试看报错——报错会指出是哪个 loader 的问题。
7代码规范:从「人治」到「机制治」

规范不是束缚,是让团队协作零摩擦的基础设施。

7.1 四大工具的分工

工具职责典型规则
ESLint代码质量与规范(找错误与坏味道)未使用变量、禁止 console、强制分号
Prettier代码格式化(统一风格,不判断对错)缩进 2 空格、单引号、行宽 100
EditorConfig编辑器级别的基础配置文件编码、缩进方式(跨 IDE 统一)
husky + lint-stagedGit 钩子:提交前自动检查提交时只检查暂存的文件,又快又准
// package.json 里的钩子配置(husky + lint-staged)
{
  "lint-staged": {
    "*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"],
    "*.{css,html,md}": ["prettier --write"]
  }
}
// 效果:每次 git commit 前自动修复格式,坏代码进不了仓库
✅ 规范落地的正确姿势 ① 先接 ESLint + Prettier,规则用成熟预设(eslint-config-airbnb / standard 风格);② 提交钩子卡住「没格式化的代码」;③ CI 里再跑一遍全量检查作为兜底;④ 有争议的规则团队投票定,定了就不改。
8TypeScript:带类型的 JavaScript

TS 是当前大厂前端的标配,从入门到熟练是这一节的目标。

8.1 为什么需要 TS

  • JS 是弱类型:参数传错类型不报错,运行时才炸,排查成本高。
  • TS = JS + 类型系统:编译期就能发现错误,代码即文档,重构更安全。
  • TS 是「编译到 JS」的超集,浏览器不认识 TS,最终会编译成 JS。
// 基础类型标注
let age: number = 25;
let name: string = "张三";
let isDone: boolean = true;
let list: number[] = [1, 2, 3];

// 接口:定义对象结构
interface User {
  id: number;
  name: string;
  email?: string;        // ?: 可选属性
  readonly createdAt: Date;  // readonly:只读
}
function greet(user: User): string {   // 参数类型 + 返回值类型
  return `你好,${user.name}`;
}

8.2 泛型(进架构必备)

// 泛型:类型像参数一样传进去,让函数复用且保持类型安全
function identity<T>(value: T): T {
  return value;
}
const num = identity<number>(42);   // T = number
const str = identity("hello");      // 自动推断 T = string

// 经典应用:API 响应包装
interface ApiResponse<T> {
  code: number;
  data: T;
  message: string;
}
// 用一次,处处类型安全:
const res = await get<User[]>("/api/users");  // res.data 是 User[]
⚠️ TS 的坑 ① TS 的类型检查在编译期,运行时依旧有风险(any 滥用会退化回 JS);② 与第三方 JS 库配合需要类型声明(@types/xxx);③ 别为了「类型好看」写过度复杂的类型体操,可维护性优先。
9CI/CD:自动化流水线

把「检查、构建、测试、部署」变成自动的流水线,质量与效率兼得。

9.1 概念:CI 与 CD

  • CI(持续集成):每次代码合并自动跑检查——类型、Lint、单测、构建。问题在合并前就被拦住。
  • CD(持续交付/部署):通过检查后自动构建产物、部署到测试/生产环境。
// GitHub Actions 示例:.github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci                 // 按锁文件精确安装
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test
      - run: npm run build
      // 全部通过 → 绿色 ✅;任一失败 → 红色 ❌ 拦住合并
💡 质量门禁的三个层次 ① 本地(husky 钩子,最快最省);② CI(合并前全量检查,兜底);③ 线上监控(发布后观测,最后的防线)。层层设防,坏代码越来越难上线。
10实战项目(通关必做)

本章动手为主,两个项目做完即通关。

项目一:从零搭建一个规范化的 Vite 工程

要求:① npm create vite@latest 创建项目并理解每个文件;② 接入 ESLint + Prettier + husky + lint-staged,提交不规范代码会被拦;③ 配置路径别名(@/ 指向 src);④ 配置代理解决开发期跨域;⑤ 加 TypeScript 严格模式;⑥ 配好提交规范(commitlint)。验收:随便写点代码,观察「提交时自动修复、commit 信息被校验」的效果。

项目二:手写一个迷你打包器

用 Node 写一个 100 行左右的打包器:① 读取入口文件;② 用正则或简单的解析找到 import;③ 递归生成依赖图;④ 把模块包装成函数,生成一个可运行的 bundle.js。做完你会彻底理解「打包」到底在做什么。

11通关自检题

工程化知识以理解 + 实操为准。

1. Node 为什么适合做 I/O 密集型任务,不适合 CPU 密集型?

2. ^18.2.0 和 ~18.2.0 和 18.2.0 的区别?锁文件为什么要提交?

3. merge 和 rebase 的区别?什么时候用哪个?

4. 说出一套提交规范(类型列表 + 格式)

5. 模块化的演进历史?为什么浏览器端最终采用 ESM?

6. 打包的完整流程?Tree Shaking 的三个前提是什么?

7. Vite 开发时为什么快?生产构建为什么用 Rollup?

8. HMR 的原理是什么?

9. Webpack 的 loader 和 plugin 的区别?loader 的执行顺序?

10. ESLint 和 Prettier 的分工?husky 解决什么问题?

11. TS 的 interface 和 type 的区别?泛型解决了什么问题?

12. CI 和 CD 的区别?质量门禁分哪三层?

✅ 全部通关? 工程化地基已就位。下一阶段进入框架的世界——React 的渲染原理、Hooks、组件设计,这是你成为「框架级」开发者的关键一跃。