Webpack 打包代码示例:
(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 = {}
:模块缓存对象
e = {
"./src/math.js": {
i: "./src/math.js",
l: true,
exports: { add: [Function] }
}
}
require.cache
)2)function n(r)
:模块加载器(等价于 __webpack_require__
)
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
n.m = t;
t
是一个数组或对象,里面存放的是每个模块的函数体)4)n.c = e
n.c = e;
__webpack_require__.c
)5)n.d
:定义模块导出属性(defineProperty)
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
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
:模块兼容处理(很关键!)
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
:获取默认导出
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
:判断属性是否存在
n.o = function (t, e) {
return Object.prototype.hasOwnProperty.call(t, e);
}
10)n.p = ""
:publicPath
用于配置静态资源路径前缀(比如图片路径前加 CDN 地址)
n.p = "/static/"
这样的配置很常见
return n(n.s = 4)
表示:执行模块 ID 为 4 的模块
这是 entry: './src/index.js'
编译后分配的模块 ID(数字 4)
t = [...]
)看到的后面这部分:
[function (t, e, n) {...}, function (t, e, n) {...}, ...]
就是每个模块对应的函数,比如:
index.js
编译成了模块 ID 4
math.js
编译成了模块 ID 0
每个函数接收的参数是标准的:module, exports, require
如果在分析 webpack 打包文件,只要识别出以下代码块:
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) |