浅入理解 webpack 模块

2021-10-19 10:13:54 浏览数 (1)

前言

很久没有写文章了,今天心血来潮,就写一下之前总结的关于 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__ 作为参数,简单来说就是类似于:

代码语言:javascript复制
(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 被编译后的代码:

代码语言: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");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__ 的实现。

代码语言:javascript复制
/******/     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

代码语言:javascript复制
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 来导入它。

0 人点赞