欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Webpack学习-工作原理(下)

程序员文章站 2022-04-23 17:19:53
继上篇 "文章" 介绍了Webpack的基本概念,完整流程,以及打包过程中广播的一些事件的作用,这篇文章主要讲生成的chunk文件如何输出成具体的文件。分同步和异步两种情况来分析输出的文件 。 模块文件show.js 同步加载 生成的bundle文件 异步加载 经webpack打包会生成两个文件0. ......

继上篇介绍了webpack的基本概念,完整流程,以及打包过程中广播的一些事件的作用,这篇文章主要讲生成的chunk文件如何输出成具体的文件。分同步和异步两种情况来分析输出的文件使用的webpack版本:3.8.0

模块文件show.js

    function show(content) {
        window.document.getelementbyid('app').innertext = 'hello,' + content;
    }

    // 通过 commonjs 规范导出 show 函数
    module.exports = show;

同步加载

// main.js

import show from './show';

show('twenty-four k');

生成的bundle文件

// webpackbootstrap启动函数
// modules存放的是所有模块的数组,每个模块都是一个函数
(function(modules) {
    var installedmodules = {}; // 缓存安装过的模块,提升性能
    //去传入的modules数组中加载对应moduleid(index)的模块,与node的require语句相似
    function __webpack_require__(moduleid) {
        // 检查是否已经加载过,如果有的话直接从缓存installedmodules中取出
        if(installedmodules[moduleid]) {
            return installedmodules[moduleid].exports;
        }
        // 如果没有的话,就直接新建一个模块,并且存进缓存installedmodules
        var module = installedmodules[moduleid] = {
            i: moduleid, // 对应modules的索引index,也就是moduleid
            l: false, // 标志模块是否已经加载
            exports: {}
        };
        // 执行对应模块函数,并且传入需要的参数
        modules[moduleid].call(module.exports, module, module.exports, __webpack_require__);
        // 将标志设置为true
        module.l = true;
        // 返回这个模块的导出值
        return module.exports;
    }
    // 储存modules数组
    __webpack_require__.m = modules;
    // 储存缓存installedmodules
    __webpack_require__.c = installedmodules;
    // 为harmony导出定义getter函数
    __webpack_require__.d = function(exports, name, getter) {
        if(!__webpack_require__.o(exports, name)) {
            object.defineproperty(exports, name, {
                configurable: false,
                enumerable: true,
                get: getter
            });
        }
    };
    // 用于与非协调模块兼容的getdefaultexport函数,将module.default或非module声明成getter函数的a属性上
    __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;
    };
    // 工具函数,hasownproperty
    __webpack_require__.o = function(object, property) { return object.prototype.hasownproperty.call(object, property); };
    // webpack配置中的publicpath,用于加载被分割出去的异步代码
    __webpack_require__.p = "";
    // 使用__webpack_require__函数去加载index为0的模块,并且返回index为0的模块也就是主入口文件的main.js的对应文件,__webpack_require__.s的含义是启动模块对应的index
    return __webpack_require__(__webpack_require__.s = 0);
})
/************************************************************************/
([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
// 设置__esmodule为true,影响__webpack_require__.n函数的返回值
object.defineproperty(__webpack_exports__, "__esmodule", { value: true });
// 同步加载index为1的依赖模块
/* harmony import */ var __webpack_imported_module_0__show__ = __webpack_require__(1);
// 获取index为1的依赖模块的export
/* harmony import */ var __webpack_imported_module_0__show___default = __webpack_require__.n(__webpack_imported_module_0__show__);
// 同步加载


__webpack_imported_module_0__show___default()('wushaobin');

/***/ }),
/* 1 */
/***/ (function(module, exports) {

function show(content) {
  window.document.getelementbyid('app').innertext = 'hello,' + content;
}

// 通过 commonjs 规范导出 show 函数
module.exports = show;


/***/ })
]);

异步加载

// main.js

// 异步加载 show.js
import('./show').then((show) => {
  // 执行 show 函数
  show('twenty-four k');
});

经webpack打包会生成两个文件0.bundle.js和bundle.js,怎么做的原因,是可以吧show.js以异步加载形式引入,这也是分离代码,达到减少文件体积的优化方法,两个文件分析如下。

0.bundle.js

// 加载本文件(0.bundle.js)包含的模块, webpackjsonp用于从异步加载的文件中安装模块,挂载至全局(bundle.js)供其他文件使用
webpackjsonp(
// 在其他文件中存放的模块id
[0],[
// 本文件所包含的模块

/***/ (function(module, exports) {

function show(content) {
  window.document.getelementbyid('app').innertext = 'hello,' + content;
}

// 通过 commonjs 规范导出 show 函数
module.exports = show;


/***/ })
]);

bundle.js

 (function(modules) { // webpackbootstrap启动函数
    // 安装用于块加载的jsonp回调
    var parentjsonpfunction = window["webpackjsonp"];
    // chunkids 异步加载文件(0.bundle.js)中存放的需要安装的模块对应的chunkid
    // moremodules 异步加载文件(0.bundle.js)中存放需要安装的模块列表
    // executemodules 异步加载文件(0.bundle.js)中存放需要安装的模块安装后需要执行的模块对应的index
    window["webpackjsonp"] = function webpackjsonpcallback(chunkids, moremodules, executemodules) {
        // 将 "moremodules" 添加到modules对象中,
        // 将所有chunkids对应的模块都标记成已经加载成功
        var moduleid, chunkid, i = 0, resolves = [], result;
        for(;i < chunkids.length; i++) {
            chunkid = chunkids[i];
            if(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(chunkids, moremodules, executemodules);
        // 执行
        while(resolves.length) {
            resolves.shift()();
        }

    };

    // 缓存已经安装的模块
    var installedmodules = {};

    // 储存每个chunk的加载状态
    // 键为chunk的id,值为0代表加载成功
    var installedchunks = {
        1: 0
    };

    // 去传入的modules数组中加载对应moduleid(index)的模块,与node的require语句相似,同上,此处省略
    function __webpack_require__(moduleid) {
        ...
    }

    // 用于加载被分割出去的需要异步加载的chunk对应的文件,
    // chunkid需要异步加载的chunk对应的id,返回的是一个promise
    __webpack_require__.e = function requireensure(chunkid) {
        // 从installedchunks中获取chunkid对应的chunk文件的加载状态
        var installedchunkdata = installedchunks[chunkid];
        // 如果加载状态为0,则表示该chunk已经加载成功,直接返回promise resolve
        if(installedchunkdata === 0) {
            return new promise(function(resolve) { resolve(); });
        }

        // installedchunkdata不为空且不为0时表示chunk正在网络加载中
        if(installedchunkdata) {
            return installedchunkdata[2];
        }

        // installedchunkdata为空,表示该chunk还没有加载过,去加载该chunk对应的文件
        var promise = new promise(function(resolve, reject) {
            installedchunkdata = installedchunks[chunkid] = [resolve, reject];
        });
        installedchunkdata[2] = promise;

        // 通过dom操作,向html head中插入一个script标签去异步加载chunk对应的javascript文件
        var head = document.getelementsbytagname('head')[0];
        var script = document.createelement('script');
        script.type = "text/javascript";
        script.charset = 'utf-8';
        script.async = true;
        script.timeout = 120000;
        // htmlelement 接口的 nonce 属性返回只使用一次的加密数字,被内容安全政策用来决定这次请求是否被允许处理。
        if (__webpack_require__.nc) {
            script.setattribute("nonce", __webpack_require__.nc);
        }
        // 文件的路径由配置的publicpath、chunkid拼接而成
        script.src = __webpack_require__.p + "" + chunkid + ".bundle.js";
        // 设置异步加载的最长超时时间
        var timeout = settimeout(onscriptcomplete, 120000);
        script.onerror = script.onload = onscriptcomplete;
        // 在script加载和执行完成时回调
        function onscriptcomplete() {
            // 防止内存泄漏
            script.onerror = script.onload = null;
            cleartimeout(timeout);
            
            var chunk = installedchunks[chunkid];
            // 判断chunkid对应chunk是否安装成功
            if(chunk !== 0) {
                if(chunk) {
                    chunk[1](new error('loading chunk ' + chunkid + ' failed.'));
                }
                installedchunks[chunkid] = undefined;
            }
        };
        head.appendchild(script);

        return promise;
    };

    // 这里会给__webpack_require__设置多个属性和方法,同上,此处省略
    

    // 异步加载的出错函数
    __webpack_require__.oe = function(err) { console.error(err); throw err; };

    // load entry module and return exports
    return __webpack_require__(__webpack_require__.s = 0);
 })
/************************************************************************/
 ([
// 存放没有经过异步加载的,随着执行入口文件加载的模块,也就是同步的模块
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
// 通过__webpack_require__.e异步加载show.js对应的chunk
// 异步加载 show.js
__webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 1)).then((show) => {
  // 执行 show 函数
  show('webpack');
});


/***/ })
 ]);

总结

这里我们通过对比同步加载和异步加载的简单应用,去分析两种情况webpack打包出来文件的差异性,我们发现,对于异步加载的bundle.js与同步的bundle.js基本相似,都是模拟node.js的require语句去导入模块,有所不同的是多了一个__webpack_require__.e函数,用来加载分割出的需要异步加载的chunk对应的文件,以及一个wepackjsonp函数,用于从异步加载的文件中去安装模块。