likes
comments
collection
share

分析一个极简的 Webpack 运行时代码

作者站长头像
站长
· 阅读数 11

文章内容:用 webpack 打包两个简单的模块,分析打包后的代码,也就是运行时代码。

代码准备

index.js

const sum = require('./sum')

console.log(sum(6, 9));

sum.js

module.exports = (a, b) => {
  return a + b
}

webpack.config.js

const path = require('path')
module.exports = {
  entry: './index.js',
  output: {
    path: path.resolve(__dirname, 'build')
  },
  mode: 'none'
}
// npx webpack --entry ./index.js --output-path build --mode=none

在控制台执行

npx webpack

在目录下输出 build/main.js

/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/***/ ((module) => {

module.exports = (a, b) => {
  return a + b
}


/***/ })
/******/ 	]);
/************************************************************************/
/******/ 	// 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.
(() => {
const sum = __webpack_require__(1)

console.log(sum(6, 9));
})();

/******/ })()
;

接下来对 main.js 的代码进行分析

运行时代码分析

整体分析

main.js 整个是一个立即执行函数,首先先看一下整体

分析一个极简的 Webpack 运行时代码

立即执行函数内包裹了五个部分的内容:

  • 1、2、4 是变量的声明与赋值,
  • 3 是函数的声明,
  • 5 又是一个立即执行函数,

这时还不知道这些变量和函数的作用是什么,可以带着疑问去调试代码;

接下来就进行断点调试

  • 将断点打在 5 这个立即执行函数内部第一行代码上
  • 然后打开 JavaScript Debug Terminal,执行 node main.js
  • 进入我们所打的断点位置
  • 然后单步调试,看代码的执行情况

分析一个极简的 Webpack 运行时代码

通过调试运行时代码分析得出

  • 1 -> __webpack_modules__:是一个数组,存放所有加载到的模块。在此示例中,__webpack_modules__[0] 是空的,__webpack_modules__[1] 是 sum.js 模块;
  • 2 -> __webpack_module_cache__:是一个对象,对模块执行结果进行缓存,这样能够保证每个模块只被执行一次;
  • 3 -> __webpack_require__:是一个函数,作用是加载模块,如果模块第一次被加载,则通过 __webpack_modules__[moduleId] 匹配上对应的模块,并进行缓存;如果是已加载的模块,则直接从 __webpack_module_cache__[moduleId] 取;
  • 4 -> __webpack_exports__:是一个对象,存放导出的内容;
  • 5 -> IIFE:加载入口模块

__webpack_require__具体做了什么?

webpack 的模块加载器是如何实现的,见注释

/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ([
/* 0 */,
/* 1 */
/***/ ((module) => {

module.exports = (a, b) => {
  return a + b
}


/***/ })
/******/ 	]);
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// (1)检查模块是否存在于缓存中
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
            // (2)如果缓存存在,则直接返回缓存中 moduleId 所对应的 exports 对象
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// (3)如果缓存不存在,则创建一个 module 对象,用于初始化 module 信息,同时创建对应的缓存
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// (4)通过 moduleId 匹配到 __webpack_modules__ 存放的对应的模块,并传入 module, module.exports, __webpack_require__ 参数,执行模块
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// (5)返回 module.exports
/******/ 		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.
(() => {
const sum = __webpack_require__(1)

console.log(sum(6, 9));
})();

/******/ })()
;

如何理解 5 -> IIFE:加载入口函数

将 main.js 改造一下

分析一个极简的 Webpack 运行时代码

/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ([
/* 0 */
() => {
  const sum = __webpack_require__(1)

  console.log(sum(6, 9));
},
/* 1 */
/***/ ((module) => {

module.exports = (a, b) => {
  return a + b
}


/***/ })
/******/ 	]);
/************************************************************************/
/******/ 	// 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.
__webpack_require__(0);

/******/ })()
;

可见 webpack 运行时代码最后是 __webpack_require__(0)

小结

通过对 webpack 运行时代码进行调试以及分析,如果不考虑缓存的情况,可用一句话说明其逻辑:加载入口模块,入口模块所依赖的其他模块,通过 __webpack_require__ 模块加载函数进行递归加载存放在 __webpack_modules__的对应模块。