跳到主要内容

产物与代码分割

生产构建要把源码变成 可长期缓存、体积可控、按路由懒加载 的静态文件。工具怎么转译见 构建工具与 Vite;模块格式为何影响摇树见 模块系统。

解决什么问题​

痛点产物策略
首屏下载 5MB 一个 index.js拆 chunk:路由级 import()、把 vendor 分开
发版后用户仍跑旧逻辑文件名带 content hash,HTML 引用新名字
删了没用的导出,包一点没小不是 ESM / 有 side effect / 被 sideEffects 标错
线上报错看不懂sourcemap 上传到监控,不要默认把 map 公开到生产 CDN(看安全策略)

一句话:HTML 是入口清单;带 hash 的资源可以缓存很久;chunk 边界决定每次改动让谁缓存失效。

产物里有什么​

Vite 默认 dist/ 大致是:

dist/
index.html 引用带 hash 的 js/css
assets/index-xxxx.js
assets/index-xxxx.css
assets/vendor-xxxx.js (若被拆出)
文件缓存直觉
index.html往往不长期缓存或协商缓存;每次发布要换到新 hash
assets/*-hash.js内容变才变 hash,可 Cache-Control: immutable
放 public/ 的文件原样拷贝,不经过打包;适合 robots.txt、不会 import 的静态件

public/ 里的路径不要指望 tree-shake;大图、PDF 放对地方。

Tree-shaking​

打包器删除 证明没被用到的导出。前提:

  1. 源码是 ESM(import/export)
  2. 模块没有无法分析的副作用,或 package.json 的 sideEffects 标对
{
"sideEffects": ["*.css", "./src/polyfill.js"]
}

标 sideEffects: false 等于声称「只导入副作用也可以删整文件」。标错会把 polyfill / CSS 摇掉,运行时才炸。

默认导出、export * from、动态 import(变量) 都会削弱分析。库作者想被摇树:提供 ESM、避免在顶层乱写副作用。

代码分割​

const Page = lazy(() => import("./pages/Settings.tsx"));

import() 是运行时边界,打包器据此切 async chunk。用户进设置页才下载。

策略用途
路由级动态 import最常见,按页面切
手动 manualChunks把 react 等稳定依赖打进 vendor,业务改动不让它失效
组件级再切很重的编辑器、图表;切太碎会变成请求瀑布

经验:先按路由切;vendor 是否独立用打包分析(rollup-plugin-visualizer / Vite 的 bundle 可视化)看,不要一上来十几个 HTTP/1 小文件——HTTP/2 下仍要权衡压缩字典和解析成本。

Hash 与缓存​

index-a1b2c3.js → 改源码 → index-d4e5f7.js
index.html 里的 <script src> 跟着变

坑:

  • 只缓存了 HTML,资源 hash 变了用户仍拿旧 HTML → 404;HTML 要用短缓存或部署时确保新 HTML 先/同时上线
  • CDN 与源站缓存策略不一致
  • Service Worker 另有缓存,发版要有更新策略

Sourcemap​

场景建议
本地 build 排查sourcemap: true
生产map 上传 Sentry 等,CDN 不提供 .map 或鉴权

map 含源码路径甚至源码,相当于把仓库公开一截。

要点​

  1. HTML 引用 hash 资源;hash 资源可长缓存
  2. 摇树依赖 ESM + 正确的 sideEffects
  3. import() 是切包的主手段,从路由开始
  4. public/ 不打包,大文件放错目录会直接打进站点根
  5. sourcemap 是调试资产,不是默认公开文件

面试速记​

  1. 为什么文件名要带 hash? 内容变则 URL 变,才能让浏览器/CDN 长缓存且发版立即换新。
  2. Tree-shaking 是什么? 删除未被引用的 ESM 导出;CJS 很难做。
  3. 代码分割怎么做? 动态 import()(路由/重功能);可选 vendor 拆包。
  4. public 和 src/assets 差在哪? public 原样拷贝;src 经打包、可 hash、可参与模块图。
  5. 首屏很大怎么下手? 看分析图:先砍同步依赖,再按路由懒加载,而不是先开混淆档。