前言
很久没有写文章了,今天心血来潮,就写一下之前总结的关于 webpack 模块的问题。刚好在几个月前遇到过另一个问题,当时也简单看了一下 webpack 和 NodeJS 模块的源码实现:
如果你有观察过 webpack 转换后的代码,一定会发现,不管是 import
还是 require
都会被转换成 __webpack_require__
这种形式。
webpack 自己实现了一套模块化的规范,使用 __webpack_require__
来导入模块,将其挂载到 module.exports
上面,有点儿类似 CommonJS 的模块化规范。
一个来自 QQ 群的提问
某天晚上,我的 QQ 群有个童鞋问了这么一个问题:
我也比较好奇为什么 require
引入的图片还需要在后面加个 default
呢?为什么 import
引入的却不需要?是否和 file-loader
处理图片文件有关?
带着这个疑问,于是我写了一个简单的 DEMO 来验证了一下,代码如下:
在执行了 webpack 命令后,可以看到编译后的精简代码是这样的:
webpack 模块源码分析
首先,我们可以看出来这个编译后的 js 文件就是一个立即执行函数,他接收了当前文件引入的外部模块作为一个参数,所有的外部模块被放到了一个对象当中,以当前 src 目录下的绝对路径作为 key 值,value 这是一个方法,这个方法注入了 __webpack_require__
和 __webpack_exports__
作为参数,简单来说就是类似于:
(function(modules) {
})({
"./src/logo.png": function(module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);n/* harmony default export */ __webpack_exports__["default"] = (__webpack_require__.p "a218f2cb12bf56dd2a68003790d1e986.png");nn//# sourceURL=webpack:///./src/logo.png?");
}
})
我们可以明显看到,这个图片在导出的时候,实际上是在 __webpack_exports__["default"]
里面的,那么在使用 require 引入的时候又是什么样的呢?
我们来看一下 index.tsx
被编译后的代码:
/***/ "./src/pages/home/index.jsx":
/*!**********************************!*
!*** ./src/pages/home/index.jsx ***!
**********************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__, "default", function() { return Index; }); var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./node_modules/_react@16.13.1@react/index.js"); var react__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./src/pages/home/constants.js");nnnnvar logo = __webpack_require__( "./src/logo.png");nnfunction Index(props) {n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {n src: logon });n}nn//# sourceURL=webpack:///./src/pages/home/index.jsx?");
很明显可以看到,这里在引入 logo 这个图片的时候,是直接使用 __webpack_require__
来导入的,我们前面看到过 __webpack_require__
的实现。
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {},
/******/ hot: hotCreateModule(moduleId),
/******/ parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
/******/ children: []
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
这里只是返回了 module.exports
,并没有 default
,所以如果是直接用 require
来引入图片的话,那就肯定不会生效。
如果我们使用 import 来导入的话会怎么样呢?
代码语言:javascript复制/***/ "./src/pages/home/index.jsx":
/*!**********************************!*
!*** ./src/pages/home/index.jsx ***!
**********************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__, "default", function() { return Index; }); var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./node_modules/_react@16.13.1@react/index.js"); var react__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); var _constants__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__("./src/pages/home/constants.js"); var _logo_png__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__( "./src/logo.png");nnnnnvar constants = __webpack_require__( "./src/pages/home/constants.js");nnfunction Index(props) {n return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {n src: _logo_png__WEBPACK_IMPORTED_MODULE_2__["default"]n });n}nn//# sourceURL=webpack:///./src/pages/home/index.jsx?");
我们可以看到,虽然导入的时候也没有带上一个 default
,但是 React 在创建 img 标签的时候,给它带上了一个 default
,关键点在于这句 return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {n src: _logo_png__WEBPACK_IMPORTED_MODULE_2__["default"]n })
,所以直接用 import 也是可以导入的。
ES Module 和 CommonJS
实际上,如果你在 NodeJS 里面使用过一些 npm 上面第三方的模块,会发现导入的时候都是要求我们使用 require('xxx').default
的,比如大名鼎鼎的 node-xlsx
:
import xlsx from 'node-xlsx';
// Or var xlsx = require('node-xlsx').default;
const data = [[1, 2, 3], [true, false, null, 'sheetjs'], ['foo', 'bar', new Date('2014-02-19T14:30Z'), '0.3'], ['baz', null, 'qux']];
var buffer = xlsx.build([{name: "mySheetName", data: data}]); // Returns a buffer
相信看了前面的分析后,你也能猜到这是为什么了吧?node-xlsx
是直接使用 export default
导出的,而为了抹平这种差异,导致我们不得不使用 require.default
来导入它。