webpack是个bundler,每一个文件都是一个模块。写代码的时候,无论你使用的是CommonJS模块规范、ES模块规范还是混合使用两者,webpack最终打包出来的产物,都能在浏览器/NodeJS环境中正常运行。下面将会由一个简单的示例,来讲解一下webpack打包出来的代码产物,运行时是如何运行的。
配置简单的示例
文件夹目录
index.js
const test = require('./test');
function selfFuc(){};
selfFuc();
test();
test.js
function test() {
const a = '1';
return a;
};
module.exports = test;
index.es.js
import test from './test.es';
function selfFuc(){};
selfFuc();
test();
test.es.js
export default function test() {
const a = '1';
return a;
};
webpack.config.js
module.exports = {
mode: 'development', // 建议配置为development,默认是production,会有代码压缩,很难阅读
entry: './src/index.es.js', // 根据入口文件改 entry
output: {
filename: 'index.es.js' // 增加es后缀方便区分 不同的打包产物
},
devtool: false,
};
package.json
{
"name": "webpack-demos",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
}
执行命令npm i安装完依赖后,
执行npm run build 就可以用webpack打包了
CommonJS模块规范的打包产物
dist/index.js
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/test.js":
/*!*********************!*\
!*** ./src/test.js ***!
*********************/
/***/ ((module) => {
function test() {
const a = '1';
return a;
};
module.exports = test;
/***/ })
/******/ });
/************************************************************************/
/******/ // 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.
(() => {
/*!**********************!*\
!*** ./src/index.js ***!
**********************/
const test = __webpack_require__(/*! ./test */ "./src/test.js");
function selfFuc(){};
selfFuc();
test();
})();
/******/ })()
;
整体结构
先整体看一下这个产物文件,其实是一个立即执行函数,就是IIFE(Immediately Invoked Function Expression)。
再往里看一层,进行了一些变量和函数的定义、最后又是一个IIFE。
其实比较容易得发现,最后的IIFE的代码内容实际上就是我们entry文件的代码内容,只是把require函数换成了,webpack生成的__webpack_require__函数。
webpack_modules
变量会定义除了入口文件外的所有模块,key为模块的路径,value是模块函数,模块函数的函数体就是源模块的代码。在__webpack_require__函数中会使用到。
webpack_module_cache
缓存对象,用于缓存已经执行过的模块函数。在__webpack_require__函数中会使用到。
webpack_require
这个函数是webpack运行时中比较关键的函数了。入参moduleId是一个模块的模块路径。
具体的逻辑:
- 根据moduleId从
__webpack_modules__中找到对应的模块函数,创建module对象,执行并返回module.exports - 如果moduleId 在
__webpack_require__中存在,则直接返回module.exports,不重复执行模块函数。
样例解析
现在再回去看dist/index.js的代码,进行解析。
- 定义
__webpack_modules__,除了入口文件./src/index.js之外,只有./src/test.js模块,所以,__webpack_modules__对象只有一个key-value对,去掉一些注释和括号会比较好看一点:
var __webpack_modules__ = {
"./src/test.js": (module) => {
function test() {
const a = '1';
return a;
};
module.exports = test;
},
};
- 定义了
__webpack_module_cache__对象、__webpack_require__函数。 - 执行最后的IIFE。
-
- test 的值等于
__webpack_require__函数的返回值。这个时候的入参是./src/test.js。
- test 的值等于
-
-
- 先判断
__webpack_module_cache__缓存中是否存在./src/test.js模块。不存在,则重新创建一个module,并塞到__webpack_module_cache__缓存中。 - 执行
__webpack_modules__中./src/test.js的模块函数。 ./src/test.js的模块函数返回值是test函数,所以在IIFE中,test变量就被赋值为./src/test.js模块中的test函数
- 先判断
-
-
- 然后执行
selfFuc()和test()。
- 然后执行
ES模块规范的打包产物
dist/index.es.js
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/test.es.js":
/*!************************!*\
!*** ./src/test.es.js ***!
************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ test)
/* harmony export */ });
function test() {
const a = '1';
return a;
};
/***/ })
/******/ });
/************************************************************************/
/******/ // 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;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // 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 */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // 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.
(() => {
/*!*************************!*\
!*** ./src/index.es.js ***!
*************************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test_es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test.es */ "./src/test.es.js");
function selfFuc(){};
selfFuc();
(0,_test_es__WEBPACK_IMPORTED_MODULE_0__["default"])();
})();
/******/ })()
;
整体结构
对比CommonJS模块规范的产物,使用ES模块规范的打包产物,整体结构上是一样的。但是比CommonJS规范产物多了一些代码。
- 新增定义了
__webpack_require__.d``__webpack_require__.o``__webpack_require__.r函数 - 每个模块的,模块函数中多进行了一下两个操作:
-
- 调用
__webpack_require__.r函数,入参是module.exports - 调用
__webpack_require__.d函数,入参是module.exports和definition模块输出定义对象。
- 调用
webpack_require.r
这个函数使用时,入参是module.exports。作用就是给module.exports对象增加一个属性__esModule,值为true,标识这是一个ES模块规范的模块输出。
webpack_require.d
在解释这个函数的作用时,可以先结合着看一下ES模块规范产物中的./src/test.js模块函数和__webpack_require__函数。
./src/test.es.js模块函数中,虽然我们写代码的时候指明了需要把test函数export出去,但是产物中并没有把test函数往module.exports上挂载(CommonJS规范是有相关操作的)。但是当./src/index.es.js模块引用./src/text.es.js模块时,使用的__webpack_require__函数是没有发生改变的,return的还是module.exports。_test_es__WEBPACK_IMPORTED_MODULE_0__["default"]是怎么样拿到./src/test.es.js模块中的test函数的呢?起作用的就是__webpack_require__.d函数。
这个函数有两个入参,第一个就是module.exports,第二个是definition--我把他叫做“模块输出定义对象”,webpack会根据本模块的export情况生成这个对象,对象里的key是export 的变量名(例子中export default 写法 key就是 default),value是一个getter函数,返回模块代码中的需要export的变量。
__webpack_require__.d函数的逻辑就是,循环遍历definition对象中的key,如果definition对象中存在,但是module.exports对象不存在(证明是输出的变量是通过ES模块规范输出的),就使用Object.defineProperty方法给module.exports对象定义这个key,值就是definition中对应的getter函数definition[key]。这样的话,当./src/index.es.js模块,通过_test_es__WEBPACK_IMPORTED_MODULE_0__["default"]语句,就可以获取到./src/test.es.js模块的test函数了。
webpack_require.o
这个函数实现实际上就是Object.prototype.hasOwnProperty这个方法的调用。这个方法就不在这里具体的介绍,直接看MDN吧。
方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)
样例解析
ES模块规范产物其实跟CommonJS模块规范产物基本上是一致的,只需要结合__webpack_require__.d函数,理解_test_es__WEBPACK_IMPORTED_MODULE_0__["default"]语句是如何获取到./src/test.es.js模块的test函数。
- 定义了
__webpack_module_cache__对象、__webpack_require__、__webpack_require__.d、__webpack_require__.o、__webpack_require__.r函数。 - 执行最后的IIFE。
-
- 执行
__webpack_require__函数,这个时候的入参是./src/test.es.js。_test_es__WEBPACK_IMPORTED_MODULE_0__的值等于__webpack_require__(./src/test.es.js)的返回值。
- 执行
-
-
- 先判断
__webpack_module_cache__缓存中是否存在./src/test.es.js模块。不存在,则重新创建一个module,并塞到__webpack_module_cache__缓存中。 - 执行
__webpack_modules__中./src/test.es.js的模块函数。
- 先判断
-
-
-
-
- 调用
__webpack_require__.r函数,声明 ESM 模块标识。 - 调用
__webpack_require__.d函数,实现将通过ES模块规范导出的内容附加到module.exports对象上。 - 其他代码执行,返回
module.exports。
- 调用
-
-
-
- 然后执行
selfFuc() _test_es__WEBPACK_IMPORTED_MODULE_0__["default"]的值就是__webpack_require__.d调用时指定的getter函数的返回值--test函数,所以在IIFE中,_test_es__WEBPACK_IMPORTED_MODULE_0__["default"]()就是调用了./src/test.es.js模块中的test函数。
- 然后执行
混合使用两种规范
新建一个index.mix.js文件。
混用两种规范有四种可能性:
- 以ESM的语法引入CommonJS的模块
- 以ESM的语法引入ESM的模块
- 以CommonJS的语法引入CommonJS的模块
- 以CommonJS的语法引入ESM的模块
index.mix.js(ESM) x test.js(CommonJS)
./src/index.mix.js文件
import test from './test';
function selfFuc(){};
selfFuc();
test();
稍微修改一下./src/test.js文件
function test() {
const a = '1';
return a;
};
module.exports = {
test,
};
打包产物./dist/index.mix.js
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/test.js":
/*!*********************!*\
!*** ./src/test.js ***!
*********************/
/***/ ((module) => {
function test() {
const a = '1';
return a;
};
module.exports = {
test,
};
/***/ })
/******/ });
/************************************************************************/
/******/ // 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;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // 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 */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/******/ /* webpack/runtime/make namespace object */
/******/ (() => {
/******/ // 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 in strict mode.
(() => {
"use strict";
/*!**************************!*\
!*** ./src/index.mix.js ***!
**************************/
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test */ "./src/test.js");
/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_test__WEBPACK_IMPORTED_MODULE_0__);
// import test from './test.es';
// const test = require('./test.es');
function selfFuc(){};
selfFuc();
(0,_test__WEBPACK_IMPORTED_MODULE_0__.test)();
})();
/******/ })()
;
整体结构
整体结构还是差不多的。这次的变动是:
- 新增了
__webpack_require__.n函数。 - 新增了一个没有用到的变量
var _test__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_test__WEBPACK_IMPORTED_MODULE_0__)
webpack_require.n
函数的入参是模块函数执行结果,也就是module.exports。
这个函数的作用就是判断模块的module.expots上时候有__esModule值,如果有,就返回一个getter函数() => module.exports.default,否则就返回另一个getter函数() => module.exports。
概括起来就是,这个函数会返回一个getter函数,当模块被标识为ESMdodule是,返回ES模块的export default。当模块不是ESModule,返回module.exports。
为什么需要用这样的一个函数呢?
我们可以先把上面我提到的四种混用的情况都试一遍,观察一下四份产物。四份产物这里就不一一列举了,直接给结论:
只有当使用ESM语法来引入CommonJS模块的时候,__webpack_require__.n函数才会被调用,入参是ComminJS模块的模块函数执行的返回值(module.exports)。
因为,在ESM的import语法中,有这样的一个语法糖:
import xx from 'xxx',这样是与import { default as xx } from 'xxx'等价的,在CommonJS的import语法中没有这样的用法,require的左边都是等于引入模块的module.exports。并且,webpack在对CommonJS的模块进行处理时,没有在CommonJS模块的模块函数代码中处理default的逻辑,在对ESM的模块进行处理时,在ESM模块的模块函数代码中用__webpack_require__.d函数对default进行了处理。以ES模块规范的打包产物为例子:
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ test)
/* harmony export */ });
为了兼容两种规范的混用,也就是使用ESM语法 import xx from 'xx' 来引入CommonJS模块时,此时,xx的值就是 CommonJS模块的module.exports。