写在前面
2023年前端行业的内卷加剧,29岁似乎已经提前退休在家待业,为了能够重新返岗就业,不得不去卷下源码,虽然很枯燥但是收获还是有的。第一次写技术文章为了记录下自己的理解。大家且看且轻喷。
工程准备
- 新建了一个工程化的项目如下结构 (关于怎么去建立这些东西就不用说了,文章重点是在于编译后的源码分析)
| - node_modules
| - dist
|- main.js
|- index.html
| - src
| - utils.js
| - index.js
| - package.json
| - webpack.config.js
| - yarn.lock
// utils.js
const printHello = () => {
console.log("hello");
};
const key = "this is a webpack build result analysis";
export { printHello };
export default key;
// index.js
import key, { printHello } from "./utils";
function component() {
const element = document.createElement("div");
printHello();
element.innerHTML = key;
return element;
}
document.body.appendChild(component());
编译后的结果
(() => {
// webpackBootstrap
"use strict";
var __webpack_modules__ = {
"./src/utils.js": (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
default: () => __WEBPACK_DEFAULT_EXPORT__,
printHello: () => printHello,
});
const printHello = () => {
console.log("hello");
};
const key = "this is a webpack build result analysis";
const __WEBPACK_DEFAULT_EXPORT__ = key;
},
};
// The module cache
var __webpack_module_cache__ = {};
// The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// Create a new module (and put it into the cache)
var module = (__webpack_module_cache__[moduleId] = {
// no module.id needed
// no module.loaded needed
exports: {},
});
// Execute the module function
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// Return the exports of the module
return module.exports;
}
// 第一个IIFE
(() => {
// define getter functions for harmony exports
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();
/* webpack/runtime/hasOwnProperty shorthand */
// 第二个IIFE
(() => {
__webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
})();
/* webpack/runtime/make namespace object */
// 第三个IIFE
(() => {
// define __esModule on exports
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
}
Object.defineProperty(exports, "__esModule", { value: true });
};
})();
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
// 第四个IIFE
(() => {
__webpack_require__.r(__webpack_exports__);
var _utils__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/utils.js");
function component() {
const element = document.createElement("div");
(0, _utils__WEBPACK_IMPORTED_MODULE_0__.printHello)();
element.innerHTML = _utils__WEBPACK_IMPORTED_MODULE_0__["default"];
return element;
}
document.body.appendChild(component());
})();
})();
编译结果分析
- 通过 webpack 编译后会用一个 IIFE 包裹住 包裹的原因是为了防止变量的全局污染,创造了一个函数的作用域
- 首先可以看到 webpack 定义一个 webpack__modules 对象,对象的 key 值是模块文件的相对路径,value 值是一些带有参数模块的方法,该方法的中调用 webpack__require.d 方法传入的是我们自己定义的模块具名导出和默认导出的方法函数。
- 我们再来看看 webpack_require 这个方法,该方法传入了一个 moduleId,从编译结果的注释中可以看到第四个IIFE,中的代码可以看出 moduleId 就是模块引入的相对路径,也就是 import printHello from "./src/utils.js" 在 webpack 看来就是用 webpack_require 实现了自己的模块化导入。
- 再深入到 webpack_require 的方法中,该方法中的基本操作就是定义一个缓存的对象,先去判断这个对象中有没有之前存过的 moduleId 它的值是一个对象 {exports:{}},如果没有就重新定义一个变量来保存。所以我们可以想到是这个 exports 对象里面的值是后面通过添加进来的
- webpack_require 中的 webpack_modules[moduleId](module, module.exports, webpack_require) 是在匹配 webpack__modules 中的 key 返回的方法然后执行,传入的是刚刚定义过的 exports 空对象,进入到返回的函数体中执行了 webpack_require.d 这个方法,这个方法是定义在 webpack_require 方法上的方法(挂载在__webpack_require__的构造函数上的)
- 再来看看 webpack_require.d 做了什么事情,也就是第一个IIFE,参数是一个 exports和 definition,这个 definition 是什么呢,其实是一个对象我们定义的具名导出和默认导出的方法对象, 这个IIFE执行结果是在 exports 上挂载具名导出和默认导出的对象方法,该方法在definition上做了一个循环,做了一个判断用到了 webpack_require.o 这个方法,这个方法的作用是判断是不是原型对象上的key 。
- if(webpack_require.o(definition, key) && !webpack_require.o(exports, key)) 这个意思是只遍历definition对象上的键值然后exports 不存在相应的键值 才会添加到 exports 上,所以我们可以想到的是 module.exports = { default: () => WEBPACK_DEFAULT_EXPORT, printHello: () => printHello, } 应该是这个对象。
- var utils__WEBPACK_IMPORTED_MODULE_0_ 这个变量拿到的就应该是 module.exports这个对象
- 然后通过对象的方式去调用这个方法。
总结
- webpack 是通过 webpack_require 和_ webpack_modules 实现了自己的模块化,通过对象调用的方式执行对应的方法。
写在后面
通过读源码知道了 webpack 是怎么样去组织模块化的。自己用于记录学习过程,同时也希望能够帮助到你们。😊