54 webpack原理-输出文件简单分析

输出文件简单分析

本章节将简单揭秘一下,为什么通过Webpack输出的打包文件能直接在浏览器运行,其输出文件到底有什么魔法?

为了方便阅读输出文件的源码,在此在这里对输出文件进行了一些格式改造,但是并没有修改其功能,代码如下:

(function (modules) { // 启动匿名函数

    // 从异步加载的文件中安装模块
    function webpackJsonpCallback(data) {
        // 异步加载的文件中存放的需要安装模块对应的Chunk ID
        var chunkIds = data[0];
        // 异步加载的文件中存放的需要安装的模块列表
        var moreModules = data[1];
        // 将moreModules中的所有模块添加到modules
        // 将所有ChunkIds读经的模块标记为加载成功
        var moduleId, chunkId, i = 0, resolves = [];
        for (; i < chunkIds.length; i++) {
            chunkId = chunkIds[i];
            if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
                resolves.push(installedChunks[chunkId][0]);
            }
            installedChunks[chunkId] = 0;
        }
        for (moduleId in moreModules) {
            if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                modules[moduleId] = moreModules[moduleId];
            }
        }
        if (parentJsonpFunction) parentJsonpFunction(data);
        while (resolves.length) {
            resolves.shift()();
        }
    };
    // 缓存已经加载过的模块
    var installedModules = {};
    /**
    * 缓存每个chunk的加载状态
    * key为chunk的id
    * value表示chunk的状态
    * value = undefined:没有加载
    * value = null:预取和预加载
    * value = Promise: 加载中
    * value = 0: 加载完毕
    */
    var installedChunks = {
        "main": 0
    };
    // 返回拼接好的文件路径
    function jsonpScriptSrc(chunkId) {
        return __webpack_require__.p + "" + ({}[chunkId] || chunkId) + ".js"
    }
    // 导入函数,代替node.js中的require函数
    function __webpack_require__(moduleId) {
        // 如果模块已经被安装过,直接返回模块的导出值
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // 创建一个新的模块
        var module = installedModules[moduleId] = {
            // 模块id
            i: moduleId,
            // 是否被安装
            l: false,
            // 模块导出值
            exports: {}
        }
        // 根据moduleId,从modules中获取对应执行函数
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
        // 模块已经被加载
        module.l = true;
        // 返回模块的导出值
        return module.exports;
    }
    // 加载被分割出去的需要异步加载的chunk对应文件
    __webpack_require__.e = function requireEnsure(chunkId {
        var promises = [];
        // 异步加载文件
        var installedChunkData = installedChunks[chunkId];
        // Chunk没有被加载过
        if (installedChunkData !== 0) { // 0 means "already 		installed".
        // installedChunkData不为空不为0 表示正处于网络加载中
        if (installedChunkData) {
            promises.push(installedChunkData[2]);
        } else {
            // installedChunkData不为0为空 表示chunk没有被加载过,去加载chunk对应的文件
            var promise = new Promise(function (resolve, reject) {
                installedChunkData = installedChunks[chunkId] = [resolve, reject];
            });
            promises.push(installedChunkData[2] = promise);
            // 进行DOM操作,创建一个script标签添加到head中,去加载chunk对应的文件
            var script = document.createElement('script');
            var onScriptComplete;
            script.charset = 'utf-8';
            script.timeout = 120;
            if (__webpack_require__.nc) {
                script.setAttribute("nonce", __webpack_require__.nc);
            }
            script.src = jsonpScriptSrc(chunkId);
            var error = new Error();
            // script加载和执行完成是的回调函数
            onScriptComplete = function (event) {
                // 防止IE中内存泄漏
                script.onerror = script.onload = null;
                clearTimeout(timeout);
                // 加载chunid对应的chunk是安装成功,安装成功才会存在于installedChunks中
                var chunk = installedChunks[chunkId];
                if (chunk !== 0) {
                    if (chunk) {
                        var errorType = event && (event.type === 'load' ? 'missing' : event.type);
                        var realSrc = event && event.target && event.target.src;
                        error.message = 'Loading chunk ' + chunkId + 'failed.\n('
                            + errorType + ': ' + realSrc + ')';
                        error.name = 'ChunkLoadError';
                        error.type = errorType;
                        error.request = realSrc;
                        chunk[1](error);
                    }
                    installedChunks[chunkId] = undefined;
                }
            };
            // 设置异步加载的超使时间
            var timeout = setTimeout(function () {
                onScriptComplete({ type: 'timeout', target: script });
            }, 120000);
            script.onerror = script.onload = onScriptComplete;
            document.head.appendChild(script);
        }
    }
    return Promise.all(promises);
};
// __webpack_require__上绑定所有模块
__webpack_require__.m = modules;
// __webpack_require__上绑定所有加载过的模块
__webpack_require__.c = installedModules;
// define getter function for harmony exports
__webpack_require__.d = function (exports, name, gette {
    if (!__webpack_require__.o(exports, name)) {
    Object.defineProperty(exports, name, {
        enumerable: true, get:
            getter
    });
}
    };
// 给模块的导出值定义_esModule属性,表示该模块ES模块
__webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
        Object.defineProperty(exports, Symbol.toStringTag, {
            value: 'Module'
        });
    }
    Object.defineProperty(exports, '__esModule', {
        value: true
    });
};
/** 
* 创建一个伪命名空间对象
* mode & 1 value 是一个模块id,require(value)加载模块
* mode & 2 将value的所有属性合并到ns对象当中
* mode & 4 返回已经是nsobject到value
* mode & 8 | 1 绑定getter方法
*/
__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;
};
// getDefaultExport function for compatibility with non-harmony modules
__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;
};
// 判断对象(object)自身属性是有指定的属性(property)
__webpack_require__.o = function (object, property) { returnObject.prototype.hasOwnProperty.call(object, pro	perty); };
// puplicPath output.publicPath中设置的值
__webpack_require__.p = "";
// 异步加载出现错误的处理函数
__webpack_require__.oe = function (err) { console.error(err); throw err; };
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for (var i = 0; i < jsonpArray.length; i++) 	webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
// 执行入口模块,并返回结果
return __webpack_require__(__webpack_require__.s = "./src/index.js");
    })
({
    "./src/index.js":
        (function (module, __webpack_exports__, __webpack_require__
        ) {
            "use strict";
            eval(`
    __webpack_require__.r(__webpack_exports__);
    var _name__webpack_IMPORTED_MODULE_0__ = _webpack_require__("./src/name.js");
    setTimeout(()=>{
    __webpack_require__.e(0)
    .then(__webpack_require__.bind(null,"./src/async.js"))
    .then(res=>{console.log(res);})
    },100)
    console.log(_name__webpack_IMPORTED_MODULE_0__["default"]);
    `);
        }),
    "./src/name.js":
        (function (module, __webpack_exports__, __webpack_require__) {
            "use strict";
            eval(`
    __webpack_require__.r(__webpack_exports__);
    __webpack_exports__["default"] = ('Render');
    `);
        })
});
// 从整体开始一步一步的分析,该文件究竟做了些什么。很显然的是,输出内容是一个自执行函数,如下所示:
(function (modules) {
    // 模拟require函数
    function __webpack_require__(moduleId) { }
})(
    {
        // 存放所有模块的对象
    }
)

该函数通过定义了浏览器能够识别的 __webpack_require__函数,来代替Node.js中的require函数。

__webpack_require__的作用就是根据模块id,执行对应的模块可执行函数,然后返回模块的导出值,其函数功能如下:

  • 根据模块id先从缓存中去读取对应的模块,找到则直接返回模块的导出值。
  • 没有找到模块,则初始化一个新模块,根据模块id执行modules(这里的modules就是自执行函数传入的存放所有模块的对象)中对应的模块的执行函数,将新模块加入到缓存中,然后返回新模块的导出值。

自执行函数会通过如下代码,从入口文件开始执行__webpack_require__函数:

return __webpack_require__(__webpack_require__.s = "./src/index.js");

从这开始,看看输出文件是如何处理模块之间的依赖关系。根据上面分析的__webpack_require__功能,webpack_require(webpack_require.s = “./src/index.js”)代码语句会执行下面这个函数:

function (module, __webpack_exports__, __webpack_require__) {
    "use strict";
    eval(`
    __webpack_require__.r(__webpack_exports__);
    var _name__webpack_IMPORTED_MODULE_0__ = __webpack_require__("./src/name.js");
    setTimeout(()=>{
    __webpack_require__.e(0)
    .then(__webpack_require__.bind(null, "./src/async.js"))
    .then(res=>{console.log(res);})
    },100)
    console.log(_name__webpack_IMPORTED_MODULE_0__["default"]);
    `);
}

函数的入参module, webpack_exports, __webpack_require__是__webpack_require__函数中传入的,对应代码如下:

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

因此才可以去得到入口文件依赖的name.js的模块返回值,如下所示:

var _name__webpack_IMPORTED_MODULE_0__ = __webpack_require__("./src/name.js");

如果name.js中依赖其他模块,那么就会递归的执行该操作,直到所有被依赖的模块都被处理,最后入口文件中就得到了最后的结果。

setTimeout(() => {
    __webpack_require__.e(0)
        .then(__webpack_require__.bind(null, "./src/async.js"))
        .then(res => { console.log(res); })
}, 100)

是属于输出文件中异步加载模块的处理,webpack_require.e函数功能如下:

  1. 首先根据chunkId去缓存中去寻找对应的Chunk。
  2. 如果对应的Chunk正在加载中,就直接使用Promise处理该Chunk。
  3. 如果对应的Chunk没有被加载,那么就创建一个script标签去加载该Chunk。并把该Chunk加入到缓存中
  4. 返回对应的Chunk被Promise处理过的结果。

在这里chunkId = 0对应的是源码中的async模块,所以这里表示去加载async异步模块,其实从构建出来的0.js中也可以得到该结果,0.js内容如下:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push(
    [
        [0],
        {
            "./src/async.js":
                (function (module, __webpack_exports__, __webpack_require__) {
                    "use strict";
                    eval(`
    __webpack_require__.r(__webpack_exports__);
    __webpack_exports__["default"] = ('async');
    `);
                })
        }
    ]
);

0.js中的window[“webpackJsonp”] = window[“webpackJsonp”] || []).push方法的功能是由如下代码实现:

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;

而webpackJsonpCallback函数用于如何处理异步加载的模块。

至此,我们分析了输出文件中如何加载同步模块和异步模块,已经如何处理模块之间的依赖。知道了为什么输出文件能够直接在浏览器中运行。并不是所有的输出文件内容都是如此,但是整体结构和思想都是一样的,如果你对输出文件感兴趣,可以针对不同项目去尝试分析不同的输出文件,你将受益匪浅。本章节提供生成输出文件的源码:https://gitee.com/mvc_ydb/webpack/blob/master/webpack_demo.zip

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值