Webpack加载流程

半兽人 发表于: 2025-02-21   最后更新时间: 2025-02-21 15:08:59  
{{totalSubscript}} 订阅, 18 游览
!(function(moduleDefinitions) {
    // 用于存储已加载的模块
    const loadedModules = {};

    // Webpack 核心加载函数,用于动态加载模块
    function __webpack_require__(moduleId) {
        // 1. 如果模块已缓存,直接返回其导出对象
        if (loadedModules[moduleId]) {
            return loadedModules[moduleId].exports;
        }

        // 2. 创建新模块对象并缓存
        const module = loadedModules[moduleId] = {
            id: moduleId,        // 模块 ID
            isLoaded: false,     // 是否已加载标志
            exports: {}          // 模块的导出对象
        };

        // 3. 执行模块函数,传入 module、exports 和 __webpack_require__
        moduleDefinitions[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        // 4. 标记模块为已加载并返回导出对象
        module.isLoaded = true;
        return module.exports;
    }

    // 绑定属性到 __webpack_require__
    __webpack_require__.m = moduleDefinitions;      // 所有模块的定义
    __webpack_require__.c = loadedModules;          // 已缓存的模块

    // 定义模块导出属性的 getter 函数
    __webpack_require__.d = function(targetExports, propertyName, propertyGetter) {
        if (!__webpack_require__.o(targetExports, propertyName)) {
            Object.defineProperty(targetExports, propertyName, {
                enumerable: true,   // 可枚举
                get: propertyGetter // getter 函数动态获取值
            });
        }
    };

    // 标记对象为 ES 模块
    __webpack_require__.r = function(targetExports) {
        if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
            Object.defineProperty(targetExports, Symbol.toStringTag, { value: 'Module' });
        }
        Object.defineProperty(targetExports, '__esModule', { value: true });
    };

    // 处理不同类型的模块导入(兼容 CommonJS 和 ES Module)
    __webpack_require__.t = function(importValue, importMode) {
        if (importMode & 1) importValue = __webpack_require__(importValue); // 加载模块
        if (importMode & 8) return importValue;                            // 直接返回
        if (importMode & 4 && typeof importValue === 'object' && importValue && importValue.__esModule) {
            return importValue;                                            // 返回 ES 模块
        }

        const namespace = Object.create(null);                             // 创建新命名空间
        __webpack_require__.r(namespace);                                  // 标记为 ES 模块
        Object.defineProperty(namespace, 'default', {
            enumerable: true,
            value: importValue
        });

        if (importMode & 2 && typeof importValue !== 'string') {
            for (const key in importValue) {
                __webpack_require__.d(namespace, key, (key => importValue[key]).bind(null, key));
            }
        }
        return namespace;
    };

    // 获取模块的默认导出
    __webpack_require__.n = function(sourceModule) {
        const defaultGetter = sourceModule && sourceModule.__esModule
            ? function() { return sourceModule['default']; }               // ES 模块默认导出
            : function() { return sourceModule; };                         // CommonJS 模块
        __webpack_require__.d(defaultGetter, 'a', defaultGetter);
        return defaultGetter;
    };

    // 判断对象是否拥有某个属性
    __webpack_require__.o = function(targetObject, propertyName) {
        return Object.prototype.hasOwnProperty.call(targetObject, propertyName);
    };

    // Webpack 的公共路径(通常用于动态加载资源)
    __webpack_require__.p = "";

    // 指定入口模块 ID 并加载
    __webpack_require__.s = 3;
    return __webpack_require__(__webpack_require__.s);
})([
    // 模块 0:日志输出组件
    function(module, exports) {
        function logMessage() {
            console.log("日志组件被调用");
        }
        module.exports = logMessage;
    },

    // 模块 1:数据返回组件
    function(module, exports) {
        function getData() {
            return {
                text: "数据组件返回的信息"
            };
        }
        module.exports = getData;
    },

    // 模块 2:组合其他组件
    function(module, exports, __webpack_require__) {
        const logMessage = __webpack_require__(0);   // 引用模块 0
        const getData = __webpack_require__(1);      // 引用模块 1

        function mainComponent() {
            logMessage();
            console.log(getData().text);
        }
        module.exports = mainComponent;
    },

    // 模块 3:入口模块,导出组件集合
    function(module, exports, __webpack_require__) {
        "use strict";
        __webpack_require__.r(exports);              // 标记 exports 为 ES 模块

        const componentExports = {};
        __webpack_require__.r(componentExports);     // 标记 componentExports 为 ES 模块

        // 定义导出的组件
        __webpack_require__.d(componentExports, "TiAccordion", function() {
            return accordionModule.a;
        });
        __webpack_require__.d(componentExports, "TiActionMenu", function() {
            return actionMenuModule.a;
        });

        // 加载依赖模块
        const logModule = __webpack_require__(0);
        const accordionModule = __webpack_require__.n(logModule);
        const dataModule = __webpack_require__(1);
        const actionMenuModule = __webpack_require__.n(dataModule);

        // Polyfill Object.values(兼容旧环境)
        Object.values = Object.values || function(targetObject) {
            if (targetObject !== Object(targetObject)) {
                throw new TypeError("Object.values called on a non-object");
            }
            const values = [];
            for (const key in targetObject) {
                if (Object.prototype.hasOwnProperty.call(targetObject, key)) {
                    values.push(targetObject[key]);
                }
            }
            return values;
        };

        // 输出组件集合
        document.body.innerHTML = Object.values(componentExports).join(', ');
        console.log("....", Object.values(componentExports));
    }
]);

在线运行

这一个简化版的 Webpack 运行时模块加载器,它模拟 Webpack 如何管理和执行模块。以下是详细的解析:


1. 顶层立即执行函数 (IIFE)

代码的最外层是一个 立即执行函数表达式 (IIFE),它接受 moduleDefinitions 作为参数,这是一个 模块定义数组,其中每个索引对应一个模块的实现:

!(function(moduleDefinitions) { 
    // Webpack 加载器逻辑
})([ /* 模块定义数组 */ ]);

其中:

  • moduleDefinitions 是一个数组,每个元素都是一个模块的 定义函数

2. Webpack 的核心加载函数 (__webpack_require__)

Webpack 运行时的核心是 __webpack_require__,它负责:

  1. 检查模块是否已被缓存
  2. 如果未缓存,则创建一个新的模块对象
  3. 执行模块代码
  4. 标记模块已加载
  5. 返回模块的导出对象

代码:

function __webpack_require__(moduleId) {
    if (loadedModules[moduleId]) { 
        return loadedModules[moduleId].exports; 
    }

    const module = loadedModules[moduleId] = {
        id: moduleId,
        isLoaded: false,
        exports: {}
    };

    moduleDefinitions[moduleId].call(module.exports, module, module.exports, __webpack_require__);

    module.isLoaded = true;
    return module.exports;
}
  • loadedModules 作为缓存,避免重复加载。
  • moduleDefinitions[moduleId] 通过 .call() 方式执行,传入 module, exports, __webpack_require__

3. Webpack 运行时的工具函数

Webpack 还提供了一些工具函数来处理 ES6 import/export 兼容性问题:

(1)__webpack_require__.d

定义 ES6 的 getter 形式的模块导出:

__webpack_require__.d = function(targetExports, propertyName, propertyGetter) {
    if (!__webpack_require__.o(targetExports, propertyName)) {
        Object.defineProperty(targetExports, propertyName, {
            enumerable: true,
            get: propertyGetter
        });
    }
};
  • 作用:定义 动态计算 的导出属性,支持 import { name } from 'module'

(2)__webpack_require__.r

标记某个模块为 ES Module

__webpack_require__.r = function(targetExports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(targetExports, Symbol.toStringTag, { value: 'Module' });
    }
    Object.defineProperty(targetExports, '__esModule', { value: true });
};
  • 作用:让 targetExports.__esModule = true,这样 Webpack 知道它是一个 ES6 模块。

(3)__webpack_require__.t

支持 import * as 形式:

__webpack_require__.t = function(importValue, importMode) {
    if (importMode & 1) importValue = __webpack_require__(importValue);
    if (importMode & 8) return importValue;
    if (importMode & 4 && typeof importValue === 'object' && importValue && importValue.__esModule) {
        return importValue;
    }

    const namespace = Object.create(null);
    __webpack_require__.r(namespace);
    Object.defineProperty(namespace, 'default', { enumerable: true, value: importValue });

    if (importMode & 2 && typeof importValue !== 'string') {
        for (const key in importValue) {
            __webpack_require__.d(namespace, key, (key => importValue[key]).bind(null, key));
        }
    }
    return namespace;
};
  • importMode 的值:
    • 1:加载模块
    • 8:直接返回
    • 4:如果是 ES6 模块,则直接返回
    • 2:把模块的所有属性导出(用于 import *

(4)__webpack_require__.n

处理 CommonJS 默认导出 的兼容性:

__webpack_require__.n = function(sourceModule) {
    const defaultGetter = sourceModule && sourceModule.__esModule
        ? function() { return sourceModule['default']; }
        : function() { return sourceModule; };

    __webpack_require__.d(defaultGetter, 'a', defaultGetter);
    return defaultGetter;
};
  • 作用:确保 CommonJS 和 ES6 的 default 导出 兼容。

(5)__webpack_require__.o

用于检查 对象是否包含某个属性

__webpack_require__.o = function(targetObject, propertyName) {
    return Object.prototype.hasOwnProperty.call(targetObject, propertyName);
};

4. 入口模块的执行

__webpack_require__.s = 3;
return __webpack_require__(__webpack_require__.s);
  • __webpack_require__.s = 3 表示 入口模块moduleDefinitions[3]
  • 然后调用 __webpack_require__(3) 运行主模块。

5. 各个模块的定义

Webpack 运行时会将 源代码的不同部分拆分成多个模块,每个模块在 moduleDefinitions 数组中有对应的索引。

// 模块 0:日志组件
function(module, exports) {
    function logMessage() {
        console.log("日志组件被调用");
    }
    module.exports = logMessage;
}
  • 作用:定义 logMessage 函数,并通过 module.exports 导出。
// 模块 1:数据返回组件
function(module, exports) {
    function getData() {
        return {
            text: "数据组件返回的信息"
        };
    }
    module.exports = getData;
}
  • 作用:定义 getData 函数,返回一个对象。
// 模块 2:组合其他组件
function(module, exports, __webpack_require__) {
    const logMessage = __webpack_require__(0);
    const getData = __webpack_require__(1);

    function mainComponent() {
        logMessage();
        console.log(getData().text);
    }
    module.exports = mainComponent;
}
  • 作用:加载 logMessagegetData,并组合使用。
// 模块 3:入口模块
function(module, exports, __webpack_require__) {
    "use strict";
    __webpack_require__.r(exports);

    const componentExports = {};
    __webpack_require__.r(componentExports);

    __webpack_require__.d(componentExports, "TiAccordion", function() {
        return accordionModule.a;
    });
    __webpack_require__.d(componentExports, "TiActionMenu", function() {
        return actionMenuModule.a;
    });

    const logModule = __webpack_require__(0);
    const accordionModule = __webpack_require__.n(logModule);
    const dataModule = __webpack_require__(1);
    const actionMenuModule = __webpack_require__.n(dataModule);

    // 兼容 `Object.values`
    Object.values = Object.values || function(targetObject) {
        if (targetObject !== Object(targetObject)) {
            throw new TypeError("Object.values called on a non-object");
        }
        const values = [];
        for (const key in targetObject) {
            if (Object.prototype.hasOwnProperty.call(targetObject, key)) {
                values.push(targetObject[key]);
            }
        }
        return values;
    };

    console.log("....", Object.values(componentExports));
}
  • 作用:
    • 定义 componentExports
    • 兼容 Object.values
    • 运行 Webpack 入口逻辑

总结

  1. Webpack 运行时的作用

    • 缓存模块
    • 动态加载模块
    • 处理 ES6 和 CommonJS 兼容性
    • 定义模块的 import/export
  2. 模块的执行

    • moduleDefinitions[3] 作为入口点
    • 依赖模块 0,1,2 先被加载
  3. 代码结构

    • Webpack 模拟了 Node.js 的 require 方式,同时兼容 ES6 import/export
ES6
更新于 2025-02-21
在线,2小时前登录

查看ES6更多相关的文章或提一个关于ES6的问题,也可以与我们一起分享文章