我们在 webpack初体验 这篇文章中演示到,浏览器不支持 CommonJS ,在特定场景下才支持 Es Module ,而 webpack 可以将这些模块化的代码解析成浏览器可识别的语法。

那么 webpack 究竟是对模块化做了怎样的处理呢?一起来看看。

项目结构

demo
├─ src
│ ├─ utils
│ │ ├─ common_math.js
│ │ └─ esmodule_format.js
│ ├─ common_index.js
│ ├─ esmodule_index.js
│ └─ index.js
├─ index.html
├─ package.json
└─ webpack.config.js

分别以 CommonJS 和 ES Module 两种方式来进行导入导出

// common_math.js —— 使用 CommonJS 导出
function add(a, b) {
  return a + b;
}
function sub(a, b) {
  return a - b;
}
module.exports = {
  add,
  sub,
}; // esmodule_format.js —— 使用 ES Module 导出
function timeFormat() {
  return "2022-02-02";
}
export { timeFormat };

config文件配置

webpack.config.js 中 "mode" 默认为 "production",此时代码是经过压缩和丑化的,为了更利于阅读,我们将它设置为 "development"。

而 "development" 模式下 "devtool" 默认为 "eval",会给代码增加很多暂时我们不需要的内容,所以将 "devtool" 设置为 "source-map"(关于 "source-map",下一篇文章会详细介绍)

const path = require("path");
module.exports = {
  entry: "./src/common_index.js",
  mode: "development",
  devtool: "source-map",
  output: {
    filename: "./bundle.js",
    path: path.resolve(__dirname, "./dist"),
  },
};

CommonJS

首先让 webpack 解析 CommonJS 语法,所以入口指定 common_index.js

// common_index.js —— 使用 CommonJS 导入
const { add, sub } = require("./utils/common_math.js");
console.log(add(20, 30));
console.log(sub(20, 30));

执行 npm run build ,为了方便阅读,将生成的 将 bundle.js 中的注释、以及自执行函数的最外层全部删除。

对于 CommonJS 的处理,主要有两个对象和一个函数

  • __webpack_modules__ 对象,用于保存文件路径和文件内容的映射关系
  • __webpack_module_cache__ 对象,用于缓存已加载过的文件,key值为文件路径,value为导出的内容
  • __webpack_require__ 函数,用于加载文件,将导出内容添加到 module.exports 和 exports 对象中

具体webpack编译源码

var __webpack_modules__ = {
  // 定义一个对象,对象中的 key 为文件路径,value 为函数,函数中包括文件内容
  "./src/utils/common_math.js": (module) => {
    function add(a, b) {
      return a + b;
    }
    function sub(a, b) {
      return a - b;
    }
    module.exports = {
      add,
      sub,
    };
  },
}; // 定义用于缓存已加载过文件的对象
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
  // 从缓存对象中取当前moduleId的value
  var cachedModule = __webpack_module_cache__[moduleId];
  // 如果存在直接返回 exports 对象
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  // 如果不存在,在缓存对象中增加 key 为 moduleId,值为 { export: {} } 的数据
  var module = (__webpack_module_cache__[moduleId] = {
    exports: {},
  });
  // 通过 moduleId,执行保存在 __webpack_modules__ 的方法,并传入参数 module 对象,
  // 执行方法后,会修改 module.exports 以及 exports 对象
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  // 当前返回的就是 { add, sub } 这个对象
  return module.exports;
} var __webpack_exports__ = {}; // 这里没有用到
// 执行 common_math.js
const { add, sub } = __webpack_require__("./src/utils/common_math.js");
console.log(add(20, 30));
console.log(sub(20, 30));

通过以上方式,webpack 将浏览器不可识别的 CommonJS 代码编译成了浏览器可正常运行的语法

ES Module

将webpack.config.js 文件中的入口改成 esmodule_index.js。

// esmodule_index.js —— 使用 ES Module 导入
import { timeFormat } from "./utils/esmodule_format.js";
console.log(timeFormat());

再执行 npm run build,为了便于阅读,还是将 bundle.js 中的注释、以及自执行函数的最外层、严格模式的规定全部删除。

ES Module 的实现与 CommonJS 相同之处在于,它也有这两个对象和一个函数

  • __webpack_modules__ 对象,用于保存文件路径和文件内容的映射关系
  • __webpack_module_cache__ 对象,用于缓存已加载过的文件,key值为文件路径,value为导出的内容
  • __webpack_require__ 函数,用于加载文件,将导出内容添加到 module.exports 和 exports 对象中

但 ES Module 更为复杂一些,还存在这三个函数

  • __webpack_require__.d 将传入的对象属性和值遍历到 exports 对象上
  • __webpack_require__.o 判断某属性是否存在于某对象中
  • __webpack_require__.r 给使用 ES Module 实现模块化的文件中 exports 对象里增加 __esModule 属性

具体webpack编译源码

var __webpack_modules__ = {
  "./src/utils/esmodule_format.js": (
    __unused_webpack_module,
    __webpack_exports__,
    __webpack_require__
  ) => {
    // 给exports增加__esModule属性
    __webpack_require__.r(__webpack_exports__);
    // 将 esmodule_format.js 中导出的函数 timeFormat 添加到 exports 对象中
    __webpack_require__.d(__webpack_exports__, {
      timeFormat: () => timeFormat,
    });
    function timeFormat() {
      return "2022-02-02";
    }
  },
}; var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = (__webpack_module_cache__[moduleId] = {
    exports: {},
  });
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  return module.exports;
} __webpack_require__.d = (exports, definition) => {
  // 遍历 esmodule_format.js 中导出的对象
  for (var key in definition) {
    // 如果当前exports对象中不存在该属性,则复制到exports对象中
    if (
      __webpack_require__.o(definition, key) &&
      !__webpack_require__.o(exports, key)
    ) {
      // 定义exports对象,将函数作为get属性
      Object.defineProperty(exports, key, {
        enumerable: true,
        get: definition[key],
      });
    }
  }
}; // 判断 prop 属性是否存在于 obj 对象中
__webpack_require__.o = (obj, prop) =>
  Object.prototype.hasOwnProperty.call(obj, prop); // 给 exports 对象增加 __esModule 属性
__webpack_require__.r = (exports) => {
  if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, {
      value: "Module",
    });
  }
  Object.defineProperty(exports, "__esModule", { value: true });
}; var __webpack_exports__ = {};
// __webpack_exports__ 上下两行暂时没有用到
__webpack_require__.r(__webpack_exports__); // 使用函数将导出内容加载到 exports 和 module.exports 对象中
var _utils_esmodule_format_js__WEBPACK_IMPORTED_MODULE_0__ =
  __webpack_require__("./src/utils/esmodule_format.js"); // 以下代码和函数直接调用效果一致
console.log(
  (0, _utils_esmodule_format_js__WEBPACK_IMPORTED_MODULE_0__.timeFormat)()
);

webpack 对于 ES Module 和 CommonJS 处理方式有些不同,CommonJS 中是直接将属性添加到 exports 对象中,而 ES Module 是通过 defineProperty 定义到该属性的 存取选择器 get 中

ES Module 和 CommonJS 混合使用

将webpack.config.js 文件中的入口改成 index.js。

// index.js
// ES Module 导出的内容 CommonJS 导入
const { timeFormat } = require( "./utils/esmodule_format.js");
// CommonJS 导出的内容 ES Module 导入
import { add, sub } from ("./utils/common_math.js");
console.log(timeFormat());
console.log(add(20, 30));
console.log(sub(20, 30));

再执行 npm run build,为了便于阅读,还是将 bundle.js 中的注释、以及自执行函数的最外层、严格模式的规定全部删除。

模块化相互引用的方式复用了 ES Module 和 CommonJS 都有的两个对象和一个函数

  • __webpack_modules__ 对象,用于保存文件路径和文件内容的映射关系
  • __webpack_module_cache__ 对象,用于缓存已加载过的文件,key值为文件路径,value为导出的内容
  • __webpack_require__ 函数,用于加载文件,将导出内容添加到 module.exports 和 exports 对象中

同时也保留了 ES Module 独有的这三个函数

  • __webpack_require__.d 将传入的对象属性和值遍历到 exports 对象上
  • __webpack_require__.o 判断某属性是否存在于某对象中
  • __webpack_require__.r 给使用 ES Module 实现模块化的文件中 exports 对象里增加 __esModule 属性

再增加了一个函数

  • __webpack_require__.n 定义遍历赋值为函数,并在该变量上添加a属性,值为函数
var __webpack_modules__ = {
  "./src/utils/common_math.js": (module) => {
    function add(a, b) {
      return a + b;
    }
    function sub(a, b) {
      return a - b;
    }
    module.exports = {
      add,
      sub,
    };
  },
  "./src/utils/esmodule_format.js": (
    __unused_webpack_module,
    __webpack_exports__,
    __webpack_require__
  ) => {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    __webpack_require__.d(__webpack_exports__, {
      timeFormat: () => timeFormat,
    });
    function timeFormat() {
      return "2022-02-02";
    }
  },
}; var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
  var cachedModule = __webpack_module_cache__[moduleId];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  var module = (__webpack_module_cache__[moduleId] = {
    exports: {},
  });
  __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  return module.exports;
} __webpack_require__.n = (module) => {
  var getter =
    module && module.__esModule ? () => module["default"] : () => module;
  __webpack_require__.d(getter, { a: getter });
  return getter;
}; __webpack_require__.d = (exports, definition) => {
  for (var key in definition) {
    if (
      __webpack_require__.o(definition, key) &&
      !__webpack_require__.o(exports, key)
    ) {
      Object.defineProperty(exports, key, {
        enumerable: true,
        get: definition[key],
      });
    }
  }
};
__webpack_require__.o = (obj, prop) =>
  Object.prototype.hasOwnProperty.call(obj, prop); __webpack_require__.r = (exports) => {
  if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
  }
  Object.defineProperty(exports, "__esModule", { value: true });
}; var __webpack_exports__ = {};
__webpack_require__.r(__webpack_exports__);
var _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
  "./src/utils/common_math.js"
); // 暂时没有用到
var _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0___default =
__webpack_require__.n(_utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__); const { timeFormat } = __webpack_require__("./src/utils/esmodule_format.js");
console.log(timeFormat());
console.log(
  (0, _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__.add)(20, 30)
);
console.log(
  (0, _utils_common_math_js__WEBPACK_IMPORTED_MODULE_0__.sub)(20, 30)
);

ES Module 和 CommonJS 混合使用的方式是被支持的,webpack 的处理方式就是将两者单独处理合并在一起。

以上就是webpack编译模块化文件的源码的内容,更多有关webpack的内容可以参考我其它的博文,持续更新中~

其实webpack编译"模块化"的源码没那么难的更多相关文章

  1. msvc2013编译qt5.6源码

    1.回顾 说起到qt的编译,真是领人痛心啊,不仅编译选项繁多,而且编译时间比较久,总是能使想编译qt源码的人望而却步,呵呵...我就是其中一个,不知道从什么时候开始就想着把qt的源码编译一下,也尝试过 ...

  2. 在Ubuntu Server14.04上编译Android6.0源码

    此前编译过Android4.4的源码,但是现在Android都到了7.0的版本,不禁让我感叹Google的步伐真心难跟上,趁这周周末时间比较充裕,于是在过去的24小时里,毅然花了9个小时编译了一把An ...

  3. 【转】编译Android系统源码和内核源码

    原文网址:http://blog.csdn.net/jiangwei0910410003/article/details/37988637 好长时间没有写blog了,之所以没有写,主要还是工作上的事, ...

  4. FW 编译Android系统源码和内核源码

    编译Android系统源码和内核源码 分类: Android2014-07-21 20:58 7287人阅读 评论(28) 收藏 举报 好长时间没有写blog了,之所以没有写,主要还是工作上的事,发现 ...

  5. 编译Android系统源码和内核源码

    [日期:2016-01-11] 来源:Linux社区  作者:jiangwei [字体:大 中 小]     把我之前编译Android系统源码和内核源码的过程记录一下,因为这个过程真的是受益匪浅,看 ...

  6. Atitit.反编译apk android源码以及防止反编译apk

    Atitit.反编译apk android源码以及防止反编译apk 1.1. Tool  apk逆向助手1 1.2. 二.使用dex2jar + jd-gui 得到apk的java源码1 1.3. 用 ...

  7. Ubuntu 下载 & 编译 Android5.1 源码

    ustc & tsinghua android srchttps://lug.ustc.edu.cn/wiki/mirrors/help/aosphttps://mirrors.tuna.ts ...

  8. 编译android5.0源码的

    java环境 Android 5.1 用到的jdk不再是Oracle 的 jdk ,而是开源的 openjdk,在ubuntu安装好后,使用如下命令安装jdk: $sudo apt-get insta ...

  9. vs2008编译FileZilla客户端源码

    vs2008编译FileZilla客户端源码 下载FileZilla客户端源码,下载地址https://download.filezilla-project.org/. FileZilla客户端解决方 ...

  10. 编译jmeter5.0源码

    jmeter5.0使用过程中,遇到request或者response乱码的情况,想要一次性解决这个问题,需要编译ApacheJMeter_http.jar这个包(lib\ext文件下)里的Reques ...

随机推荐

  1. Unity中实现字段/枚举编辑器中显示中文(中文枚举、中文标签)

    在unity开发编辑器相关经常会碰到定义的字段显示在Inspector是中文,枚举也经常碰到显示的是字段定义时候的英文,程序还好,但是如果编辑器交给策划编辑,策划的英文水平不可保证,会很头大,所以还是 ...

  2. React Hooks方法

    1.useState import React, { useState } from "react"; /* 目标: 掌握useState的使用 作用:实现响应式数据的 用法:引入 ...

  3. 2022-08-16:绳子总长度为M, 100 -> M, (6, 100) (7,23) (10,34) -> arr, 每一个长度的绳子对应一个价格,比如(6, 10)表示剪成长度为6的绳子,对应

    2022-08-16:绳子总长度为M, 100 -> M, (6, 100) (7,23) (10,34) -> arr, 每一个长度的绳子对应一个价格,比如(6, 10)表示剪成长度为6 ...

  4. var,let,const的区别

    JS中变量的定义方式有四种 不写var,let,const--直接定义变量 a = 10; 使用var关键字定义 var a = 10; 使用let关键字定义 let a = 10; 使用const关 ...

  5. 深入理解 python 虚拟机:破解核心魔法——反序列化 pyc 文件

    深入理解 python 虚拟机:破解核心魔法--反序列化 pyc 文件 在前面的文章当中我们详细的对于 pyc 文件的结构进行了分析,pyc 文件主要有下面的四个部分组成:魔术. Bite Filed ...

  6. ES5 apply与call详解

    虽然es6已经出台了很多简单的方法替代了apply和call,但是还是有很多老大项目使用到了es5的这些方法,所以对于这些方法的掌握是有必要的 先回顾一下官方对apply.call的诠释 apply方 ...

  7. springboot+springsecurity+jwt+elementui图书管理系统

    ​​图书管理系统​​ 一.springboot后台 1.mybatis-plus整合 1.1添加pom.xml <!--mp逆向工程 --> <dependency> < ...

  8. Linux常用磁盘管理命令详解

    du du命令用于查看文件和目录磁盘的使用空间. 命令语法:du [参数] [文件或目录名称] 参数说明: 参数 说明 -a 列出所有的文件与目录容量. -h 以G.M.K为单位,返回容量. -s 列 ...

  9. 我们的智能化应用是需要自动驾驶(Autopilot)还是副驾驶(Copilot)

    自动驾驶Autopilot 是一个知识密集且科技含量很高的技术,不基于点什么很难把它讲的相对清楚. 副驾驶 Copilot 是一种由 AI 提供支持的数字助理,旨在为用户提供针对一系列任务和活动的个性 ...

  10. 驱动开发:内核实现SSDT挂钩与摘钩

    在前面的文章<驱动开发:内核解析PE结构导出表>中我们封装了两个函数KernelMapFile()函数可用来读取内核文件,GetAddressFromFunction()函数可用来在导出表 ...