简单分析 Webpack 的构建产物

473 阅读3分钟

为什么选择 Webpack

以下是引用 Webpack 官方网的部分原话

想要理解为什么要使用 webpack,我们先回顾下历史,在打包工具出现之前,我们是如何在 web 中使用 JavaScript 的。

在浏览器中运行 JavaScript 有两种方法。第一种方式,引用一些脚本来存放每个功能;此解决方案很难扩展,因为加载太多脚本会导致网络瓶颈。第二种方式,使用一个包含所有项目代码的大型 .js 文件,但是这会导致作用域、文件大小、可读性和可维护性方面的问题。

来自 Web 项目的好消息是,模块正在成为 ECMAScript 标准的官方功能。然而,浏览器支持不完整,版本迭代速度也不够快,目前还是推荐上面那些早期模块实现。

是否可以有一种方式,不仅可以让我们编写模块,而且还支持任何模块格式(至少在我们到达 ESM 之前),并且可以同时处理资源和资产?

这就是 webpack 存在的原因。它是一个工具,可以打包你的 JavaScript 应用程序(支持 ESM 和 CommonJS)。

Webpack 构建出来的产物是什么

通过构建产物我们分析以下三个概念

  • 模块注册器:webpack_modules
  • 模块缓存器:webpack_module_cache
  • 模块加载器:webpack_require

首先看下 CommonJS 模式

// count.js 文件
const count = 10;
exports.count = count;

// list.js 文件
const list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
module.exports = {
  list,
};

// index.js 文件
const list = require('./course/list');
const count = require('./course/count');

console.log(list);
console.log(count);

// 打包后的产物
(() => {
    const __webpack_modules__ = ({
      './src/course/count.js':
        ((__unused_webpack_module, exports) => {
          const count = 10;
          exports.count = count
        }),

      './src/course/list.js':
        ((module) => {
          const list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
          module.exports = {
            list,
          }
        })
    });
    const __webpack_module_cache__ = {};

    function __webpack_require__(moduleId) {
      const cachedModule = __webpack_module_cache__[moduleId];
      if (cachedModule !== undefined) {
        return cachedModule.exports;

      }
      const module = __webpack_module_cache__[moduleId] = {
        exports: {}
      };

      __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

      return module.exports;

    }

    const __webpack_exports__ = {};

    (() => {
      const list = __webpack_require__('./src/course/list.js')
      const count = __webpack_require__('./src/course/count.js')
      console.log(list)
      console.log(count)
    })();
  })();

可以看到 webpack_moduleswebpack_module_cache 是一个以文件路径为 key, 文件内容为 value 的对象,webpack_require 是以文件路径为参数获取文件内容的函数。我们使用模块的时候也就是通过 moduleId 来加载模块文件。

再来看下 ESM 模式

// count.js 文件
const count = 10;
export default count;

// list.js 文件
const list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
export default list;

// index.js 文件
import list from './course/list';
import count from './course/count';

console.log(list);
console.log(count);

// 打包后的产物
(() => {
  const __webpack_modules__ = ({
    './src/course/count.js':
      ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, {
          'default': () => (__WEBPACK_DEFAULT_EXPORT__)
        });
        const count = 10;
        const __WEBPACK_DEFAULT_EXPORT__ = (count);
      }),

    './src/course/list.js':
      ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, {
          'default': () => (__WEBPACK_DEFAULT_EXPORT__)
        });
        const list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
        const __WEBPACK_DEFAULT_EXPORT__ = (list);
      })
  });

  const __webpack_module_cache__ = {};

  function __webpack_require__(moduleId) {
    const cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }

    const module = __webpack_module_cache__[moduleId] = {
      exports: {}
    };

    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    return module.exports;
  }

  (() => {
    __webpack_require__.d = (exports, definition) => {
      for (const 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 });
    };
  })();

  const __webpack_exports__ = {};
  (() => {
    __webpack_require__.r(__webpack_exports__);
    const _course_list__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__('./src/course/list.js');
    const _course_count__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__('./src/course/count.js');

    console.log(_course_list__WEBPACK_IMPORTED_MODULE_0__['default']);
    console.log(_course_count__WEBPACK_IMPORTED_MODULE_1__['default']);
  })();
})();

可以看到 ESM 模式是在通过,首先在注册模块阶段通过 webpack_require.r 打上 __esModule 以及 Symbol.toStringTag 标记,然后通过 webpack_require.d 借助 Object.defineProperty 将模块注册到 webpack_exports 上。

总结

这是我对 Webpack 构建出来的文件产物分析,作为自己学习过程中的笔记记录下来,后续可能在学习中继续补充新的理解以及修改其中存在的错误。