前言
有段时间我自己很喜欢用 vite + pnpm。但是最近自己着手新项目时, 被一个问题困扰了好久, 后来逐步排查问题时才发现自己对 pnpm + vite 的运行机制并不是很了解
就花了一些时间上网搜查和使用 ai 得到了一些笔记, 最后又使用 ai 反复追问和熔炼得到了这篇文章, 还是有不少收获哇, 记录一下
一、前置基石:ESM vs Bundle vs CJS
在理解 Vite 之前,必须先弄懂三种模块化形态的区别。现代前端工程化,本质上就是在这三者之间做转换和取舍。
| 特性维度 | ESM (ES Modules) | Bundle (打包产物) | CJS (CommonJS) |
|---|---|---|---|
| 加载方式 | 静态解析,异步按需加载 | 一次性同步加载 | 运行时阻塞式加载 |
| 输出绑定 | 实时绑定(引用) | 值拷贝 | 值拷贝(浅拷贝) |
| 浏览器支持 | ✅ 现代浏览器原生支持 | ✅ 任何环境兼容 | ❌ 不支持(缺 require 等环境变量) |
| Tree Shaking | ✅ 完美支持(基于静态解析) | ✅ 构建时优化剔除 | ❌ 极难实现(动态加载) |
| 核心场景 | Vite 开发环境 / 现代浏览器 | 生产环境部署 | Node.js 环境 / 传统服务端 |
一句话总结:ESM 是未来的标准(按需快),CJS 是 Node 的历史包袱,而 Bundle 则是当前网络环境妥协下的生产最优解。
二、Vite 双重人格:开发与生产的完整链路
很多人觉得 Vite 快,是因为它“不打包”。这只说对了一半。Vite 的精髓在于开发环境 No-Bundle,生产环境硬核 Bundle。
1. pnpm run dev:极速启动与热更新
开发环境的核心诉求是快。Vite 启动时不做全量打包,而是把工作交给了现代浏览器。
- 依赖预构建(Esbuild 登场):首次启动时,Vite 会用 Esbuild 极速把
node_modules里的 CJS/UMD 依赖全转成 ESM,并合并碎文件(缓存到.vite/),解决浏览器发几百个网络请求卡死的问题。 - 请求拦截与即时编译:当浏览器请求
/src/App.vue时,Vite 服务器拦截请求,实时把 Vue 编译成浏览器能懂的 JS/ESM 返回。不请求,不编译。 - HMR 热更新:改了哪个文件,就通过 WebSocket 通知浏览器重新请求那一个模块,做到毫秒级更新。
2. pnpm run build:极致性能与产物压缩
生产环境的核心诉求是小和稳。因为浏览器的 HTTP 请求是有成本的,不能像开发环境那样发散。
- 依赖图构建:从入口(
index.html)开始,静态分析所有import,建立依赖关系。 - 全量编译(Rollup 登场):进行语法降级、Tree-Shaking(剔除无用代码)、CSS 提取与图片压缩。
- 产物优化:生成带有内容哈希值(如
index-a1b2c3d4.js)的产物,配合 CDN 实现永久缓存;将第三方库(vendor)单独分包。
3. 开发 vs 生产 核心差异概览
| 维度 | dev (开发环境) | build (生产环境) |
|---|---|---|
| 底层引擎 | Esbuild (极速预构建) + 原生 ESM | Rollup (精细化全量打包) |
| 网络请求 | 模块数量 ≈ HTTP 请求数量 | 合并压缩,固定少量请求 (2-5个) |
| 热更新 | ✅ 支持 HMR | ❌ 静态文件,无热更新 |
三、原生 ESM 这么好,为什么生产环境还要打包?
既然现代浏览器都支持 <script type="module"> 了,为什么 build 时还要大费周章合并文件?
- 网络性能瓶颈:开发环境下,一个页面加载 100 个 JS 模块没问题(本地 localhost)。但在真实网络中,100 次 HTTP 请求会引发严重的网络阻塞。打包将请求缩减到 1-5 个。
- Tree Shaking 与代码压缩:原生 ESM 只是加载机制,不管代码体积。打包器能静态分析你的代码,把没用到的
unusedFunction删掉,并混淆变量名,极大减小体积。 - 兼容性兜底:并不是所有用户的浏览器都支持最新的 ES 语法,打包器(结合 Babel/SWC)能帮你做语法降级。
四、pnpm 的核心魔法:为什么它是最佳搭档?
Vite 解决了构建速度,而 pnpm 解决了依赖管理的痛点。
- 全局存储 + 硬链接(省空间 + 极速安装)
传统的 npm/yarn 在每个项目里都塞一个完整的 node_modules。如果你有 10 个 React 项目,你的硬盘上就有 10 份 React。
pnpm 策略:全电脑共用一个全局 Store (~/.pnpm-store)。项目里的 node_modules 只是指向全局 Store 的硬链接。安装速度极快,且几乎不占额外磁盘空间。
- 严格隔离(消灭幽灵依赖)
npm 默认采用扁平化结构,导致你可以 import 在 package.json 中根本没声明过的深层依赖(幽灵依赖)。一旦深层依赖版本变了,项目直接跑不起来。
pnpm 策略:强制非扁平化。没在 package.json 里声明的包,代码里绝对 import 不到,从根源斩断依赖混乱。
结语
现代前端工程化的演进,其实就是一部权衡的历史。
- 开发时,我们拥抱 ESM + Vite,享受“不打包”带来的秒级启动和极速热更新;
- 生产时,我们信任 Rollup + Bundle,用构建时间的拉长换取用户首屏加载的极致性能;
- 底层架构上,我们选择 pnpm,保障磁盘效率与依赖安全。
弄懂了这条链路,以后再遇到模块解析报错、打包体积过大或者热更新失效的问题,就能一眼看穿本质了!
ai 真是太好用了.jpg
