Skip to content

Webpack 的模块机制

现在我们有以下的 4 个文件

  • main.js
  • common.js
  • module1.js
  • module2.js
js
// main.js
require("./common.js")
require("./module1.js")
import { module2Fun1 } from "./module2.js"

module2Fun1()
js
// common.js
export default {
  name: "common",
}
js
// module1.js
require("./common.js")

let module1Var1 = "module1Var1"
let module1Var2 = "module1Var2"

export const module1Fun1 = function () {
  console.log(`module1Fun1+${module1Var2}`)
}

export const module1Fun2 = function () {
  console.log(`module1Fun2`)
}

let a = 1
export const b = a
a = 2

export const module1Var3 = 10
js
// module2.js
require("./common.js")

let module2Var1 = "module2Var1"
let module2Var2 = "module2Var2"

export const module2Fun1 = function () {
  console.log(`module2Fun1+${module2Var2}`)
}

export const module2Fun2 = function () {
  console.log(`module2Fun2`)
}

然后我们将 webpack.config.js 配置成以下 options

js
module.exports = {
  // 禁用 webpack 内置优化
  mode: "none",
  // 入口文件
  entry: {
    main: "./public/js/main.js",
  },
  output: {
    filename: process.env.NODE_ENV === "production" ? "[name].[contenthash].js" : "[name].js",
    // 将输出的文件都放在dist目录下
    path: path.resolve(__dirname, "dist"),
  },

  optimization: {
    // 不压缩
    minimize: false,
  },
}

然后我们执行编译流程生成下面文件

js
/*

 webpack的模块化机制 其核心是一个 自执行函数 (function(modules){})([function module1(){}, function module2(){}, function module3(){} ])

*/

// modules 当前环境中所有模块的数组对象 那么我们就可以通过 modules[下标]() 去执行模块
;(function (modules) {
  // webpackBootstrap
  // The module cache
  // 已加载模块缓存
  var installedModules = {}

  // The require function
  /**
   *
   * require('./module1') 或者 import from './modulex.js' 的核心方法
   *
   * @param moduleId 模块的ID
   */
  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] = {
      // 模块的ID
      i: moduleId,
      // 模块是否加载过  默认为false
      l: false,
      //
      exports: {},
    })

    // Execute the module function
    // 真正的去执行加载的模块
    // 通过模块的下标去modules中找到相应的模块 然后执行此模块
    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__)
  // 暴露所有的模块对象
  __webpack_require__.m = modules

  // expose the module cache
  // 对外暴露模块中所有的缓存
  __webpack_require__.c = installedModules

  // define getter function for harmony exports
  /**
   * 定义当前模块exports 导出的变量
   *
   *  例如  export const name = 1;
   *
   *  exports = {
   *    name : function(){
   *     return 1;
   *   }
   * }
   *
   * @param exports 通过对象地址引用的方式传入一个当前模块导出的变量保存的对象
   * @param name  当前模块导出变量的名称
   * @param getter  当前模块导出变量的执行函数
   */
  __webpack_require__.d = function (exports, name, getter) {
    // 判断当前exports 对象上是否存在此属性
    if (!__webpack_require__.o(exports, name)) {
      // 通过 defineProperty 去定义此属性
      Object.defineProperty(exports, name, { enumerable: true, get: getter })
    }
  }

  // define __esModule on exports
  /**
   * 定义 exports 的 __esModule 属性值
   *
   * @param exports 当前模块对象 { i: , l: , 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
  }

  __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
  }

  /**
   * 判断property 是否是object对象自身包含的特定的自身(非继承)属性。
   */
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty.call(object, property)
  }

  // 公开的路径  publicPath
  __webpack_require__.p = ""

  // 从下标为0的第一个模块执行
  return __webpack_require__((__webpack_require__.s = 0))
})(
  /************************************************************************/
  [
    // main.js
    function (module, __webpack_exports__, __webpack_require__) {
      "use strict"
      __webpack_require__.r(__webpack_exports__)
      // import { module2Fun1 } from './module2.js';
      // 通过__webpack_require__(moduleId)去加载module2 因为有依赖的值,所以存在一个返回值 _module2_js__WEBPACK_IMPORTED_MODULE_0__
      var _module2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3)
      // require('./module1.js');  通过__webpack_require__(moduleId)去加载module1
      __webpack_require__(1)
      __webpack_require__(2)

      Object(_module2_js__WEBPACK_IMPORTED_MODULE_0__["module2Fun1"])()
    },
    // common.js
    function (module, __webpack_exports__, __webpack_require__) {
      "use strict"
      __webpack_require__.r(__webpack_exports__)

      // 对于default 直接使用 exports['default']去保存
      __webpack_exports__["default"] = {
        name: "common",
      }
    },
    // module1.js
    function (module, __webpack_exports__, __webpack_require__) {
      "use strict"
      __webpack_require__.r(__webpack_exports__)
      // 为什么需要通过 Object.defineProperty 将export的变量定义为一个函数的属性?
      // 因为对于webpack的模块化机制除了default 其他的export 都有一个提升的机制,会在函数的最上面定义export的变量的属性,
      // 那么如果不是函数那么 我们import module 这时候执行module函数,
      /*
			那么这种情况

			let a = 1;
			export const b = a ;
			a = 2;

			// 编译后的代码为 
			__webpack_require__.d(__webpack_exports__, 'b', a);
			let a = 1;
			a = 2;
			// 这时候发现 a是undefined

			如果是这样的话
			// 编译后的代码为 
			__webpack_require__.d(__webpack_exports__, 'b', function () {
				return a;
			});
			let a = 1;
			a = 2;
			// 只有执行到 我们需要 b 的时候才去获取当前的 a的值 这时候就是 2;

		
		    */
      __webpack_require__.d(__webpack_exports__, "module1Fun1", function () {
        return module1Fun1
      })
      __webpack_require__.d(__webpack_exports__, "module1Fun2", function () {
        return module1Fun2
      })
      __webpack_require__.d(__webpack_exports__, "module1Var3", function () {
        return module1Var3
      })

      __webpack_require__(1)

      let module1Var1 = "module1Var1"
      let module1Var2 = "module1Var2"

      const module1Fun1 = function () {
        console.log(`module1Fun1+${module1Var2}`)
      }

      const module1Fun2 = function () {
        console.log(`module1Fun2`)
      }

      const module1Var3 = 10
    },

    // module2.js
    function (module, __webpack_exports__, __webpack_require__) {
      "use strict"
      __webpack_require__.r(__webpack_exports__)
      __webpack_require__.d(__webpack_exports__, "module2Fun1", function () {
        return module2Fun1
      })
      __webpack_require__.d(__webpack_exports__, "module2Fun2", function () {
        return module2Fun2
      })
      __webpack_require__(1)

      let module2Var1 = "module2Var1"
      let module2Var2 = "module2Var2"

      const module2Fun1 = function () {
        console.log(`module2Fun1+${module2Var2}`)
      }

      const module2Fun2 = function () {
        console.log(`module2Fun2`)
      }
    },
  ]
)

从中我们发现 webpack 的模块化的机制是什么?

整体流程

webpack 将每一个文件变成一个 module,然后存放在一个统一的 modules 数组中,那么就可以通过 modules[下标]去访问对应的文件的函数,其中入口文件存放在 modules 数组的第一个,那么其初始化的时候就可以通过__webpack_require__((__webpack_require__.s = 0))去从入口文件开始执行整个流程

js
// 从下标为0的第一个模块执行
return __webpack_require__((__webpack_require__.s = 0))

那么其是怎么管理每一个依赖的?

上面我们知道所有的模块都存放在 modules 数组那么,那么在一个文件中通过 import或者 require 依赖的其他文件都变成

js
var _module2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2)

所以 webpack 的模块化核心就是 __webpack_require__

js
// The require function
/**
 *
 * require('./module1') 或者 import from './modulex.js' 的核心方法
 *
 * @param moduleId 模块的ID
 */
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] = {
    // 模块的ID
    i: moduleId,
    // 模块是否加载过  默认为false
    l: false,
    //
    exports: {},
  })

  // Execute the module function
  // 真正的去执行加载的模块
  // 通过模块的下标去modules中找到相应的模块 然后执行此模块
  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
}

从上面我们也可以发现其就是通过modules[moduleId]去管理依赖的,然后通过modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 去执行依赖文件。

那么其如何管理依赖的 exports 和 import 的 ?

我们继续看上面的 __webpack_require__ 发现在 call 依赖函数的时候传入了一个 空的module.exports === {}对象,那么在文件函数执行的过程中通过 Object.defineProperty将 export 的属性定义在 modules.exports 的属性上面

js
// function (module, __webpack_exports__, __webpack_require__) {}  // __webpack_exports__ ===  module.exports
__webpack_require__.d(__webpack_exports__, "module2Fun1", function () {
  return module2Fun1
})

// 对于default 直接使用 exports['default']去保存
__webpack_exports__["default"] = {
  name: "common",
}

那么我们在模块中就可以通过

js
// 获取返回的 exports对象
var _module2_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3)

// 获取相应的属性的值,其固定为一个函数
Object(_module2_js__WEBPACK_IMPORTED_MODULE_0__["module2Fun1"])()

其中为什么对于 export 的属性变成一个函数的方式去获取?

js
// module1.js
function (module, __webpack_exports__, __webpack_require__) {
		'use strict';
		__webpack_require__.r(__webpack_exports__);

		__webpack_require__.d(__webpack_exports__, 'module1Fun1', function () {
			return module1Fun1;
		});
		__webpack_require__.d(__webpack_exports__, 'module1Fun2', function () {
			return module1Fun2;
		});
		__webpack_require__.d(__webpack_exports__, 'module1Var3', function () {
			return module1Var3;
		});

		__webpack_require__(1);

		let module1Var1 = 'module1Var1';
		let module1Var2 = 'module1Var2';

		const module1Fun1 = function () {
			console.log(`module1Fun1+${module1Var2}`);
		};

		const module1Fun2 = function () {
			console.log(`module1Fun2`);
		};

		const module1Var3 = 10;
	}

我们去看 module1 的 export 的变量,发现其在模块的最前面进行定义,那么如果我们通过 为什么需要通过 Object.defineProperty 将 export 的变量定义为一个函数的属性? 因为对于 webpack 的模块化机制除了 default 其他的 export 都有一个提升的机制,会在函数的最上面定义 export 的变量的属性, 那么如果不是函数那么 我们 import module 这时候执行 module 函数,

那么这种情况

js
let a = 1
export const b = a
a = 2

编译后的代码为

js
__webpack_require__.d(__webpack_exports__, "b", a)
let a = 1
a = 2

这时候发现 a 是 undefined 但是我们通过变成一个函数的话

js
// 编译后的代码为
__webpack_require__.d(__webpack_exports__, "b", function () {
  return a
})
let a = 1
a = 2

只有执行到 我们需要 b 的时候才去获取当前的 a 的值 这时候就是 2;