yinguangyao / blog

关于 JavaScript 前端开发、工作经验的一点点总结。

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

深入理解 webpack 模块

yinguangyao opened this issue · comments

commented

前言

在上篇讲 Nuxt 同构问题的时候,我有提到过 NodeJS 和 webpack 的模块化实现。今天主要来讲解 webpack 中的模块化。

如果你有观察过 webpack 转换后的代码,一定会发现,不管是 import 还是 require 都会被转换成 webpack_require 这种形式。

image

webpack 自己实现了一套模块化的规范,使用 webpack_require 来导入模块,将其挂载到 module.exports 上面,有点儿类似 CommonJS 的模块化规范。

一个来自 QQ 群的提问

某天晚上,我的 QQ 群有个童鞋问了这么一个问题:

image

image

我也比较好奇为什么 require 引入的图片还需要在后面加个 default 呢?为什么 import 引入的却不需要?是否和 file-loader 处理图片文件有关?

带着这个疑问,于是我写了一个简单的 DEMO 来验证了一下,代码如下:

image

在执行了 webpack 命令后,可以看到编译后的精简代码是这样的:

image

webpack 模块源码分析

首先,我们可以看出来这个编译后的 js 文件就是一个立即执行函数,他接收了当前文件引入的外部模块作为一个参数,所有的外部模块被放到了一个对象当中,以当前 src 目录下的绝对路径作为 key 值,value 这是一个方法,这个方法注入了 webpack_requirewebpack_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\");\n\n//# 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\");\n\n\n\nvar logo = __webpack_require__( \"./src/logo.png\");\n\nfunction Index(props) {\n  return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n    src: logo\n  });\n}\n\n//# 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 来导入的话会怎么样呢?

/***/ "./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\");\n\n\n\n\nvar constants = __webpack_require__( \"./src/pages/home/constants.js\");\n\nfunction Index(props) {\n  return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(\"img\", {\n    src: _logo_png__WEBPACK_IMPORTED_MODULE_2__[\"default\"]\n  });\n}\n\n//# 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('').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 导出的,而 webpack 为了抹平这种差异,导致我们不得不使用 require.default 来导入它。