webpack系列目录
- webpack5之核心配置梳理
- webpack5之模块化原理
- webpack5之Babel/ESlint/浏览器兼容
- webpack5之性能优化
- webpack5之Loader和Plugin的实现
- webpack5之核心源码解析
模块化原理
在webpack5中,我们既能使用 CommonJS
,又能使用ES Module
,还能支持其他如AMD
,CMD
,UMD
等模块化规则。那webpack怎么实现这些模块化呢。我们讲下目前主流的CommonJS
和ES Module
这两种模块化原理,我们可以分为四种导出引入方式并查找对应的实现机制。
CommonJS
模块化实现原理ES Module
实现原理CommonJS
加载ES Module
的原理ES Module
加载CommonJS
的原理
目前我们有两种导出文件 dateFormat.js
通过CommonJS
导出,priceFormat.js
通过ES Module
导出。
// dateFormat.js
const dateNow = () => {
const date = new Date()
return date.toLocaleString()
}
module.exports = {
dateNow
}
// priceFormat.js
const add = (a, b) => {
return a + b
}
const minus = (a, b) => {
return a - b
}
export {
add,
minus
}
CommonJS模块化实现原理
我们在入口文件main.js引入dateFormat.js。
// main.js
const { dateNow } = require('./js/dateFormat')
console.log(dateNow())
因为我们还需要能够查看webpack打包源码,因此还需要做以下配置
module.exports = {
//...
mode: 'development',
devtool: 'source-map'
//...
}
重新打包后我们可以查看bundle.js源码,并将一些无效注释去掉。
var __webpack_modules__ = ({
"./src/js/dateFormat.js": (function (module) {
const dateNow = () => {
const date = new Date()
return date.toLocaleString()
}
module.exports = {
dateNow
}
})
});
// 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;
}
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
!function () {
const { dateNow } = __webpack_require__(/*! ./js/dateFormat */ "./src/js/dateFormat.js")
console.log(dateNow())
}();
//# sourceMappingURL=bundle.js.map
去掉注释后的源码已经很简单了,可以从中得知首先会定义所有模块 __webpack_modules__
,其中里面key为模块路径,value为模块函数,里面存放了相应的执行函数。第二步会定义一个缓存空对象 __webpack_module_cache__
。第三步会定义一个引用模块方法 __webpack_require__
,最后一步才是真正执行我们要执行的方法,可以看到 const { dateNow } = __webpack_require__(/*! ./js/dateFormat */ "./src/js/dateFormat.js")
,调用了引用方法并将模块路径传入进去,在 __webpack_require__
这个引用方法中会先对缓存对象 __webpack_module_cache__
做判断,如果存在缓存则返回 cachedModule.exports
,否则就将key值: 模块路径和value值: { exports: {} }
放进缓存对象中,并赋值给 var module
,之后再调用模块对象 __webpack_modules__
取对应的模块,将 module
作为参数传进去,并返回 module.exports = { dateNow }
。最后返回 module.exports
,__webpack_require__
返回并将 dateNow
解构出来打印。
做个小结:
- 生成模块对象
__webpack_modules__
- 申明模块缓存对象
__webpack_module_cache__
- 申明模块引用函数
__webpack_require__
- 执行模块引用函数从
__webpack_modules__
模块对象里面返回方法并执行后返回属性方法对象
ES Module模块化实现原理
现在改造main.js引入priceFormat.js。
// main.js
import { add, minus } from './js/priceFormat'console.log(add(2, 3))
console.log(minus(5, 3))
重新打包并去掉一些干扰的注视后可以看到
var __webpack_modules__ = ({
"./src/js/priceFormat.js": (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
"add": function () { return /* binding */ add; },
"minus": function () { return /* binding */ minus; }
});
const add = (a, b) => {
return a + b
}
const minus = (a, b) => {
return a - b
}
})
});
// The module cachevar
__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;
}
/* webpack/runtime/define property getters */
!function () {
// define getter functions for harmony exports
__webpack_require__.d = function (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 */
!function () {
__webpack_require__.o = function (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
}();
/* webpack/runtime/make namespace object */
!function () {
// define __esModule on exports
__webpack_require__.r = function (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.
!function () {
/*!*********************!*\ !*** ./src/main.js ***! *********************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _js_priceFormat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./js/priceFormat */ "./src/js/priceFormat.js");
console.log((0, _js_priceFormat__WEBPACK_IMPORTED_MODULE_0__.add)(2, 3))
console.log((0, _js_priceFormat__WEBPACK_IMPORTED_MODULE_0__.minus)(5, 3))
}();
//# sourceMappingURL=bundle.js.map
ES Module模块话显然比CommonJS代码多了很对,我们来看下,首先也是先定义一个模块对象 __webpack_modules__
,里面存放着 ./src/js/priceFormat.js
模块,第二步,定义一个 __webpack_module_cache__
缓存对象,第三步定义引用模块函数 __webpack_require__
。可以看到第二步和第三步和 CommonJS
定义的一摸一样,之后又定义了几个函数 __webpack_require__.d
,__webpack_require__.o
,__webpack_require__.r
,这些函数之后执行到了再说,之后定义了__webpack_exports__
为空对象。在最后一个执行函数里面才开始真正执行我们需要用的代码。可以看到先调用__webpack_require__.r(__webpack_exports__)
方法。__webpack_require__.r
其实为传入的exports
打上 __esModule
属性,用来表示是ES Module模块,因为我们入口是main.js用ES Module 模块化引入。最后执行引用方法 __webpack_require__
,传入模块路径 ./src/js/priceFormat.js
,通过之前的CommonJS介绍,引用方法里取的就是 __webpack_modules__
对应模块路径的函数并调用。在模块函数里面首先也是先调用了 __webpack_require__.r
方法,给导入的 module.exports
标识为ES Module 模块,然后调用 __webpack_require__.d
方法,可以看到我们除了传入 module.exports
,还传入了definition: { "add": function () { return add; }, "minus": function () { return minus; }});
这个对象,可以看到这是一个导出的方法名为key,value为一个返回该导出方法的函数。我们看下这个 __webpack_require__.d
干了什么呢,首先遍历 definition
的key,调用 __webpack_require__.o
方法判断key是否是传入该对象自身的属性,且在传入的module.exports
上没有该属性。如果条件满足,则对 module.exports
做一层代理,Object.defineProperty(exports, key, { enumerable: true, get: definition[key] })
,当我们访问 module.exports
上的属性方法时,它会从 definition
对象上取。
小结:
- 生成模块对象
__webpack_modules__
- 申明模块缓存对象
__webpack_module_cache__
- 申明模块引用函数
__webpack_require__
- 申明
__webpack_require__.d
,__webpack_require__.o
,__webpack_require__.r
- 执行模块引用函数从
__webpack_modules__
模块对象里面返回方法后执行 __webpack_modules__
取得的方法里面首先调用__webpack_require__.r
定义模块__esModule
属性,代表是ES Module模块。- 之后定义一个由属性方法为key,value为函数且返回该属性方法的对象
definition
和module.exports
一起传入__webpack_require__.d
方法里面 __webpack_require__.d
方法里面首先会对definition
和module.exports
通过__webpack_require__.o
方法判断属性的归属- 条件满足后执行
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] })
对module.exports
做一层代理,当我们访问module.exports
上的属性方法时,它会从definition
对象上取。
剩下的CommonJS
加载ES Module
和 ES Module
加载CommonJS
的原理其实跟上述一样,对于 CommonJS会直接从 __webpack_modules__
取,对于 ES Module
会对属性方法做一层代理,我们在访问 ES Module
引入的模块时,会代理到代理对象上面去取。