webpack 打包文件分析

108 阅读7分钟

webpack 打包文件分析



阅读代码准备

// ./sub.js
export default function sub(a, b) {
  return a - b;
}
// ./index.js
import sub from "./sub";
const res = sub(1, 2);
console.log(res);
// ./webpack.config.js
const path = require("path");
module.exports = {
  entry: "./index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
};
npm install webpack webpack-cli -D # 安装依赖
npm run build # 构建

打包后的代码(已删除部分注释)

// bundle.js
(() => {
  // webpackBootstrap
  "use strict";
  var __webpack_modules__ = {
    "./src/index.js": (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      var _sub__WEBPACK_IMPORTED_MODULE_0__ =
        __webpack_require__("./src/sub.js");
      const res = (0, _sub__WEBPACK_IMPORTED_MODULE_0__["default"])(10, 5);
      console.log("**** res ****", res);
    },

    "./src/sub.js": (
      __unused_webpack_module,
      __webpack_exports__,
      __webpack_require__
    ) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        default: () => Sub,
      });
      function Sub(a, b) {
        return a - b;
      }
    },
  };
  // 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];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    // Create a new module (and put it into the cache)
    var module = (__webpack_module_cache__[moduleId] = {
      // no module.id needed
      // no module.loaded needed
      exports: {},
    });

    // Execute the module function
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    // Return the exports of the module
    return module.exports;
  }

  /* webpack/runtime/define property getters */
  (() => {
    // define getter functions for harmony exports
    __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/runtime/hasOwnProperty shorthand */
  (() => {
    __webpack_require__.o = (obj, prop) =>
      Object.prototype.hasOwnProperty.call(obj, prop);
  })();

  /* webpack/runtime/make namespace object */
  (() => {
    // define __esModule on exports
    __webpack_require__.r = (exports) => {
      if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
      }
      Object.defineProperty(exports, "__esModule", { value: true });
    };
  })();

  // startup
  // Load entry module and return exports
  // This entry module can't be inlined because the eval devtool is used.
  var __webpack_exports__ = __webpack_require__("./src/index.js");
})();

代码解读


1. 立即执行函数 (IIFE):魔法城堡的大门

整个 bundle.js 文件是一个立即执行函数(IIFE),用于执行 Webpack 的构建逻辑。这确保了所有的模块和变量都在一个封闭的作用域中,避免污染全局命名空间。

(() => {
  // webpackBootstrap
  "use strict";
  // ...
})();

比喻:想象你站在一座宏伟的魔法城堡前,这座城堡是 Webpack 的魔法工厂。当你推开这扇巨大的门(自执行函数),你就进入了这个充满魔法的世界。这扇门确保所有的魔法都在一个安全的环境中进行,不会影响外界。

意义:使用 IIFE 确保所有模块和变量都在一个封闭的作用域中,避免污染全局命名空间。这样可以防止与其他代码冲突,并提供更好的封装性。

2. 模块集合 (__webpack_modules__):魔法配方库

__webpack_modules__ 变量表示所有模块的集合,键为模块的 id,值为一个函数,表示模块的加载函数。每个模块的定义被包装成一个函数,以便在需要时调用

var __webpack_modules__ = {
  "./src/index.js": (
    __unused_webpack_module,
    __webpack_exports__,
    __webpack_require__
  ) => {
    // 模块内容
  },
  // 其他模块...
};

比喻:在城堡的深处,有一个巨大的魔法配方库(__webpack_modules__)。每个模块都有自己的独特配方,比如 ./src/index.js./src/sub.js。这些配方告诉 Webpack 如何制作每个模块,最终将它们组合成一个完整的魔法药剂(你的应用程序)。

意义:将所有模块的定义集中存储在一个对象中,方便 Webpack 在需要时查找和加载模块。每个模块被包装成一个函数,确保模块的独立性和可复用性。

3. 模块缓存 (__webpack_module_cache__):魔法仓库

__webpack_module_cache__ 变量表示所有模块的缓存,键为模块的 id,值为对象,包含模块的导出。这确保了已经加载过的模块不会重复加载,提高了性能。

var __webpack_module_cache__ = {};

比喻:接下来是一个巨大的魔法仓库(__webpack_module_cache__),用来存放已经处理过的模块。每次你调用 __webpack_require__ 函数时,Webpack 都会先检查这个仓库,看看是否已经有了这个模块。如果有,就直接返回;如果没有,就会创建一个新的模块并存入仓库。这样可以避免重复劳动,提高效率。

意义:缓存已经加载的模块,避免重复加载,提高性能。这对于大型项目尤其重要,可以显著减少不必要的计算和资源消耗。

4. 模块加载函数 (__webpack_require__):魔法传送门

__webpack_require__ 函数表示模块的加载逻辑。它会检查模块是否已经在缓存中,如果存在则直接返回缓存的结果;否则,它会执行模块的定义函数,并将结果存入缓存。

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__ 就像一个魔法传送门,你可以通过它瞬间到达你需要的任何模块。每次你调用这个函数,Webpack 都会检查它的仓库(__webpack_module_cache__),看看是否已经有了这个模块。如果有,就直接返回;如果没有,就会创建一个新的模块并存入仓库。这个过程就像是传送门带你去到不同的房间,每个房间都是一个模块。

意义:提供一个统一的接口来加载模块,确保模块的加载顺序和依赖关系正确。通过缓存机制,提高了模块加载的效率。

5. 工具函数:魔法助手

Webpack 提供了一些辅助函数来处理模块的导出和属性检查:

  • __webpack_require__.r :魔法标志

exports 中定义 __esModule 属性,值为 true ,以确保模块可以正确地使用 ES 模块语法。

__webpack_require__.r = (exports) => {
  if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
    Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
  }
  Object.defineProperty(exports, "__esModule", { value: true });
};

比喻:__webpack_require__.r 是一个魔法标志,确保每个模块都有一个特殊的标签(__esModule),以便其他模块知道如何正确地使用它。就像给每个魔法药剂贴上标签,确保使用者知道这是什么药剂以及如何使用。

意义:确保模块导出的内容符合 ES 模块规范,使得其他模块可以正确识别和使用这些导出。

  • __webpack_require__.d: 魔法挂载器

在 exports 上挂载要导出的方法或变量。

__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__.d是一个魔法挂载器,负责将模块的导出内容挂载到 exports 上。它像一个魔法挂钩,把模块的功能一个个挂上去,确保其他模块可以方便地访问这些功能。

意义:在 exports 上挂载要导出的方法或变量,确保模块的导出内容可以在外部被正确引用和使用。

  • __webpack_require__.o : 魔法检测器

判断对象是否包含指定的属性

__webpack_require__.o = (obj, prop) =>
  Object.prototype.hasOwnProperty.call(obj, prop);

比喻:__webpack_require__.o 是一个魔法检测器,用于判断对象是否包含指定的属性。它像一个魔法探测器,可以快速准确地告诉你某个魔法药剂是否含有特定成分。

意义:提供一个简短的方式检查对象是否包含某个属性,简化了代码逻辑,提高了代码的可读性和维护性。

6. 启动函数

最后,有一个启动函数用于加载入口模块并开始执行应用逻辑。这个函数会调用 __webpack_require__ 来加载入口模块,并将结果赋值给 __webpack_exports__

比喻:最后,到了启动的时候了!Webpack 会从入口模块(./src/index.js)开始,像举行一场盛大的魔法仪式一样,逐步加载和执行所有的模块。最终,所有的魔法都汇聚在一起,形成了一个完整的魔法药剂(也就是你的应用程序)。这个过程就像是点燃了一个巨大的魔法火焰,照亮了整个城堡。

意义:加载入口模块并开始执行应用逻辑,确保整个应用能够正常启动和运行。它是整个打包文件的起点,触发了后续的所有操作。

7. 总结

  • 整体立即执行

整个文件是一个立即执行函数。

  • 两个主要变量:

__webpack_modules__:模块集合。 __webpack_module_cache__:模块缓存。

  • 三个工具函数:

__webpack_require__.r:定义 **esModule 属性。 **webpack_require**.d:挂载导出的方法或变量。 **webpack_require__.o:检查对象属性。

  • 一个启动函数

加载入口模块并开始执行应用逻辑。

即:整体立即执行,两个变量,三个工具函数,一个启动函数,简称 2+3+1 模式