在 wepback 中是可以使用 CommonJS 模块化,也可以使用 ES6 模块化的,而且 CommonJS 和 ES6 两种模块化可以混用,即 ES6 模块化导出使用 CommonJS 模块化导入, CommonJS 模块化导出使用 ES6 模块化导入。
CommonJS 模块化
文件结构
基于下面的文件结构然后查看打包后的源码:
// common.jsfunction increase(num1, num2) {return num1 + num2;}function multiply(num1, num2) {return num1 + num2;}module.exports = {increase,multiply,}
// 入口: index.jsconst common = require('./common');console.log(common.increase(1, 2));
打包后的源码解析
入口为 index.js 文件,在 index.js 文件中引入了 common.js 文件,webpack 打包后的源码中会有一个自执行函数,这个自执行函数就是入口 index.js 文件中的内容:
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.(() => {/********************!*\!*** ./src/index.js ***!\**********************/const common = __webpack_require__( /*! ./common */ "./src/common.js");console.log(common.increase(1, 2));})();
在上面的代码中可以看到打包后的源码将 require 函数替换成了__webpack_require__ 调用并传递了文件路径。__webpack_require__ 源码如下:
/************************************************************************//******/ // 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];// 检查缓存对象中是否存在 moduleId 对应的数据, 有则返回if (cachedModule !== undefined) {/******/return cachedModule.exports;/******/}/******/ // Create a new module (and put it into the cache)// 如果缓存对象中没有, 则创建一个对象复赋值给缓存对象[moduleId]属性和一个新的 module 变量var module = __webpack_module_cache__[moduleId] = {/******/ // no module.id needed/******/ // no module.loaded needed/******/exports: {}/******/};/******//******/ // Execute the module function// 在 __webpack_modules__ 调用对应 moduleId 的函数__webpack_modules__[moduleId](module, module.exports, __webpack_require__);/******//******/ // Return the exports of the module// 返回数据return module.exports;/******/}
在 __webpack_require__ 函数中接受一个 moduleId 的参数,这个 moduleId 就是调用时传递的 "./src/common.js",首先会在 __webpack_module_cache__ 这个缓存对象中查找是否有对应 moduleId 的数据,有的话就直接返回了,没有会创建下面这样的对象:
{exports: {},}
这个对象会同时赋值给一个新的 module 属性和 __webpack_module_cache__[moduleId] 属性,然后的调用了 __webpack_modules__[moduleId] 下面的一个函数,并将 module 这个新对象和 module 下面的 exports 属性再加上自身传递了过去,最后返回了 module.exports。
var __webpack_modules__ = ({"./src/common.js":/*!***********************!*\!*** ./src/common.js ***!\***********************//***/((module) => {function increase(num1, num2) {return num1 + num2;}function multiply(num1, num2) {return num1 + num2;}module.exports = {increase,multiply,}})});
__weboack_modules 是一个对象,这个对象中就保存着所有模块,对象中的 key 就是模块对应的路径,值是一个函数,函数中是模块中的内容,在__webpack_require__函数中调用__webpack_modules__[moduleId] 就是找到了地址对应的模块函数,执行并把模块中需要导出的数据挂到了 module.exports 中,这时在 __webpack`require_ 中创建的 module 属性和webpack_module_cache[moduleId]` 内容如下:
{exports: {increase,multiply,},}
也就是说 __webpack_require__ 导出的内容就是上面的 exports 对象,这样就实现了 commonJS 的模块化。
ES6 模块化
文件结构
// es.jsfunction increase(num1, num2) {return num1 + num2;}function multiply(num1, num2) {return num1 + num2;}export default {increase,multiply,}
// 入口:index.jsimport es from './es';console.log(es.multiply(2, 2));
打包后的源码解析
基于上面的代码结构,webpack 打包出来的内容和 commonJS 模块化打包处理的内容相差不是很多,也有一个自执行函数代表入口文件:
// 这里不同的是 多了一个 __webpack_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.(() => {/*!**********************!*\!*** ./src/index.js ***!\**********************/// 执行了 __webpack_require__.r 函数, 函数的作用是打一个标记__webpack_require__.r(__webpack_exports__);/* harmony import */var _es__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./es */ "./src/es.js");console.log(_es__WEBPACK_IMPORTED_MODULE_0__.default.multiply(2, 2));})();
在 ES6 打包出来的源码中会多几个功能函数如下:
__webpack_require__.r = (exports) => {// 判断是否支持 Symbol 和 Symbol.toStringTag 是否存在if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {// 存在的话则在 exports 对象上添加一个 key 为 Symbol.toStringTag, 值为 Module 的属性// 这个操作会在 exports.toString() 时返回 "[object Module]", 自定义了对象类型标签。Object.defineProperty(exports, Symbol.toStringTag, {value: 'Module'});}// 在 exports 对象上添加 __esModule 属性, 值为 trueObject.defineProperty(exports, '__esModule', {value: true});};
// 判断 prop 是不是在 obj 对象中__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop));
__webpack_require__.d = (exports, definition) => {// 遍历 definitionfor (var key in definition) {// 调用 o 函数找出 definition 实例属性,并且 exports 对象中没有相同 key 的属性时进行下一步if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {// 将 definition 对象的属性通过属性描述符的方式定义在了 exports 上,// exports 通过对应的 key 就可以找到 definition 上的属性值Object.defineProperty(exports, key, {enumerable: true,// 注意: get 是一个函数, 所以说 definition[key] 就是一个函数, exports[key] 访问时,// 返回的内容就是 definition[key] 函数调用后返回的内容get: definition[key]});}}};
基于上面的三个功能函数看一下自执行函数中调用的 _webpack_require 函数:
var __webpack_module_cache__ = {};function __webpack_require__(moduleId) {var cachedModule = __webpack_module_cache__[moduleId];return cachedModule.exports;}var module = __webpack_module_cache__[moduleId] = {exports: {}};__webpack_modules__[moduleId](module, module.exports, __webpack_require__);return module.exports;}
可以看到 webpackrequire 函数函数中并没有用到上面的三个功能函数,它的内容和 commonJS 生成的一样,接下来看下 webpack modules_ 对象:
var __webpack_modules__ = ({"./src/es.js":/*!*******************!*\!*** ./src/es.js ***!\*******************//***/((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {// 调用 r 函数, 给 exports 对象打上标记__webpack_require__.r(__webpack_exports__);// 调用 d 函数, 使用 defineProperty 的方式, 让 exports 可以访问到第二个参数对象__webpack_require__.d(__webpack_exports__, {// 这个 default 函数返回的对象就是当前模块中导出的对象"default": () => (__WEBPACK_DEFAULT_EXPORT__)});function increase(num1, num2) {return num1 + num2;}function multiply(num1, num2) {return num1 + num2;}// 当前模块导出的内容挂载到这个对象中,exports 也能访问到const __WEBPACK_DEFAULT_EXPORT__ = ({increase,multiply,});})});
这个时候返回的对象结构如下:
{
default: { // get
increase,
multiply,
},
__esModule: true,
[Symbol.toStringTag]: 'Module'
}
在代码中通过 es.multiply调用,打包后的代码转成了 es_WEBPACK_IMPORTED_MODULE_0.default.multiply 调用。
ES6 和 CommonJS 模块化混用
文件结构
其他文件结构和上面一样,入口 index 文件内容如下:
// 入口:index
const es = require('./es');
console.log(es.multiply(2, 2));
import { increase } from './common';
console.log(increase(2, 2));
打包后的源码分析
(() => {
// 通过 import 引入 module.exports 导出
var _common__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./common */ "./src/common.js");
// 通过 require 引入 expoet.default 导出
const es = __webpack_require__( /*! ./es */ "./src/es.js");
console.log(es.multiply(2, 2));
console.log((0, _common__WEBPACK_IMPORTED_MODULE_0__.increase)(2, 2));
})();
除去这个自执行函数外,其他的代码与上面 common、es 源码相同,测试后发现报错,原因是: 通过 require 引入 expoet.default 导出的内容时 webpack _require 这个函数函数的其实是一个带有 default 的对象,所有模块都出内容都在这个 default 对象上,而代码直接使用了 es.multiply 函数所以报错了,改成 es.default.multiply 就可以正常执行了,而 import 方式引入 module.exports 导出时是正常的。
改变文件结构
将 es 文件中的默认导出 export.default 改为 export :
export function increase(num1, num2) {
return num1 + num2;
}
export function multiply(num1, num2) {
return num1 + num2;
}
将 ES6 的默认导出改为 export 导出后代码就能正常运行了,这时因为去掉了 default 这个属性,直接导出了对应的属性,不再需要 module.defalut.xxx 这样的方式调用了:
var __webpack_modules__ = ({
"./src/es.js":
((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
// 这里导出的就不是 default 属性,而是创建了多个 get 函数
"increase": () => ( /* binding */ increase),
"multiply": () => ( /* binding */ multiply)
});
function increase(num1, num2) {
return num1 + num2;
}
function multiply(num1, num2) {
return num1 + num2;
}
})
})
所以当 ES6 模块化和 CommonJS 模块化混用时,不要通过 require 的方式去导入 export default 导出的内容!
