• 分析 webpack 打包后的代码


    写在前面的

    1. 版本说明

    使用的 webpack 版本 3.0.0。

    2. webpack 的核心思想

    • 一切皆“模块(module)”
    • 按需加载

    一、开始

    1. 准备

            当前只创建一个文件 index.js,该文件作为入口文件,代码如下。

    console.log('hello, world');

            接着使用 webpack 来进行打包,执行的命令如下。

    webpack index.js index.bundle.js

    2. 分析

            打包文件生成的一大堆代码,实际上就是一个自执行函数,仅传入一个参数为 modules,且该对象为一个数组。

    (function (modules) {
        // ...
    })([function (module, exports) {
        function sayHello () {
            console.log('hello');
        }
    }])

            该函数的作用就是管理模块,它的内部定义了两个主要的对象 installedModules 对象和 __webpack_require__(moduleId) 函数对象。

            installedModules 对象初始化代码如下。

    var installedModules = {};

            来看一下函数对象 __webpack_require__ 的定义,它的作用与 require 函数相同。

    // require 函数
    function
    __webapck_require__(moduleId) {
    // 如果模块在缓存中
    if (installedModules[moduleId]) { return installedModules[moduleId].exports; }
    // 创建一个新的模块(并将它放在缓存中)
    var module = installedModules[moduleId] = { i: moduleId, l: false,// l是loaded的缩写,指代是否已经加载 exports: {} };
    // 执行模块的函数 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    // 标记模块已经加载 module.l
    = true;
    // 返回模块的 exports 对象
    return module.exports; }

            看到这里,我们可以看出 installedModules 对象其实是被当作字典使用,key 是模块的 id,value 是代表模块状态和导出的一个对象,如下。

    {
        i: moduleId, // 模块的 id
        l: false, // l是loaded的缩写,指代是否已经加载
        exports: {} // 模块的导出对象
    }

            __webpack_require__ 还被定义了许多的静态方法和静态对象。

    // 外放 modules 对象(__webpack_modules__)
    __webpack_require__.m = modules;
    
    // 外放模块的缓存
    __webpack_require__.c = installedModules;
    
    // 定义 getter 函数以便友好的加载模块
    __webpack_require__.d = function (exports, name, getter) {
        if(!__webpack_require__.o(exports, name)) {
            Object.defineProperty(exports, name, {
                configurable: false,
                enumerable: true,
                get: getter
            });
        }
    };
    __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.prototype.hasOwnProperty.call
    __webpack_require__.o = function(object, property) { Object.prototype.hasOwnProperty.call(object, property); };

    // __webpack_public_path__
    __webpack_require__.p = "";

            在函数的最后,结果是导入 moduleId = 0 的模块,就是执行了入口文件。

    return __webpack_require__(__webpack_require__.s = 0);

            你定义的入口文件中的内容被转换为。

    (function (module, __webpack_exports__, __webpack_require__) {
        console.log('hello, world!');
    });

            传入立即函数表达式中的参数为一个数组对象,而入口文件转换的函数为数组中的第一个元素。注意 webpack 添加的注释 /* 0 */ 代表该模块的 moduleId 为 0,而 webpack 其实是将每一个资源文件看作一个模块,并将其指定一个标识 moduleId。

    [
    /* 0 */
    (function(module, __webpack_exports__, __webpack_require__) {
        // ...
    })
    ]

     二、添加依赖

    1. 准备

            添加一个新的 js 文件,命名为 util.js,代码如下。

    export function add (a, b) {
    return a + b; }

            入口文件 index.js 代码修改如下。

    import util from './util.js'
    
    console.log('1 + 2 = ' + util.add(1, 2));

            执行之前的命令,使用 webpack 构建工具进行打包。

    2. 分析

            此时查看 index.bundle.js 下的代码,之后生成的立即执行函数的传入参数发生变化,还记得之前说过该参数为一个数组,此时的数组如下。

    [
    /* 0 */
    (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
        Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
        /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__util_js__ = __webpack_require__(1);
    
    
        console.log('1 + 2 = ' + __WEBPACK_IMPORTED_MODULE_0__util_js__["a" /* default */].add(1 + 2));
    
    }),
    /* 1 */
    (function(module, __webpack_exports__, __webpack_require__) {
        "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony default export */ __webpack_exports__["add"] = add;
    function (a, b) { return a + b; } }) ]

            可见,webpack 将每个文件视为一个独立的模块,它分析模块之间的依赖关系,将模块中 import export 相关的内容做一次替换,比如。

    import b from './b'
    
    export default {
        name: 'c'  
    }
    
    // 最后被转化为
    var __WEBPACK_IMPORTED_MODULE_0_b__ = __webpack_require__(0)
    
    // 这里需要特别注意一点,webpack 将 a 属性作为某块的 default 值
    __webpack_exports__["a"] = ({
        name: 'c'
    })

             再给所有模块外面加一层包装函数,使其成为模块初始化函数,接着把所有模块初始化函数合成一个数组,赋值给 modules 变量。

    三、模块的动态导入

    1. 准备

            修改入口文件 index.js 的代码如下。

    import('./util.js').then(function (util) {
        console.log('1 + 2 = ' + util.add(1 + 2));
    })

             执行之前的命令,使用 webpack 构建工具进行打包。

    2. 分析

            这次打包后不仅获取了 index.bundle.js 文件,还产生了一个 0.index.bundle.js 文件,接下来先分析 o.index.bundle.js。

    webpackJsonp([0],[
    /* 0 */,
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
    /* harmony export (immutable) */ __webpack_exports__["add"] = add;
    function add (a, b) {
        return a + b;
    }
    
    /***/ })
    ]);

            之后可以看到该块代码以 JSONP 的形式被加载,这里可以看出该代码被加载后立即执行 webpackJsonp 这个方法,这个方法为 index.bundle.js 中新增的方法。在看这个方法之前,先看一下 index.bundle.js 中新增的一个对象。

    var installedChunks = {
        1: 0
    };

             该对象记录 chunk 的安装状态,而不记录 chunk 相关的具体内容,具体内容仍保存到 installedModules 对象中。installedChunks 也是被当作一个字典使用,key 为 chunk 的 id(此处要注意与 mouleId 的区别,每个 chunk 中至少会拥有一个 module),value 可为 0(已加载)、[resolve, reject](正在加载)、undefined(未加载),可以在 requireEnsure(chunkId) 方法中观察到这种状态的变化。

    __webpack_require__.e = function requireEnsure(chunkId) {
        var installedChunkData = installedChunks[chunkId];
        if (installedChunkData === 0) {
            return new Promise(function(resolve) { resolve(); });
        }
    
        // 一个 Promise 意味“正在加载”
        if (installedChunkData) {
            return installedChunkData[2];
        }
        
        var promise = new Promise(function(resolve, reject) {
            installedChunkData = installedChunks[chunkId] = [resolve, reject];
        });
        installedChunkData[2] = promise;
    
        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;
    
        if(__webpack_require__.nc) {
            script.setAttribute("nonce", __webpack_require__.nc);
        }
        script.src = __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".bundle.js";
        var timeout = setTimeout(onScriptComplete, 120000);
        script.onerror = script.onload = onScriptComplete;
        function onScriptComplete() {
            // avoid mem leaks in IE.
            script.onerror = script.onload = null;
            clearTimeout(timeout);
            var chunk = installedChunks[chunkId];
            if(chunk !== 0) {
                if(chunk) {
                    chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
                }
                installedChunks[chunkId] = undefined;
            }
        };
        head.appendChild(script);
    
        return promise;
    };

             最后看 webpackJsonp 函数,如下。

    var parentJsonpFunction = window["webpackJsonp"];
    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
        // 添加 “moreModules”到 modules 对象上
        // 然后所有的 “chunkIds” 已经加载并调用 callback
        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(parentJsonFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
        while(resolves.length) {
            resolves.shift()();
        }
    };
                

            该方法有三个参数,chunkIds 为该模块的先行 chunk,moreModules 为此次新添加的模块,executeModules 暂时不知道其作用。该方法先对正在加载的先行 chunk 进行处理,还记的么?正在加载的 chunk 代表其状态的对象的值为 [resolve, reject],这里将它们的 resolve 方法保存到局部定义的 resolves 数组中。还记得 modules 对象么,该对象为 webpack 生成的最外层立即执行函数的参数,这里继续将 moreModules 安装到 modules 对象中。最后执行保存在 resolves 数组中的 resolve 方法。

            可见异步加载模块时主要是使用 Promise 来包装 jsonp 请求需要的 chunk 文件,之后将其添加到 modules 参数中,随后的调用与同步的步骤的相同的。

  • 相关阅读:
    boostrapvalidator
    bootstrap 整理
    emil 的使用
    sass笔记
    sql 语句的优化
    多线程笔记
    mysql笔记
    react
    优雅的创建map/list集合
    spring中路径的注入
  • 原文地址:https://www.cnblogs.com/SyMind/p/8588014.html
Copyright © 2020-2023  润新知