一、整体结构概览
Webpack 打包代码示例:
javascript
(function (t, n) {
...
i = function () {
return function (t) {
var e = {}; // 模块缓存
function n(r) {...} // 模块加载器 __webpack_require__
// 工具函数定义 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
n.m = t; // 模块表(每个模块对应一个函数)
n.c = e; // 缓存对象
n.d = function(...) // 定义导出
n.r = function(...) // 标记为 ESModule
n.t = function(...) // 兼容 CommonJS/ESModule 模块转换工具
n.n = function(...) // 获取默认导出(兼容 require().default)
n.o = function(...) // 判断对象是否有某属性(hasOwnProperty)
n.p = ""; // 公共路径(publicPath)
// 工具函数定义 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
return n(n.s = 4); // 启动主模块(模块 ID = 4)
}
}([...]);
})
二、详细解释每一部分
1)var e = {}
:模块缓存对象
javascript
e = {
"./src/math.js": {
i: "./src/math.js",
l: true,
exports: { add: [Function] }
}
}
- webpack 保证一个模块只加载一次(类似 Node.js 的
require.cache
)
2)function n(r)
:模块加载器(等价于 __webpack_require__
)
javascript
function n(r) {
if (e[r]) return e[r].exports;
// 创建空模块
var o = e[r] = {
i: r,
l: false,
exports: {}
};
// 执行模块体函数(写到 exports 上)
t[r].call(o.exports, o, o.exports, n);
o.l = true;
return o.exports;
}
作用:
-
首次加载模块:执行模块函数、保存 exports
-
重复加载:直接返回缓存
流程是:
-
先看模块缓存(e)
-
没有缓存就初始化模块对象
-
执行模块函数**
t[r].call(...)
**(等于把源码执行) -
设置 loaded
-
返回
exports
3)n.m = t
javascript
n.m = t;
- 保存模块表(
t
是一个数组或对象,里面存放的是每个模块的函数体)
4)n.c = e
javascript
n.c = e;
- 保存模块缓存,调试或工具使用(你可以在 DevTools 中访问**
__webpack_require__.c
**)
5)n.d
:定义模块导出属性(defineProperty)
javascript
n.d = function (t, e, r) {
if (!n.o(t, e)) {
Object.defineProperty(t, e, {
enumerable: true,
get: r
});
}
}
作用:
- 用于
export { foo }
的实现,把foo
注册为访问器属性
6)n.r
:标记模块为 ESModule
javascript
n.r = function (t) {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(t, Symbol.toStringTag, {
value: "Module"
});
}
Object.defineProperty(t, "__esModule", {
value: true
});
}
作用:
-
给导出对象加
__esModule: true
-
让其它模块在
import
时能识别它是 ESModule
7)n.t
:模块兼容处理(很关键!)
javascript
n.t = function (t, mode) {
if (mode & 1) t = n(t); // 递归加载模块
if (mode & 8) return t;
if (mode & 4 && typeof t === 'object' && t && t.__esModule) return t;
var r = Object.create(null);
n.r(r);
Object.defineProperty(r, "default", { enumerable: true, value: t });
if (mode & 2 && typeof t !== "string") {
for (var key in t) {
n.d(r, key, function (k) { return t[k] }.bind(null, key));
}
}
return r;
}
用途:
mode | 含义 |
---|---|
1 | 加载模块(递归) |
2 | 混合命名导出 |
4 | 是 ESModule |
8 | 直接返回原始值 |
这个函数是 处理各种导入模式 的万能桥梁。
8)n.n
:获取默认导出
javascript
n.n = function (t) {
var getter = t && t.__esModule ? function () { return t.default } : function () { return t };
n.d(getter, "a", getter);
return getter;
}
作用:
- CommonJS 写法中**
require()
** 得到的是对象,统一处理**.default
**提取
9)n.o
:判断属性是否存在
javascript
n.o = function (t, e) {
return Object.prototype.hasOwnProperty.call(t, e);
}
10)n.p = ""
:publicPath
-
用于配置静态资源路径前缀(比如图片路径前加 CDN 地址)
-
n.p = "/static/"
这样的配置很常见
三、入口执行模块
javascript
return n(n.s = 4)
-
表示:执行模块 ID 为 4 的模块
-
这是**
entry: './src/index.js'
**编译后分配的模块 ID(数字 4)
四、模块表结构(t = [...]
)
看到的后面这部分:
javascript
[function (t, e, n) {...}, function (t, e, n) {...}, ...]
就是每个模块对应的函数,比如:
-
index.js
编译成了模块 ID 4 -
**
math.js
**编译成了模块 ID 0 -
每个函数接收的参数是标准的:
module, exports, require
五、逆向分析
如果在分析 webpack 打包文件,只要识别出以下代码块:
javascript
var e = {}, function n(r) {...}, n.m = ..., n.r = ..., n.d = ...
说明它是 Webpack 构建的典型打包结构,可以:
-
找出 **
n.m
**就能列出所有模块 -
手动调用**
n("模块 ID")
** 得到模块对象 -
把
n.m[x].toString()
打印出源码 -
Hook 某个模块函数(例如加密逻辑)
六、总结(完整映射表)
代码 | 作用 |
---|---|
n() |
模块加载器(require) |
n.m |
所有模块函数集合(模块表) |
n.c |
模块缓存 |
n.d |
定义模块导出(导出 getter) |
n.r |
标记模块为 ESModule |
n.t |
兼容 CommonJS 与 ESModule |
n.n |
提取默认导出 |
n.o |
hasOwnProperty 判断 |
n.p |
publicPath 路径前缀 |
n(n.s = X) |
启动入口模块(通常就是 index.js) |