webpack5 模块化

22 阅读4分钟

本文章对webpack5的模块化进行详细介绍

源代码

// a.js
export default class Math {
  constructor() {
    this.name = "Math";
  }
  add(a, b) {
    return a + b;
  }
}
// b.js
export default class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  say() {
    console.log(`I am ${this.name}, ${this.age} years old`);
  }
}
// index.js
import Math from './a'
import User from './b'
const math = new Math()
const user = new User('zhangSan', 18)
user.say()
const { add } = math;
console.log('sss', add(1, 2));
user.say();
// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
    compress: true,
    port: 9000,
  },
};

bundle后的代码

(() => {
  "use strict";
  // 定义模块集合, 包含Math和User两个模块
  var __webpack_modules__ = ([
    ,
    ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        "default": () => (Math)
      });

      class Math {
        constructor() {
          this.name = "Math";
        }
        
        add(a, b) {
          return a + b;
        }
      }
    }),
    ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
      __webpack_require__.r(__webpack_exports__);
      __webpack_require__.d(__webpack_exports__, {
        "default": () => (User)
      });
      
      class User {
       
        constructor(name, age) {
          this.name = name;
          this.age = age;
        }
        
        say() {
          console.log(`I am ${this.name}, ${this.age} years old`);
        }
      }
    })
  ]);

  // 模块缓存对象
  var __webpack_module_cache__ = {};
  
  /**
   * webpack的模块加载函数
   * @param {number} moduleId - 模块ID
   * @returns {object} 模块的导出内容
   */
  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;
  }
  
  (() => {
    /**
     * 为exports对象定义getter属性
     * @param {object} exports - 导出对象
     * @param {object} definition - 属性定义
     */
    __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] });
        }
      }
    };
  })();
  
  (() => {
    /**
     * 检查对象是否包含指定属性
     * @param {object} obj - 要检查的对象
     * @param {string} prop - 属性名
     * @returns {boolean} 是否包含该属性
     */
    __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
  })();
  
  (() => {
    /**
     * 将exports标记为ES模块
     * @param {object} exports - 要标记的导出对象
     */
    __webpack_require__.r = (exports) => {
      if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
      }
      Object.defineProperty(exports, '__esModule', { value: true });
    };
  })();

  // 入口代码
  var __webpack_exports__ = {};
  __webpack_require__.r(__webpack_exports__);
  var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
  var _b__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);

  // 创建实例并调用方法
  const math = new _a__WEBPACK_IMPORTED_MODULE_0__["default"]()
  const user = new _b__WEBPACK_IMPORTED_MODULE_1__["default"]('zhangSan', 18)
  user.say()
  const { add } = math;
  console.log('sss', add(1, 2));
  user.say();
})();

Webpack5 模块化原理详解

1. 整体架构

Webpack5的模块系统主要由以下几个核心部分组成:

// 1. 模块定义数组
var __webpack_modules__ = ([...]);

// 2. 模块缓存
var __webpack_module_cache__ = {};

// 3. 模块加载函数
function __webpack_require__(moduleId) {...}

// 4. 运行时工具函数
__webpack_require__.d = (exports, definition) => {...}  // 定义属性
__webpack_require__.o = (obj, prop) => {...}           // 属性检查
__webpack_require__.r = (exports) => {...}             // ES模块标记

2. 模块定义结构

2.1 模块数组

每个模块被转换为一个函数,存储在__webpack_modules__数组中:

var __webpack_modules__ = ([
  ,  // 索引0处空置
  ((module, exports, require) => {
    // 模块1: Math类
  }),
  ((module, exports, require) => {
    // 模块2: User类
  })
]);

2.2 模块函数参数

每个模块函数接收三个关键参数:

  • module: 当前模块对象
  • exports: 模块导出对象
  • require: 模块加载函数

3. 模块加载机制

3.1 模块缓存

var __webpack_module_cache__ = {};
  • 用于存储已加载的模块
  • 避免重复加载同一模块
  • 实现模块单例模式

3.2 模块加载函数

function __webpack_require__(moduleId) {
    // 1. 检查缓存
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
        return cachedModule.exports;
    }
    
    // 2. 创建新模块
    var module = __webpack_module_cache__[moduleId] = {
        exports: {}
    };
    
    // 3. 执行模块函数
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
    
    // 4. 返回模块导出
    return module.exports;
}

4. ES模块支持

4.1 ES模块标记

__webpack_require__.r = (exports) => {
    // 添加 __esModule 标记
    Object.defineProperty(exports, '__esModule', { value: true });
    // 添加 Symbol.toStringTag 标记
    if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    }
};

4.2 属性定义

__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] 
            });
        }
    }
};

5. 实际运用示例

以示例代码中的Math类模块为例:

// 1. 导入模块
var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

// 2. 创建实例
const math = new _a__WEBPACK_IMPORTED_MODULE_0__["default"]()

// 3. 使用模块方法
const { add } = math;
console.log('sss', add(1, 2));

6. 模块加载流程

  1. 入口文件通过 IIFE (立即执行函数) 启动
  2. 调用 __webpack_require__ 加载首个模块
  3. 检查模块缓存
  4. 如果未缓存,创建新模块实例
  5. 执行模块函数,填充 exports 对象
  6. 缓存并返回模块导出
  7. 在模块间建立依赖关系

webpack4 代码参考

// modules是存放所有模块的数组,数组中每个元素存储{ 模块路径: 模块导出代码函数 }
(function(modules) {
// 模块缓存作用,已加载的模块可以不用再重新读取,提升性能
var installedModules = {};

// 关键函数,加载模块代码
// 形式有点像Node的CommonJS模块,但这里是可跑在浏览器上的es5代码
function __webpack_require__(moduleId) {
  // 缓存检查,有则直接从缓存中取得
  if(installedModules[moduleId]) {
    return installedModules[moduleId].exports;
  }
  // 先创建一个空模块,塞入缓存中
  var module = installedModules[moduleId] = {
    i: moduleId,
    l: false, // 标记是否已经加载
    exports: {} // 初始模块为空
  };

  // 把要加载的模块内容,挂载到module.exports上
  modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  module.l = true; // 标记为已加载

  // 返回加载的模块,调用方直接调用即可
  return module.exports;
}

// __webpack_require__对象下的r函数
// 在module.exports上定义__esModule为true,表明是一个模块对象
__webpack_require__.r = function(exports) {
  Object.defineProperty(exports, '__esModule', { value: true });
};

// 启动入口模块main.js
return __webpack_require__(__webpack_require__.s = "./src/main.js");
})
({
  // add模块
  "./src/add.js": (function(module, __webpack_exports__, __webpack_require__) {
    // 在module.exports上定义__esModule为true
    __webpack_require__.r(__webpack_exports__);
    // 直接把add模块内容,赋给module.exports.default对象上
    __webpack_exports__["default"] = (function(a, b) {
      let { name } = { name: 'hello world,'}
      return name + a + b
    });
  }),

  // 入口模块
  "./src/main.js": (function(module, __webpack_exports__, __webpack_require__) {
    __webpack_require__.r(__webpack_exports__)
    // 拿到add模块的定义
    // _add__WEBPACK_IMPORTED_MODULE_0__ = module.exports,有点类似require
    var _add__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/add.js");
    // add模块内容: _add__WEBPACK_IMPORTED_MODULE_0__["default"]
    console.log(_add__WEBPACK_IMPORTED_MODULE_0__["default"], Object(_add__WEBPACK_IMPORTED_MODULE_0__["default"])(1, 2))
  })
});