Webpack 基础第二部分,认识 Webpack 。从最简单的一个 index.js 文件认识 Webpack。
在开始了解 Webpack 核心概念之前,让我们先看看 Webpack 的生成文件。
我们先讨论的最简单的 js 文件,不包含 html 及 Webpack jsonp 等加载知识。
index.js 入口文件
我定义了一个最简单的 index.js 作为入口文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import nav from './nav'; import { top, bottom } from './footer';
console.log(nav(), top, bottom);
const top = 'top'; const bottom = 'bottom';
module.exports = { top, bottom };
export default () => 'nav';
|
这里我们就能够跑一下 Webpack 生成对应的 runtime 文件(要先安装 webpack-cli 哦):
1 2
| $ yarn add webpack-cli webpack $ npm run webpack -- --mode development
|
生成 WebpackBootstrap 代码
下面来对应的分析下dist
目录下的生成代码(代码经过精简):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
| (function(modules) { var installedModules = {};
function __webpack_require__(moduleId) { if (installedModules[moduleId]) { return installedModules[moduleId].exports; } var module = (installedModules[moduleId] = { i: moduleId, l: false, exports: {} });
modules[moduleId].call( module.exports, module, module.exports, __webpack_require__ );
module.l = true;
return module.exports; }
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function(exports, name, getter) { if (!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { configurable: false, enumerable: true, get: getter }); } };
// 6. 为 exports 定义 __esModule 属性 __webpack_require__.r = function(exports) { Object.defineProperty(exports, '__esModule', { value: true }); };
__webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, 'a', getter); return getter; };
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
__webpack_require__.p = '';
return __webpack_require__((__webpack_require__.s = './src/index.js')); })( { './src/footer.js':
function(module, exports) { eval( `const top = 'top'; const bottom = 'bottom'; module.exports = { top, bottom }; //# sourceURL=webpack:///./src/footer.js?` );
},
'./src/index.js':
function(module, __webpack_exports__, __webpack_require__) { 'use strict'; eval( `__webpack_require__.r(__webpack_exports__); /* harmony import */ var _nav__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./nav */ "./src/nav.js"); const { top, bottom } = __webpack_require__(/*! ./footer */ "./src/footer.js"); console.log(Object(_nav__WEBPACK_IMPORTED_MODULE_0__["default"])(), top, bottom); //# sourceURL=webpack:///./src/index.js?` );
},
'./src/nav.js':
function(module, __webpack_exports__, __webpack_require__) { 'use strict'; eval( `__webpack_require__.r(__webpack_exports__); /* harmony default export */ __webpack_exports__["default"] = (() => "nav"); //# sourceURL=webpack:///./src/nav.js?` );
}
} );
|
分析 WebpackBootstrap
上面的生成的代码主要生成了一个大的 IIFE (立即执行函数)。观察可以发现,三个 js 文件被封装成了三个函数传给这个 IIFE 立即执行函数。Webpack 通过 IIFE 函数作用域来实现模块化。
执行过程是:
- 定义模块缓存
- 定义
__webpack_require__
函数
- 检查请求模块是否在缓存中,如果在,直接返回缓存中模块
- 创建一个新的模块(并将新创建的模块放入缓存中)
- 执行模块
- 标记模块为已加载完成
- 返回模块的 exports
- 定义公开模块对象 (
__webpack_require__.m
)
- 公开模块缓存 (
__webpack_require__.c
)
- 为 harmony exports (ES module exports) 定义 getter 函数
- 为 exports 定义
__esModule
属性
- 定义 getDefaultExport function 用来兼容 non-harmony modules
- Object.prototype.hasOwnProperty.call
- 定义
__webpack_public_path__
- 加载入口模块并返回 exports
其中,第 10 步调用入口模块之后,可以看到,传入的参数的函数中,模块的请求,继续调用__webpack_require__
,实现了一个递归调用和解析模块,递归完成之后,Webpack 也就能生成对应的模块依赖图,相关的调用关系也就很清晰了。
因为有了模块缓存机制,所以请求过的模块执行过一次之后,就会加入installedModules
中,下次调用所需模块就会直接从缓存对象中提取。
Webpack 模块加载对 ESM 和 CommonJs 的实现对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function(module, __webpack_exports__, __webpack_require__) { 'use strict'; eval( `__webpack_require__.r(__webpack_exports__); /* harmony import */ var _nav__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./nav */ "./src/nav.js"); const { top, bottom } = __webpack_require__(/*! ./footer */ "./src/footer.js"); console.log(Object(_nav__WEBPACK_IMPORTED_MODULE_0__["default"])(), top, bottom); //# sourceURL=webpack:///./src/index.js?` ); },
function(module, __webpack_exports__, __webpack_require__) { 'use strict'; eval( `__webpack_require__.r(__webpack_exports__); /* harmony default export */ __webpack_exports__["default"] = (() => "nav"); //# sourceURL=webpack:///./src/nav.js?` ); }
function(module, exports) { eval( `const top = 'top'; const bottom = 'bottom'; module.exports = { top, bottom }; //# sourceURL=webpack:///./src/footer.js?` ); },
|
上面代码__webpack_require__.r(__webpack_exports__)
标记了这个模块使用了 ESM ,关于 ESM 的使用方法,定义的__webpack_require__
中,去调用nav.js
, __webpack_exports__["default"] = (() => "nav");
通过定义在'default'上的函数来封装了返回的值,只有在需要调用的时候,再进一步去执行获取值,而 footer.js 则是直接执行获取了module.exports = { top, bottom };
。
关于 ESM 和 CommonJS 的互相引用则是利用了__webpack_require__.n
来进行包装获取对应的 modules 对象的值。可以看对应的代码。
=> 7. 定义 getDefaultExport function 用来兼容 non-harmony module
最后,上面的生成的 WebpackBootstrap 代码 强烈建议放在浏览器的 devtools 的 snippet 中打上断点运行一遍来细细的感受一下。