好的,我来帮你系统梳理一下 Webpack 的实现原理。Webpack 本质上是一个 模块打包工具,它的核心思想就是:
👉 一切资源皆模块,通过 依赖图(dependency graph) 把项目中各个模块串起来,最终打包成浏览器可运行的 bundle。
1. 核心流程
Webpack 的运行可以分为 初始化 → 编译 → 输出 三个阶段:
(1) 初始化
- 读取
webpack.config.js配置文件。 - 初始化 Compiler 对象(全局唯一,负责调度整个打包过程)。
- 注册各种插件(plugins),把钩子函数挂载到生命周期。
(2) 编译(核心)
-
从入口开始
Webpack 根据entry找到入口文件,调用loader对代码做转换(比如 TS → JS,CSS → JS)。 -
递归解析依赖
- 使用 AST(抽象语法树)解析代码(依赖
@babel/parser或 Acorn 等库)。 - 找到
require/import语句。 - 把依赖的文件路径加入依赖图,继续递归编译。
- 使用 AST(抽象语法树)解析代码(依赖
-
构建依赖图(Dependency Graph)
每个模块都会成为图中的一个节点,节点之间通过依赖关系连接起来。 -
Loader 处理
- Loader 本质是一个函数,接收源代码字符串,返回处理后的代码。
- 可以链式执行,比如:
scss-loader → css-loader → style-loader。
-
插件介入
插件通过tapable提供的钩子系统,在不同生命周期介入,例如:compilation:模块编译阶段emit:输出文件前done:构建完成
(3) 输出
- 根据依赖图,Webpack 把所有模块打包成一个或多个 bundle。
- bundle 内部会有一个 模块加载器(runtime) ,负责模拟
require的功能,保证模块能在浏览器中按需加载。
2. 核心原理拆解
🔹 (1) 依赖收集
- Webpack 不认识
import,需要 AST 分析。 - 通过 AST 遍历,找到依赖路径,递归处理。
例子(简化版 AST 解析):
import a from './a.js';
console.log(a);
Webpack 解析后会知道:main.js → a.js。
🔹 (2) 模块包装
Webpack 会把每个模块包装成一个函数,放到对象里,类似:
{
'./src/index.js': function(require, module, exports) {
const a = require('./a.js');
console.log(a);
},
'./src/a.js': function(require, module, exports) {
module.exports = 'hello webpack';
}
}
🔹 (3) 运行时代码(runtime)
打包后的 bundle 里有个简易版的 require:
(function(modules) {
function __webpack_require__(moduleId) {
const module = { exports: {} };
modules[moduleId].call(module.exports, __webpack_require__, module, module.exports);
return module.exports;
}
return __webpack_require__('./src/index.js');
})({
'./src/index.js': function(require, module, exports) { ... },
'./src/a.js': function(require, module, exports) { ... }
});
这样浏览器里就能像 Node.js 一样运行模块了。
3. Loader 与 Plugin 原理
🔹 Loader
- 本质是一个函数,输入源码字符串,输出新代码。
- 支持链式调用,顺序是 从右到左。
module.exports = function(source) {
return source.replace('var', 'let');
}
🔹 Plugin
- 基于
tapable的发布订阅模式。 - 插件通过钩子在不同阶段执行。
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tap('MyPlugin', compilation => {
console.log('emit 阶段:生成文件前');
});
}
}
4. 总结一句话
Webpack 的核心原理就是:从入口文件出发 → 构建依赖图 → 把模块打包成一个 bundle,并通过 loader 转换资源、plugin 插入扩展能力,最终在浏览器里用 runtime 来模拟模块系统。