obsidian-computer
前端识记AI总结

工程化与性能优化

模块 12 · 工程化与性能优化

AI生成最后更新 2026年8月21日

模块 12 · 工程化与性能优化

这块是「中高级」和「初级」的分水岭。会用脚手架不算会,能解释为什么快、为什么慢、怎么调才算。 每一节都有 🔬 动手项 —— 在 fe-bootcamp/react/ 这个 Vite 项目里就能做。


12.1 构建工具原理 ⭐⭐

Webpack 的核心概念(五个)

概念作用
entry从哪开始找依赖
output打包结果放哪、叫什么([contenthash] 就在这配)
loader转换单个文件(babel-loader、css-loader)—— 从右往左 / 从下往上执行
plugin在构建生命周期的钩子上做事(HtmlWebpackPlugin、DefinePlugin)
modedevelopment / production,决定默认开哪些优化

Webpack 构建流程:初始化参数 → 创建 Compiler → 从 entry 开始递归 → 用 loader 转换每个模块 → 记录依赖关系构建依赖图 → 组装 Chunk → 输出。

🤔 loader 和 plugin 的区别一句话:loader 是文件转换器,plugin 是生命周期钩子上的扩展。

Vite 为什么快 ⭐⭐

开发时生产构建
Webpack先打包整个 bundle 再启服务 → 项目越大启动越慢webpack
Vite不打包,直接用浏览器原生 ESM,按需编译请求到的模块Rollup(Vite 5+ 可选 Rolldown)

Vite 快的两个关键:

  1. 依赖预构建:node_modules 里的第三方库用 esbuild(Go 写的,比 JS 快 10-100 倍)预打包成 ESM,且强缓存
  2. 源码按需转换:你的业务代码只在浏览器请求到它时才转换(所以启动时间和项目大小基本无关)

🤔 追问:Vite 开发和生产用两套工具(esbuild + Rollup),会不会有行为不一致?(会,这是它被诟病的点,所以生产构建前一定要跑一次 build + preview 验证) 🤔 为什么 esbuild 不直接用于生产构建?(代码分割、CSS 处理等高级能力不如 Rollup 成熟)

🔬 实验

cd fe-bootcamp/react
npm run build -- --mode production

然后:

  1. 看 dist/assets/ 里文件名的 hash,改一行代码重新 build,观察哪些文件 hash 变了、哪些没变
  2. 打开 dist/index.html,看 Vite 生成的 <script type="module" crossorigin> 和 modulepreload
  3. npm run dev 后打开 Network,把 filter 设成 JS,看到的是一个个源文件而不是一个 bundle —— 这就是 no-bundle

12.2 Tree Shaking ⭐

前提:ESM 的静态结构(模块 09 讲过)。CommonJS 的 require 是运行时的,摇不掉。

为什么有时候摇不干净:

  1. 模块有副作用(顶层执行了代码、修改了全局、import 了 css)→ 打包器不敢删 → package.json 里声明 "sideEffects": false 或 ["*.css"]
  2. 你用了 import * as _ from 'lodash' 或 import _ from 'lodash'(CJS 包) → 改用 lodash-es,或 import debounce from 'lodash/debounce'
  3. 类的方法很难被摇掉(打包器难以证明没被用到)

🔬 实验

在 react/src/App.jsx 里 import { format } from 'date-fns' 只用一个函数,build 后看产物体积; 再改成 import * as dateFns from 'date-fns',对比体积差异。


12.3 代码分割与懒加载 ⭐

三个层次:

  1. 路由级:const Home = lazy(() => import('./Home')) —— 收益最大,优先做
  2. 组件级:重型组件(图表、编辑器、地图)按需加载
  3. 依赖级:vendor chunk 单独拆,利用长期缓存

配套的资源提示:

<link rel="preload" href="critical.js" as="script">   <!-- 当前页面马上要用 -->
<link rel="prefetch" href="next-page.js">              <!-- 未来可能用,空闲时下载 -->
<link rel="preconnect" href="https://api.example.com"> <!-- 提前建连接 -->
<link rel="dns-prefetch" href="https://cdn.example.com">

🤔 preload 和 prefetch 的优先级差别?滥用 preload 会怎样?(抢占带宽,反而拖慢关键资源)

🔬 实验

React 项目的 Lab 09 里已经有 lazy 的例子。跑 npm run build,看它是不是被拆成了独立 chunk。


12.4 Babel 与 AST

Babel 三步:parse(源码 → AST)→ transform(遍历并修改 AST,插件在这里工作)→ generate(AST → 代码)。

关键概念:

  • @babel/preset-env + browserslist —— 根据目标浏览器决定要转译哪些语法
  • polyfill vs 语法转译 —— 箭头函数是语法(Babel 转),Promise/Array.prototype.includes 是API(需要 core-js polyfill)
  • useBuiltIns: 'usage' —— 按实际用到的 API 自动引入 polyfill,比 entry 更省体积

🤔 为什么现在很多项目不再需要 Babel?(esbuild/SWC 更快;且现代浏览器基本都支持 ES2020+,只有需要兼容老浏览器时才要它) 🔬 去 astexplorer.net 粘一段代码,看它的 AST 长什么样 —— 理解「代码即数据」,ESLint / Prettier / 各种 codemod 都建立在这上面。


12.5 HMR(热更新)原理

  1. 构建工具启动一个 WebSocket 服务
  2. 文件变化 → 重新编译变化的模块
  3. 通过 WS 通知浏览器:「模块 X 更新了」
  4. 浏览器端的 HMR runtime 请求新模块,执行模块的 accept 回调替换旧模块
  5. 如果没有模块声明能处理这次更新 → 冒泡到顶 → 整页刷新

React 的 Fast Refresh 在此基础上额外做到了:替换组件实现的同时保留组件的 state。

🤔 为什么改一个组件不丢 state,改一个非组件的导出就会整页刷新?(Fast Refresh 只能安全地处理「只导出 React 组件」的模块)


12.6 性能优化清单 ⭐⭐(面试可以照这个框架答)

面试问「你做过哪些性能优化」,按加载链路分层答,比罗列技巧强一百倍:

1. 网络层

  • HTTP/2、CDN、开启 gzip/brotli 压缩
  • 缓存策略:静态资源 hash + 长缓存,HTML no-cache(模块 10.1)
  • 减少 DNS/TCP 开销:preconnect、域名收敛

2. 资源层

  • 体积:Tree Shaking、代码分割、按需引入、rollup-plugin-visualizer 分析包体积
  • 图片:WebP/AVIF、响应式图片 srcset、懒加载 loading="lazy"、雪碧图(HTTP/2 后价值下降)
  • 字体:font-display: swap|optional、子集化、preload

3. 渲染层(模块 06)

  • 关键 CSS 内联,非关键 CSS 异步
  • JS 用 defer/async
  • 避免布局抖动、用 transform/opacity 做动画
  • 长列表虚拟化
  • 骨架屏 / SSR / SSG 优化首屏感知

4. 运行时(模块 01 + React Lab 08/09)

  • 长任务切片、Web Worker
  • 防抖节流
  • React:memo 三件套、并发特性

5. 度量

  • Lighthouse / Performance 面板 / RUM 上报 Core Web Vitals
  • 先测量再优化

🤔 面试加分:说出每个手段的代价。比如「SSR 提升首屏,但增加了服务器成本和复杂度,还要处理同构代码的水合问题」。


12.7 Monorepo 与包管理(了解即可)

  • npm / yarn / pnpm 的区别:pnpm 用硬链接 + 符号链接的内容寻址存储,省磁盘、快、且天然避免幽灵依赖(你没在 package.json 里声明的包,pnpm 下 import 不到)
  • lock 文件的意义:锁定整棵依赖树的确切版本,保证任何机器上装出来一样
  • 语义化版本:^1.2.3 允许 1.x.x,~1.2.3 允许 1.2.x,1.2.3 精确
  • Monorepo:pnpm workspace / Turborepo / Nx —— 多包共享依赖、统一构建缓存

🤔 dependencies vs devDependencies vs peerDependencies 分别什么时候用?


12.8 TypeScript 常考点(前端现在基本是必选项)

主题要点
interface vs typeinterface 可声明合并、只能描述对象;type 能写联合/交叉/条件/映射类型。组件 props 用哪个都行,公共库倾向 interface
泛型<T>、约束 T extends X、默认值 T = Y
工具类型Partial Required Pick Omit Record ReturnType Parameters Awaited NonNullable —— 能手写实现最好
类型收窄typeof / in / instanceof / 字面量判别 / 自定义类型守卫 x is Y
any / unknown / neverany 关闭检查;unknown 是安全的 any(用前必须收窄);never 表示不可能
断言as 和 ! 是「相信我」,是逃生舱不是解决方案
协变逆变函数参数是逆变的(strictFunctionTypes)—— 冷门但会问
条件类型T extends U ? A : B、infer 提取类型

✍️ 手写工具类型练习(面试常考):

type MyPartial<T>  = { [K in keyof T]?: T[K] }
type MyPick<T, K extends keyof T> = { [P in K]: T[P] }
type MyOmit<T, K>  = ???            // 提示:用 Exclude
type MyReturnType<T> = ???          // 提示:用 infer
type DeepPartial<T> = ???           // 递归

🔬 去 typescript-exercises 或 type-challenges 做前 10 道 easy 题。 建议:等 React 主线项目做到 Level 6 之后,把整个 Focus 项目改写成 TS —— 这比单独刷类型体操有用得多。


结业检查

  • 能解释 Vite 为什么开发时快,以及它和 Webpack 的取舍
  • 能说清 loader 和 plugin 的区别,并举例
  • 知道自己项目的包体积构成(跑一次 visualizer)
  • 能按「网络 → 资源 → 渲染 → 运行时 → 度量」五层框架讲性能优化
  • 能手写 Partial / Pick / Omit / ReturnType

反向链接(0)

还没有笔记链接到这里。在 Obsidian 里用 [[笔记名]] 引用它,这里就会出现,关系图谱上也会多出一条连线。

评论

关系图谱

工程化与性能优化网络React快速总结This指向TypeScript 快速上手Web components前端小记手写Promise踩坑记录前端识记AI总结

On this page