打包器原理:模块图与 bundle 结构
本节目标
- 理解打包器(Webpack/Vite)把「多个模块」合成「一个 bundle」的原理
- 看懂 bundle 里「模块数组 + 启动函数」的结构
- 建立「源码模块 → 模块图 → 单文件」的心智
打包器做的事:从入口出发,按 import 画出模块图,把每个模块变成「一个函数」塞进数组,最后用一个小启动函数把它们串起来。
// 运行环境:Node.js 16+(这是「打包产物」的极简示意,非真实配置)
// 源码:main.js import { add } from './math.js'
// 打包后(示意):
const __modules = {
'./math.js': function (module, exports, require) {
module.exports = { add: (a, b) => a + b }
},
'./main.js': function (module, exports, require) {
const { add } = require('./math.js')
console.log(add(1, 2))
}
}
function require(id) {
const m = { exports: {} }
__modules[id](m, m.exports, require)
return m.exports
}
require('./main.js')价值:浏览器只需下载「一个 JS 文件」,不用几十次请求;还能做压缩、Tree Shaking。Vite 开发时用原生 ESM(快),生产时用 Rollup 打包(小)。
名词解释
打包器(Bundler):把「项目里分散的多个模块文件」分析依赖、合并成「少量(通常一个)浏览器能直接跑的文件」的工具,如 Webpack、Rollup、Vite(底层用 Rollup)。它解决「浏览器原生加载几十个模块太慢」的问题,并顺带做压缩、Tree Shaking。
模块图(再次出现):打包器第一步就是「从入口按 import 画出完整依赖图」,然后「每个模块→一个函数」塞进一个大对象,启动函数按图执行。所以「模块图」既是引擎加载 ESM 的概念,也是打包器的核心数据结构——两者一脉相承。
课后练习
练习 1:为什么「开发时用 Vite 原生 ESM、生产时 Rollup 打包」?
答案:开发时要「改一行立刻生效」,原生 ESM 让浏览器按需加载未打包的源文件,启动快、热更新快;生产时要「体积小、请求少」,Rollup 把全部模块打包压缩成一两个文件。两者各取所长:开发求快,上线求小。
练习 2:bundle 里「模块数组 + 启动函数」和 Webpack 产物(jsm 实战课)结构一样吗?
答案:思想完全一致:都是「依赖图 → 模块函数数组 → 加载器启动」。具体语法不同(Vite/Rollup 用 ESM 风格、Webpack 用 CJS 风格
__webpack_require__),但「集装箱装模块、启动器取货」的骨架相同。看懂一个就懂全部。
总结
打包器原理拆穿了就是「画图 + 装盒 + 启动」,一点都不神秘。我的观点:理解它最大的价值是破除对构建工具的敬畏——你写的 import 不是魔法,打包器只是把它们变成「数组里的函数 + 一个加载器」。一旦建立这个心智,遇到「打包后某模块没执行」「路径解析不对」这类问题,你能直接想到「是模块图哪条边断了」而不是瞎猜。现代前端离不开打包器,但它是「 servant 不是 master」:你掌控源码和依赖,它只负责搬运组装。