在邂逅webpack时就有关于提到webpack的模块化,这里前情回顾一下:
webpack默认支持各种模块化开发,比如ES Mouele、CommonJS、AMD等。(新版本的浏览器默认只支持ES Module,而旧版本是不支持所有模块化的。有了webpack我们就可以自行选择想要的模块化方案,无需担心浏览器支不支持。)
而且更牛的是,webpack还让我们的commonJs可以使用ES Module,EsModule也可以使用commonJS。它们是可以混着用的。那这些是怎么实现的呢?
这里我们将尝试看一下打包出来后的js源码,就可以最直观地感受到webpack是怎样实现模块化的了。
Webpack实现CommonJS原理
首先我们写一段使用了commonJS的语法的代码,然后使用webpack打包出来,就可以看到webpack实现commonJS模块化后转化的代码了。(webpack配置打包mode模式为development,默认的production模式打包会改变我们写的变量名,不利于阅读)
//使用CommonJS
const { dateFormat, priceFormat } = require('./js/format');
console.log(dateFormat("abc"));
console.log(priceFormat("abc"));
我们可以看到打包出来的源码,是存在很多模板注释以及其他一些杂七杂八的东西影响我们阅读。我们接下来将删掉这些东西,然后看一下我们整理好的代码。
整理好都的代码:
//每个依赖的模块都在这个对象里存储:{模块的路径(key): 函数(value)}
var __webpack_modules__ = {
//这里的key:value,其实就是我们require要导入的文件路径与我们写的代码
"./src/js/format.js": function (module) {
const dateFormat = (date) => {
return "2022-5-7";
};
const priceFormat = (price) => {
return "100.00";
};
// 将我们要导出的变量, 放入到module对象中的exports对象
module.exports = {
dateFormat,
priceFormat,
};
},
};
// 定义一个对象, 作为加载模块的缓存
var __webpack_module_cache__ = {};
// 这个函数就是webpack为我们生成的,用来替代commonJs的require
function __webpack_require__(moduleId) {
// 1.判断缓存中是否已经加载过
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
// 2.给新建的module变量和__webpack_module_cache__[moduleId]缓存对象,一起赋值了同一个对象
var module = (__webpack_module_cache__[moduleId] = { exports: {} });
// 3.在保存路径名与代码的对象里,根据路径名拿到我们需要用到的代码
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// 4.我们要require的东西已经保存到了module.exports里,return
return module.exports;
}
// 立即执行函数:开始执行代码逻辑!!!
//其实这里就是我们写的代码,但是涉及commonJs模块化的地方进行了处理
!(function () {
// 1.使用webpack生成的 __webpack_require__函数 替代commonJS的require
const { dateFormat, priceFormat } = __webpack_require__("./src/js/format.js");
console.log(dateFormat("abc"));
console.log(priceFormat("abc"));
})();
现在我们就清楚webpack是怎样实现commonJS的模块化了。就是去重写commonJS的导入与导出,写成浏览器看得懂的样子。
并且还贴心地附送了缓存功能,下次进行导入导出直接在缓存里拿到就可以了,无需重复逻辑。
Webpack实现ES Module原理
老样子,写一段使用了ES Module 的代码,然后打包看看打包出来的代码。
//使用ES Module
import { sum, mul } from "./js/math";
console.log(mul(20, 30));
console.log(sum(20, 30));
打包后的代码如下:
// 1.定义了一个对象, 对象里面放的是我们的模块映射
//每个依赖的模块都在这个对象里存储:{模块的路径(key): 函数(value)}
var __webpack_modules__ = {
"./src/es_index.js": function (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) {
// 调用r的目的:记录下__webpack_exports__是一个ES Modue
__webpack_require__.r(__webpack_exports__);
var _js_math__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/js/math.js");
console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.mul(20, 30));
console.log(_js_math__WEBPACK_IMPORTED_MODULE_0__.sum(20, 30));
},
"./src/js/math.js": function (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) {
__webpack_require__.r(__webpack_exports__);
// 调用了d函数: 给exports设置了一个代理definition(我们现在传入的第二个参数就是definition)
// exports对象中本身是没有对应的函数,所以我们通过exports拿到对应函数,其实是到代理definition里拿到的
__webpack_require__.d(__webpack_exports__, {
sum: function () {
return sum;
},
mul: function () {
return mul;
},
});
const sum = (num1, num2) => {
return num1 + num2;
};
const mul = (num1, num2) => {
return num1 * num2;
};
},
};
// 2.模块的缓存
var __webpack_module_cache__ = {};
// 3.require函数的实现(加载模块)
function __webpack_require__(moduleId) {
// 1.判断缓存中是否已经加载过
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].exports;
}
// 2.给新建的module变量和__webpack_module_cache__[moduleId]缓存对象,一起赋值了同一个对象
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
// 3.在保存路径名与代码的对象里,根据路径名module拿到我们需要用到的代码
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
// 4.我们要require的东西已经保存到了module.exports里,return
return module.exports;
}
//写了三个立即执行函数,为我们的__webpack_require__添加三个属性,分别是d、o、r,它们分别是一个函数,有各自的作用
!(function () {
// __webpack_require__.d属性:exports没有definition的值时,就修改exports的个体,保证exports可以拿到definition的值
__webpack_require__.d = function (exports, definition) {
//遍历definition
for (var key in definition) {
//如果exports里没有我们的key,就去修改get,这样每次get都可以从definition上拿到我们要的key
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();
!(function () {
__webpack_require__.o = function (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
};
})();
//r的作用是用来记录当前使用的模块化是ES Module类型
!(function () {
__webpack_require__.r = function (exports) {
//如果支持Symbol,就会加这个东西:记录下exports是一个Module模块(exports就是用来存放我们使用模块化ES Moduel导入的东西)
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
}
// 记录下exports是一个ES Module
Object.defineProperty(exports, "__esModule", { value: true });
};
})();
//入口进入
__webpack_require__("./src/es_index.js");