首页 前端 正文

跟着webpack产物来学习webpack是如何做模块处理

   一、思考    webpack是何如实现 ESM(Es Module) 与 CMS(Commonjs) 相互导出引用呢?带着这些问题,咱们通过简单文件的打包来分析不同类型的模块是如何处理的    二、极简单的代码    webpack.config.js const path = require("path")

一、思考

Webpack是何如实现 ESM(Es Module) 与 CMS(Commonjs) 相互导出引用呢?带着这些问题,咱们通过简单文件的打包来分析不同类型的模块是如何处理的

二、极简单的代码

webpack.config.js

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  devtool: "none",
  mode: "development",
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
};

index.js

const home = require("./home");
console.log("Hello World");
console.log(home);

home.js

module.exports = "我是主页面";

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>跟着webpack产物来学习webpack是如何做模块处理</title>
  </head>
  <body>
    <h1>跟着webpack产物来学习webpack是如何做模块处理</h1>
  </body>
</html>

三、分析产物的公共方法和属性

1. 完整的产物

(function (modules) {
  // webpackBootstrap
  // The module cache
  var installedModules = {};
  // The require function
  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: {},
    });
    // Execute the module function
    modules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __webpack_require__
    );
    // Flag the module as loaded
    module.l = true;
    // Return the exports of the module
    return module.exports;
  }
  // expose the modules object (__webpack_modules__)
  const home = require("./home");
console.log("Hello World");
console.log(home);0
  // expose the module cache
  const home = require("./home");
console.log("Hello World");
console.log(home);1
  // define getter function for harmony exports
  __webpack_require__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter });
    }
  };
  // define __esModule on exports
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
    }
    Object.defineProperty(exports, "__esModule", { value: true });
  };
  // create a fake namespace object
  // mode & 1: value is a module id, require it
  // mode & 2: merge all properties of value into the ns
  // mode & 4: return value when already ns object
  // mode & 8|1: behave like require
  __webpack_require__.t = function (value, mode) {
    if (mode & 1) value = __webpack_require__(value);
    if (mode & 8) return value;
    if (mode & 4 && typeof value === "object" && value && value.__esModule)
      return value;
    var ns = Object.create(null);
    __webpack_require__.r(ns);
    Object.defineProperty(ns, "default", { enumerable: true, value: value });
    if (mode & 2 && typeof value != "string")
      for (var key in value)
        __webpack_require__.d(
          ns,
          key,
          function (key) {
            return value[key];
          }.bind(null, key)
        );
    return ns;
  };
  // getDefaultExport function for compatibility with non-harmony modules
  __webpack_require__.n = function (module) {
    var getter =
      module && module.__esModule
        ? function getDefault() {
            return module["default"];
          }
        : function getModuleExports() {
            return module;
          };
    __webpack_require__.d(getter, "a", getter);
    return getter;
  };
  // Object.prototype.hasOwnProperty.call
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty.call(object, property);
  };
  // __webpack_public_path__
  const home = require("./home");
console.log("Hello World");
console.log(home);2
  // Load entry module and return exports
  return __webpack_require__((__webpack_require__.s = "./src/index.js"));
})({
  "./src/home.js":
    /*! no static exports found */
    function (module, exports) {
      module.exports = "我是主页面";
    },
  "./src/index.js":
    /*! no static exports found */
    function (module, exports, __webpack_require__) {
      const home = __webpack_require__(/*! ./home */ "./src/home.js");
      console.log("Hello World");
      console.log(home);
    },
});

是一个IIFE模块,实参是moduleId 和 对应的模块(函数)

2. 公共方法分析

a. 模块导入方法:__webpack_require__

// 模块导入方法:缓存模块信息,执行模块函数,将模块的导出return出去
function __webpack_require__(moduleId) {
  // 缓存已经加载过的模块,比如:key是 ./src/index.js,value 是模块信息
  if (installedModules[moduleId]) {
    // 将模块导出的内容 return 出去
    return installedModules[moduleId].exports;
  }
  // 创建一个新的模块,并将其放入缓存中
  var module = (installedModules[moduleId] = {
    i: moduleId, // 模块的id  比如:./src/index.js
    l: false, // 标记当前模块是否被加载 当时是未被加载
    exports: {}, // 存储模块导出的内容
  });
  // Execute the module function
  // 执行模块函数
  modules[moduleId].call(
    module.exports, // 模块内的this执行的是模块导出的内容
    module, // 当前的模块
    module.exports, // 存储模块导出的内容
    __webpack_require__ // // 导入方法
  );
  // 标记当前模块已经被加载
  module.l = true;
  // 返回模块导出的内容
  return module.exports;
}

b. 判断对象上是否有 xxx 属性

// 判断对象上是否有 xxx 属性
__webpack_require__.o = function (object, property) {
  return Object.prototype.hasOwnProperty.call(object, property);
};

c. 向exports对象上自定义属性和属性的getter方法

剧透:向ESM的exports添加导出内容,后面有具体的分析

// 向exports对象上自定义属性和属性的getter方法
__webpack_require__.d = function (exports, name, getter) {
// 当前 导出对象中没有有 xxx 属性时候
if (!__webpack_require__.o(exports, name)) {
// 向 exports 对象添加 xxx 属性,并设置值为 getter 方法的返回值,可枚举的
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};

d.标记 exports 是 esModule 模块方法

// 标记 exports 是 esModule 模块
__webpack_require__.r = function (exports) {
  // 自定义 exports.toString 为 [object Module],标记当前 exports 是 esModule 模块
  if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
  }
  // 在exports添加属性 __esModule 值为 true
  Object.defineProperty(exports, "__esModule", { value: true });
};

e.兼容 ESM 和 CMS 默认导出

__webpack_require__.n = function (module) {
  // 如果是 esModule 模块,创建 getter 获取 模块 default 值
  // 非 esModule 模块,创建 getter 获取 模块 exports 值
  var getter =
    module && module.__esModule
      ? function getDefault() {
          return module["default"];
        }
      : function getModuleExports() {
          return module;
        };
  // 在 getter 函数添加 a 为当前模块 默认 导出的值
  __webpack_require__.d(getter, "a", getter);
  // 获取 当前模块 默认 导出的值 的 getter
  return getter;
};

f.当所有的modules 挂在到 __webpack_require__ 方法上

const home = require("./home");
console.log("Hello World");
console.log(home);0

g. 模块的缓存,挂在到 __webpack_require__ 方法上

const home = require("./home");
console.log("Hello World");
console.log(home);1

h. 配置的public path 挂在到 __webpack_require__ 方法上

const home = require("./home");
console.log("Hello World");
console.log(home);2

以上属于挂在到  __webpack_require__ 方法上 主要是为了后面方便获取对应的值

四、分析ESM(Es Module) 与 CMS(Commonjs)的相互导出引用

1. 导出:CMS, 导入:CMS

a.源码

// index.js -- 导入
const home = require("./home");
console.log("Hello World");
console.log(home);

// home.js -- 导出
module.exports = "我是主页面";

b. 产物分析

实参

{
  "./src/home.js": function (module, exports) {
    module.exports = "我是主页面";
  },
  "./src/index.js": function (module, exports, __webpack_require__) {
    const home = __webpack_require__(/*! ./home */ "./src/home.js");
    console.log("Hello World");
    console.log(home);
  },
}

跟着webpack产物来学习webpack是如何做模块处理  第1张

由上图可看出将 ./src/index.js 作为moduleId传给 __webpack_require__
这个函数作用:模块导入方法:缓存模块信息,执行模块函数,将模块的导出return出去 具体这个函数坐上,到【公共方法分析】查看,这里注重分析模块执行 跟着webpack产物来学习webpack是如何做模块处理  第2张 上面执行加载 home 模块,并将home导出的内容答应出来,下面看看加载home的时候,怎样将内容导出去 跟着webpack产物来学习webpack是如何做模块处理  第3张 由上图可以值,“index.js” 中home 值是 “我是主页面”

2. 导出:CMS, 导入:ESM

a.源码

const home = require("./home");
console.log("Hello World");
console.log(home);6

// home.js -- 导出
module.exports = "我是主页面";

b. 产物分析

实参

{
  "./src/home.js": function (module, exports) {
    module.exports = "我是主页面";
  },
  "./src/index.js": function (
    module,
    __webpack_exports__,
    __webpack_require__
  ) {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    var _home__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
      /*! ./home */ "./src/home.js"
    );
    var _home__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(
      _home__WEBPACK_IMPORTED_MODULE_0__
    );

    console.log(_home__WEBPACK_IMPORTED_MODULE_0___default.a);
  },
}

执行顺序和上面的一样,咱们直接分析 ./src/index.js 模块的执行

跟着webpack产物来学习webpack是如何做模块处理  第4张 首先执行 __webpack_require__.r(__webpack_exports__);,这个函数就是标记 当前模块exports 对象 为 esModules 跟着webpack产物来学习webpack是如何做模块处理  第5张 接着加载home模块

const home = require("./home");
console.log("Hello World");
console.log(home);9

此时 _homeWEBPACK_IMPORTED_MODULE_0 值 为 “我是主页面” 跟着webpack产物来学习webpack是如何做模块处理  第6张 接着 做一波 兼容 ESM 和 CMS 默认导出 `webpack_require.n` 的操作

module.exports = "我是主页面";0

跟着webpack产物来学习webpack是如何做模块处理  第7张跟着webpack产物来学习webpack是如何做模块处理  第8张 可以看出,_home__WEBPACK_IMPORTED_MODULE_0___default就是一个模块导出的的getter,获取home的值。
最后 console.log(_home__WEBPACK_IMPORTED_MODULE_0___default.a); 打印的就是 ‘我是主页面’

3. 导出:ESM, 导入:CMS

a.源码

module.exports = "我是主页面";1

module.exports = "我是主页面";2

b. 产物分析

实参

module.exports = "我是主页面";3

执行顺序和上面的一样,咱们直接分析 ./src/index.js 模块的执行 跟着webpack产物来学习webpack是如何做模块处理  第9张 这块,上面其实已经分析过了,加载 home模块,并将home导出的内容打印出来,核心关注 home是做模块导出的 跟着webpack产物来学习webpack是如何做模块处理  第10张 从这里就能看到 ./src/index.js 打印的default 和 name 就是 home模块 的 "Hello World" 和 "我是主页面"

4. 导出:ESM, 导入:ESM

a.源码

module.exports = "我是主页面";1

module.exports = "我是主页面";2

b. 产物分析

实参

module.exports = "我是主页面";6

执行顺序和上面的一样,咱们直接分析 ./src/index.js 模块的执行 跟着webpack产物来学习webpack是如何做模块处理  第11张 那么 我们去看看 home 模块做了哪些 跟着webpack产物来学习webpack是如何做模块处理  第12张 从这里就能看到 ./src/index.js 打印的default 和 name 就是 home模块 的 "Hello World" 和 "我是主页面"

五、总结

  1. ESM导出做了三步:
    a. 标记当前导出对象为 ES Module
    b. 如果导出 有default,直接将值挂在到 导出对象的 default属性上
    c. 如果导出具名的内容,首先将当前属性通过 defineProperty 定义在 导出对象上,getter就是获取当前作用于下的该属性,并在当前模块定声明该属性并赋值

  2. CMS导出:直接将内容挂在到 导出的对象上

  3. ESM导入做了四步:
    a. 标记当前导出对象为 ES Module
    b. 导入被导入的模块
    c. 对导入的内容 default 做 ESM 和 CMS的 getter 兼容
    d. 在getter的a属性获取被导入的值

  4. CMS导入:直接将导入的内容打印出来即可

原文:https://juejin.cn/post/7099506941390487565
打赏
海报

本文转载自互联网,旨在分享有价值的内容,文章如有侵权请联系删除,部分文章如未署名作者来源请联系我们及时备注,感谢您的支持。

转载请注明本文地址:https://www.shouxicto.com/article/5111.html

相关推荐

IPv4和IPv6何去何从

IPv4和IPv6何去何从

   一、思考    webpack是何如实现 ESM(Es Module) 与 CMS(Commonjs) 相互导出引用呢...

前端 2022.06.26 0 39

发布评论

ainiaobaibaibaibaobaobeishangbishibizuichiguachijingchongjingdahaqiandaliandangaodw_dogedw_erhadw_miaodw_tuzidw_xiongmaodw_zhutouganbeigeiliguiguolaiguzhanghahahahashoushihaixiuhanheixianhenghorse2huaixiaohuatonghuaxinhufenjiayoujiyankeaikeliankouzhaokukuloukunkuxiaolandelinileimuliwulxhainiolxhlikelxhqiuguanzhulxhtouxiaolxhwahahalxhzanningwennonuokpinganqianqiaoqinqinquantouruoshayanshengbingshiwangshuaishuijiaosikaostar0star2star3taikaixintanshoutianpingtouxiaotuwabiweifengweiquweiwuweixiaowenhaowoshouwuxiangjixianhuaxiaoerbuyuxiaokuxiaoxinxinxinxinsuixixixuyeyinxianyinyueyouhenghengyuebingyueliangyunzanzhajizhongguozanzhoumazhuakuangzuohenghengzuoyi
支付宝
微信
赞助本站