CcbeanBlog CcbeanBlog
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Ccbean

靡不有初,鲜克有终
首页
  • 前端文章

    • JavaScript
    • HTML+CSS
    • Vue
    • React
  • 系列笔记

    • React使用学习
    • Vue2源码探究
  • Node文章

    • 基础
    • 问题
    • 框架
  • 系列笔记

    • 数据结构与算法
  • 构建工具文章

    • webpack
  • 系列笔记

    • Webpack5使用学习
  • MySQL
  • Linux
  • 网络
  • 小技巧
  • 杂记
  • 系列笔记

    • Protobuf Buffers
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 邂逅Webpack
  • Webpack的配置和处理CSS资源
  • Webpack加载和处理其它资源
  • Webpak模块化原理
    • mode配置简介
    • Webpack的模块化
      • CommonJS模块化实现原理
      • ES Module模块化实现原理
      • CommonJS、ES Module可相互加载的原理
  • Webpack中的source-map
  • Webpack中的babel
  • Webpack中的DevServer和HMR
  • Webpack环境分离和代码分离
  • Webpack中的DLL、Terser和ScopeHoisting
  • Webpack中的TreeShaking以及其它优化
  • Webpack打包分析
  • Webpack自定义Loader
  • Webpack自定义Plugin
  • 分析React和Vue脚手架
  • Gulp的使用
  • Rollup的使用
  • Vite的使用
  • Webpack5使用学习
ccbean
2022-04-21
目录

Webpak模块化原理

# Webpak模块化原理

# mode配置简介

https://webpack.docschina.org/configuration/mode/

使用mode 配置选项,告知 webpack 使用相应模式的内置优化。具体优化项详见mode: development (opens new window)。

webpack默认mode值是production,可选值有:

  • none:不使用任何默认优化选项
  • development: 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
  • production:会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin 和 TerserPlugin 。

注意

  • 如果 mode 未通过配置或 CLI 赋值,CLI 将使用可能有效的 NODE_ENV 值作为 mode。
  • 设置NODE_ENV并不会自动地设置mode。

使用方式

在配置对象中提供 mode 选项

module.exports = {
  mode: 'production'
};

或在命令行中:

webpack --mode=production

# Webpack的模块化

Webpack打包的代码,允许我们使用各种各样的模块化,最常用的CommonJS、ES Module。

那么它是如何帮助我们实现了代码中支持模块化呢?

我们来研究一下它的原理,包括如下原理:

  • CommonJS模块化实现原理;
  • ES Module模块化实现原理;
  • CommonJS加载ES Module的原理;
  • ES Module加载CommonJS的原理;

设置mode和devtool属性如下:

module.exports = {
  mode: 'development',
  devtool: 'source-map'
};

# CommonJS模块化实现原理

我们有CommonJS规范实现的format.js文件

const dateFormat = (date) => {
  return "2020-12-12";
}

const priceFormat = (price) => {
  return "100.00";
}

module.exports = {
  dateFormat,
  priceFormat
}

main.js对该模块进行引用如下:

const { dateFormat, priceFormat } = require('./js/format');

console.log(dateFormat("1213"));
console.log(priceFormat("1213"));

执行打包命令npm run build,我们可以得到bundle.js文件,去掉一些注释后如下:

(function () {
  // 依赖的模块
  var __webpack_modules__ = ({
    "./src/js/format.js": (function (module) {
        const dateFormat = (date) => {
          return "2020-12-12";
        }

        const priceFormat = (price) => {
          return "100.00";
        }

        module.exports = {
          dateFormat,
          priceFormat
        }
      })
  });

  // 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] = {
      exports: {}
    };

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

    // Return the exports of the module
    return module.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.
  !function () { // 感叹号会将IIFE函数转为表达式,去掉则会报错。 或者使用括号来包裹IIFE
    const { dateFormat, priceFormat } = __webpack_require__("./src/js/format.js");

    console.log(dateFormat("1213"));
    console.log(priceFormat("1213"));
  }();
})();

//# sourceMappingURL=bundle.js.map
  1. 整个文件是一个IIFE函数,起到模块隔离的作用,即作用域隔离。

  2. 定义变量__webpack_modules__,是一个对象,它存储的是代码中所有的依赖模块信息,key是模块标识符,value是一个函数funciton(module, exports, require),函数接收三个参数:

    • 第一个参数module:模块本身的module
    • 第二个参数exports:对module中exports属性的引用。也就是我们在代码中经常用来做到处使用的exports
    • 第三个参数require:require()函数,在模块中引用其它模块时常用到的require()函数。
    • 这也就是为什么我们在编写一个CommonJS模块时,能够在模块中使用module.exports、exports、require()的原因。同理,NodeJS中对模块函数的实现的函数签名为function (exports, require, module, __filename, __dirname)。
    • 这里看着之所以只有一个参数,是因为不需要后两个参数,webpack编译省略掉了
  3. 函数内部是该模块的业务逻辑代码。最后对module.exports进行赋值

  4. __webpack_module_cache__为模块加载后的缓存对象。

  5. 函数__webpack_require__(moduleId)就是webpack对CommonJS的require()函数的实现,函数接收一个参数模块标识符。

    • 当函数执行时,传入标识符moduleId,首先判断模块缓存__webpack_module_cache__对象中是否有对moduleId的缓存,如果有则直接返回。没有则执行下面的加载过程。这样可以在模块多次加载时,避免多次执行模块函数。

    • 模块加载,创建键为模块标识符moduleId,值为模块函数__webpack_modules__[moduleId](module, module.exports, __webpack_require__)的执行结果,即对module.exports进行赋值。这里也就正好传入了__webpack_modules__对象中函数签名定义的三个参数。

  • 最后,是入口文件entry相关代码,使用IIFE对入口进行包裹,这是为了隔离chunk中其它模块的污染,即作用域隔离。
    • 这里require被替换成了Webpack的对应实现__webpack_require__()

总结

和Node对CommonJS规范的实现类似。本质上Webpack自己实现了require函数。通过立即执行函数IIFE,给每个模块提供自己的函数作用域,实现模块隔离。将要导出的属性和方法赋值到module.exports上。

# ES Module模块化实现原理

我们有CommonJS规范实现的math.js文件。

export const sum = (num1, num2) => {
  return num1 + num2;
}

export const mul = (num1, num2) => {
  return num1 * num2;
}

main.js对该模块进行引用如下:

import { sum, mul } from './js/math.js';

console.log(sum(20, 30));
console.log(mul(20, 30));

执行打包命令npm run build,我们可以得到bundle.js文件,去掉一些注释后如下:

(function () {
  "use strict";
  var __webpack_modules__ = ({
    "./src/js/math.js":
      (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
        // module.exports上添加Symbol.toStringTag: 'Module'和`__esModule: true`属性
        __webpack_require__.r(__webpack_exports__);
        // 设置导出方法sum和mul的代理
        __webpack_require__.d(__webpack_exports__, {
          "mul": function () { return mul; },
          "sum": function () { return sum; }
        });

        const sum = (num1, num2) => {
          return num1 + num2;
        }

        const mul = (num1, num2) => {
          return num1 * num2;
        }
      })

  });
  // 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] = {
      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 */
  !function () {
    // define getter functions for harmony exports
    __webpack_require__.d = function (exports, definition) {
      for (var key in definition) {
        if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
          // 生成访问器属性
          // {
          //   exports: {
          //     mul: /* getter */ function () { return mul; },
          //     sum: /* getter */ function () { return sum; }
          //   }
          // }
          Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
        }
      }
    };
  }();

  /* webpack/runtime/hasOwnProperty shorthand */
  !function () {
    __webpack_require__.o = function (obj, prop) {
      return Object.prototype.hasOwnProperty.call(obj, prop);
    }
  }();

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

  var __webpack_exports__ = {};
  // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
  !function () {
    __webpack_require__.r(__webpack_exports__);
    var _js_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/math.js");

    // 此语法等价于: _js_math_js__WEBPACK_IMPORTED_MODULE_0__.sum(20, 30);
    console.log((0, _js_math_js__WEBPACK_IMPORTED_MODULE_0__.sum)(20, 30));
    console.log((0, _js_math_js__WEBPACK_IMPORTED_MODULE_0__.mul)(20, 30));
  }();
})();

//# sourceMappingURL=bundle.js.map
  1. 最外层包裹IIFE函数,进行作用域隔离。

  2. 定义变量__webpack_modules__,是一个对象。key为模块标识符,value是一个函数function (__unused_webpack_module, __webpack_exports__, __webpack_require__),接收三个参数

    • __unused_webpack_module
    • ``webpack_exports`
    • __webpack_require__
  3. 声明模块缓存对象__webpack_module_cache__

  4. 定义require函数function __webpack_require__(moduleId),接收模块标识符作为参数。

    • 首先从缓存中加载模块,如果模块存在则直接返回。不存在则继续执行
    • 执行模块函数__webpack_modules__[moduleId](module, module.exports, __webpack_require__)加载模块
  5. require函数上定义了三个属性d、o、r,分别是三个方法

    • __webpack_require__.d(),给模块中要导出的属性和方法设置一个访问器属性代理到module.exports上

    • __webpack_require__.o(),hasOwnProperty方法的简写

    • __webpack_require__.r(),给module.exports对象上设置数据属性Symbol.toStringTag: 'Module'和__esModule: true

  6. 执行入口文件

总结

ES Module 通过Object.defineProperty给导出的属性和方法设置代理。同时在module.exports上设置了____esModule: true,以及有Symbol时,设置Symbol(Symbol.toStringTag): 'Module',用来标识这是一个ESModule。

CommonJS是将要导出的属性和方法赋值到module.exports上。而ESModule是要将导出的属性和方法设置代理到module.exports上。

# CommonJS、ES Module可相互加载的原理

main.js对该模块进行引用如下:

// commonjs模式导入es module
const math = require('./js/math');
const { sum, mul } = require('./js/math');
// es module模式导入commonjs
import format, { dateFormat, priceFormat } from './js/format';

console.log(math.sum(20, 30));
console.log(math.mul(20, 30));

console.log(sum(20, 30));
console.log(mul(20, 30));

console.log(format.dateFormat("1213"));
console.log(format.priceFormat("1213"));

console.log(dateFormat("1213"));
console.log(priceFormat("1213"));

执行打包命令npm run build,我们可以得到bundle.js文件,去掉一些注释后如下:

(function () {
  var __webpack_modules__ = ({
    "./src/js/format.js":
      (function (module) {
        const dateFormat = (date) => {
          return "2020-12-12";
        }
        const priceFormat = (price) => {
          return "100.00";
        }

        module.exports = {
          dateFormat,
          priceFormat
        }
      }),
    "./src/js/math.js":
      (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
        "use strict";
        // module.exports上添加Symbol.toStringTag: 'Module'和`__esModule: true`属性
        __webpack_require__.r(__webpack_exports__);
        // 设置导出方法sum和mul的代理 即 设置访问器getter属性
        __webpack_require__.d(__webpack_exports__, {
          "mul": function () { return mul; },
          "sum": function () { return sum; }
        });
        const sum = (num1, num2) => {
          return num1 + num2;
        }

        const mul = (num1, num2) => {
          return num1 * num2;
        }
      })
  });
  // 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] = {
      exports: {}
    };

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

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

  /* webpack/runtime/compat get default export */
  !function () {
    // getDefaultExport function for compatibility with non-harmony modules
    __webpack_require__.n = function (module) {
      var getter = module && module.__esModule ?
        function () { return module['default']; } :
        function () { return module; };
      __webpack_require__.d(getter, { a: getter });
      return getter;
    };
  }();

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

  /* webpack/runtime/make namespace object */
  !function () {
    // 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 });
    };
  }();

  var __webpack_exports__ = {};
  // This entry need to be wrapped in an IIFE because it need to be in strict mode.
  !function () {
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    var _js_format__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/js/format.js");
    var _js_format__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(_js_format__WEBPACK_IMPORTED_MODULE_0__);
    // commonjs模式导入es module
    const math = __webpack_require__("./src/js/math.js");
    const { sum, mul } = __webpack_require__("./src/js/math.js");

    console.log(math.sum(20, 30));
    console.log(math.mul(20, 30));
    // commonjs的导入方式会解构赋值 无论导入模块是哪种格式的
    console.log(sum(20, 30));
    console.log(mul(20, 30));

    // esmodule的导入通过代理实现 无论导入的模块是哪种格式的
    console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().dateFormat("1213"));
    console.log(_js_format__WEBPACK_IMPORTED_MODULE_0___default().priceFormat("1213"));

    console.log((0, _js_format__WEBPACK_IMPORTED_MODULE_0__.dateFormat)("1213"));
    console.log((0, _js_format__WEBPACK_IMPORTED_MODULE_0__.priceFormat)("1213"));
  }();
})();
//# sourceMappingURL=bundle.js.map
编辑 (opens new window)
上次更新: 2022/04/29, 22:09:58
Webpack加载和处理其它资源
Webpack中的source-map

← Webpack加载和处理其它资源 Webpack中的source-map→

最近更新
01
阅读精通正则表达式总结
09-29
02
项目搭建规范的配置
07-15
03
Vite的使用
07-03
更多文章>
Theme by Vdoing | Copyright © 2018-2023 Ccbeango
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式