zoukankan      html  css  js  c++  java
  • Webpack编译结果浅析

    如今Webpack已经是一个不可或缺的前端构建工具,借助这个构建工具,我们可以使用比较新的技术(浏览器不能直接支持)来开发。

    你是否好奇你写的代码经过Webpack构建之后会生成什么东西?是否有时调试遇到莫名其妙的问题?

    本文不讲如何进行配置,只是基于几个基础的例子,简要分析一下 webpack@4.20.2 构建后的代码结构,当然了,并不全面,时间问题能力问题还不能理解到位。

    代码比较长,生成的代码也比较晦涩比较绕,也可能条理不顺,客官坐好咧~

    一、Webpack的运行机制

    Webpack的运行过程实际上可以归纳为这个步骤

    读取配置参数 -> 相关事件绑定(插件参与) ->  识别各入口Entry模块 -> 编译文件(loader参与)-> 生成文件

    首先读取我们的配置文件如 webpack.config.js,然后事件流就参与进来绑定相关的事件,Webpack中的事件使用 Tapable 来管理,在这一阶段,除了绑定webpack内置的一大堆事件之外,还支持自定义的一些事件处理。

    配置中的 plugins部分,实际上也可以看作是一些自定义的事件处理,因为插件将在定义的”相关时刻“插入到编译过程中处理资源,这里的”相关时刻“指的就是 订阅-发布 模式中的发布环节

    webpack支持多个入口模块,所以还需要进行各入口模块的分析(这里的入口模块只能为JS模块),比如以下两个入口模块

    分析完入口模块,接下来分析该模块的依赖,并使用相关loader进行编译(如果需要loader的话),真正的编译环节是在这里。

    期间会使用AST抽象语法树来分析语法,直到编译完成,输出到相应的文件中

    可以来看看这篇文章 Webpack运行机制

    二、Webpack编译结果

    由最简单的例子开始

    2.1 无依赖的单个模块

    ./main.js

    console.log('main');

    ./webpack.config.js

    module.exports = {
        // entry: './main',
        entry: {
            main: './main'
        },
    
        mode: 'none',
    
        output: {
            path: path.resolve(__dirname, 'dist'),
            filename: '[name].js'
        }
    };

    注意,在webpack4中默认的mode对 development和production进行了一些特殊配置,为了简化,这里就设置成none

    编译一个文件,将在dist目录中生成

    ./dist/main.js

     1 /******/ (function(modules) { // webpackBootstrap
     2 /******/     // The module cache
     3 /******/     var installedModules = {};
     4 /******/
     5 /******/     // The require function
     6 /******/     function __webpack_require__(moduleId) {
     7 /******/
     8 /******/         // Check if module is in cache
     9 /******/         if(installedModules[moduleId]) {
    10 /******/             return installedModules[moduleId].exports;
    11 /******/         }
    12 /******/         // Create a new module (and put it into the cache)
    13 /******/         var module = installedModules[moduleId] = {
    14 /******/             i: moduleId,
    15 /******/             l: false,
    16 /******/             exports: {}
    17 /******/         };
    18 /******/
    19 /******/         // Execute the module function
    20 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    21 /******/
    22 /******/         // Flag the module as loaded
    23 /******/         module.l = true;
    24 /******/
    25 /******/         // Return the exports of the module
    26 /******/         return module.exports;
    27 /******/     }
    28 /******/
    29 /******/
    30 /******/     // expose the modules object (__webpack_modules__)
    31 /******/     __webpack_require__.m = modules;
    32 /******/
    33 /******/     // expose the module cache
    34 /******/     __webpack_require__.c = installedModules;
    35 /******/
    36 /******/     // define getter function for harmony exports
    37 /******/     __webpack_require__.d = function(exports, name, getter) {
    38 /******/         if(!__webpack_require__.o(exports, name)) {
    39 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
    40 /******/         }
    41 /******/     };
    42 /******/
    43 /******/     // define __esModule on exports
    44 /******/     __webpack_require__.r = function(exports) {
    45 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    46 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    47 /******/         }
    48 /******/         Object.defineProperty(exports, '__esModule', { value: true });
    49 /******/     };
    50 /******/
    51 /******/     // create a fake namespace object
    52 /******/     // mode & 1: value is a module id, require it
    53 /******/     // mode & 2: merge all properties of value into the ns
    54 /******/     // mode & 4: return value when already ns object
    55 /******/     // mode & 8|1: behave like require
    56 /******/     __webpack_require__.t = function(value, mode) {
    57 /******/         if(mode & 1) value = __webpack_require__(value);
    58 /******/         if(mode & 8) return value;
    59 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    60 /******/         var ns = Object.create(null);
    61 /******/         __webpack_require__.r(ns);
    62 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    63 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
    64 /******/         return ns;
    65 /******/     };
    66 /******/
    67 /******/     // getDefaultExport function for compatibility with non-harmony modules
    68 /******/     __webpack_require__.n = function(module) {
    69 /******/         var getter = module && module.__esModule ?
    70 /******/             function getDefault() { return module['default']; } :
    71 /******/             function getModuleExports() { return module; };
    72 /******/         __webpack_require__.d(getter, 'a', getter);
    73 /******/         return getter;
    74 /******/     };
    75 /******/
    76 /******/     // Object.prototype.hasOwnProperty.call
    77 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    78 /******/
    79 /******/     // __webpack_public_path__
    80 /******/     __webpack_require__.p = "";
    81 /******/
    82 /******/
    83 /******/     // Load entry module and return exports
    84 /******/     return __webpack_require__(__webpack_require__.s = 0);
    85 /******/ })
    86 /************************************************************************/
    87 /******/ ([
    88 /* 0 */
    89 /***/ (function(module, exports) {
    90 
    91 
    92 console.log('main');
    93 
    94 
    95 /***/ })
    96 /******/ ]);

    可以看到首先是一个匿名函数,在87行时自执行传入

    [
    /* 0 */
    /***/ (function(module, exports) {
    
    
    console.log('main');
    
    
    /***/ })
    /******/ ]

    这个是modules,表示有一个模块需要加载

    第3行使用 installedModules 来缓存已经加载的模块

    webpack由最初支持 commonjs模块规范,到后来要支持es6的模块等,为了兼容不同的模块机制,定义了一个 __webpack_require__ 函数作为webpack内部的require

    /******/     // The require function
    /******/     function __webpack_require__(moduleId) {
    /******/
    /******/         // Check if module is in cache
                    // 如果模块已经加载则直接使用
    /******/         if(installedModules[moduleId]) {
    /******/             return installedModules[moduleId].exports;
    /******/         }
    /******/         // Create a new module (and put it into the cache)
    /******/         var module = installedModules[moduleId] = {
    /******/             i: moduleId, // 模块ID
    /******/             l: false, // 模块是否已加载
    /******/             exports: {} // 模块的导出项
    /******/         };
    /******/
    /******/         // Execute the module function
    /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    /******/
    /******/         // Flag the module as loaded
    /******/         module.l = true; // 标记已经加载
    /******/
    /******/         // Return the exports of the module
    /******/         return module.exports; // 返回模块的导出项目
    /******/     }

    其中,这个调用非常重要

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

    结合匿名函数传入的参数来看,modules[moduleId] 其实就是这个

    (function(module, exports) {
    
    
    console.log('main');
    
    
    /***/ })

    第一个参数 module.exports 实际上就是上面模块的导出项,是为了保证this能正确地指向module,第二第三个参数按着顺序来,第四个参数一般用于依赖

    因为这里 main.js没有依赖其他模块,所以没有传进来

    最后 return module.exports; 实际上就是返回了模块的导出项,在上面的84行中,入口模块被引入 。从而自动地加载第一个模块并执行

    return __webpack_require__(__webpack_require__.s = 0); // __webpack_require__.s为入口文件,此处引用模块ID

    另外再看其它代码,

    /******/     // expose the modules object (__webpack_modules__)
    /******/     __webpack_require__.m = modules; // 将模块存起来
    /******/
    /******/     // expose the module cache
    /******/     __webpack_require__.c = installedModules; // 将已经加载的模块存起来
    
    /******/     // __webpack_public_path__
    /******/     __webpack_require__.p = ""; // 设置的 publicPath

    这里没什么可说的,这里的publicPath对应于 output中的配置,如

    output: {
            publicPath: './dist/',
            path: path.resolve(__dirname, 'dist'),
            filename: '[name].js'
        },

    另外

    /******/     // define getter function for harmony exports
    /******/     __webpack_require__.d = function(exports, name, getter) {
    /******/         if(!__webpack_require__.o(exports, name)) {
    /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
    /******/         }
    /******/     };
    /******/
    /******/     // define __esModule on exports
    /******/     __webpack_require__.r = function(exports) {
    /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    /******/         }
    /******/         Object.defineProperty(exports, '__esModule', { value: true });
    /******/     };
    
    /******/     // Object.prototype.hasOwnProperty.call
    /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

    这里 __webpack_require__.o 这里只是hasOwnProperty的包装

    __webpack_require__.d 这里是对exports定义一个属性(当前模块未用到,暂且如此,理解不到位)

    __webpack_require__.r 这里是对es6模块中的export的支持(当前模块未用到,暂且如此,理解不到位)

    还有这个,这个就更难理解了

    /******/     // create a fake namespace object
    /******/     // mode & 1: value is a module id, require it
    /******/     // mode & 2: merge all properties of value into the ns
    /******/     // mode & 4: return value when already ns object
    /******/     // mode & 8|1: behave like require
    /******/     __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;
    /******/     };

    __webpack_require__.t 暂时不说明了,还看不懂怎么调用的..

    __webpack_require__.n 这个主要也是为 es6模块服务的,也没能理解好,知道的可以在评论区留言哈~

    2. 有依赖的单个模块

    先使用最基础的commonjs模块规范  require, exports ,module.exports 有助于理解上面那个模块的导出项目

    ./main.js

    let number = require('./number');
    
    console.log('main', number);

    ./number.js

    let n = 10;
    
    exports.n = n;

    编译后,生成的文件变化的只是匿名函数传入的部分

    ./dist/main.js

    // 省略
    
    /******/ ([
    /* 0 */
    /***/ (function(module, exports, __webpack_require__) {
    
    
    let number = __webpack_require__(1);
    
    console.log('main', number);
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, exports) {
    
    
    let n = 10;
    
    exports.n = n;
    
    
    /***/ })
    /******/ ]);

    注意到前面的数字即是模块的ID,也可图中的一致

    这里__webpack_require__参数被传进来,main.js中引入number这个模块 __webpack_require__(1);

    number模块中 exports.n = n,注意这里的 exports即是调用时的第二个参数

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

    所以此时 n属性被存入module的export导出项中,从而__webpack_require__(1) 就能获取这个导出项

    换种方式,使用es6的模块导出

    更改 ./number.js

    let n = 10;
    
    export {
        n
    };

    编译后 ./dist/main.js

    /******/ ([
    /* 0 */
    /***/ (function(module, exports, __webpack_require__) {
    
    
    let number = __webpack_require__(1);
    
    console.log('main', number);
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });
    
    let n = 10;
    
    
    
    
    /***/ })
    /******/ ]);

    可以看到模块1变了,为了兼容 export ,使用 __webpack_require__.r 定义了它为es6模块,再使用__webpack_require__.d 将 n保存到模块的导出项中

    __webpack_require__.d 函数中的 getter即为 这里的 function() { return n; },通过设置为对象的get属性,可以获取到 n这个返回值

    var o = {};
    
    Object.defineProperty(o, 'abc', {
        get: function() {
            return 123;
        }
    });
    
    console.log(o.abc); // 123

    所以将 let n = 10 定义在后面也是没问题的,因为getter是在number模块被调用返回之后才使用的

    接着,我们把引入依赖文件改为import

    ./main.js

    import {n} from './number';
    
    console.log('main', n);

    编译后 ./dist/main.js

    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
    
    
    
    console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["n"]);
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });
    
    let n = 10;
    
    
    
    
    /***/ })
    /******/ ]);

    同样的,这时main模块用到了es6的模块引入方式,所以 __webpack_require__.r(__webpack_exports__);

    var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);

    这个 __webpack_require__(1) 实际上就是 number模块的模块导出项,自然就能取到属性 n 了

    接下来,着眼那个 default字眼,继续更换模块的导入导出方式

    ./main.js

    import n from './number';
    
    console.log('main', n);

    ./number.js

    let n = 10;
    
    export default n;

    ./dist/main.js

    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
    
    
    
    console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["default"]);
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    
    let n = 10;
    
    /* harmony default export */ __webpack_exports__["default"] = (n);
    
    
    /***/ })
    /******/ ]);

    可以看到,变化只是属性变成了default

    再来一种 es6的方式

    ./main.js

    import n from './number';
    
    console.log('main', n);

    ./number.js

    import {str as n} from './str';
    
    export default n;

    ./str.js

    export var str = 10;

    编译后

    ./dist/main.js

    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _number__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
    
    
    
    console.log('main', _number__WEBPACK_IMPORTED_MODULE_0__["default"]);
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _str__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
    
    
    
    /* harmony default export */ __webpack_exports__["default"] = (_str__WEBPACK_IMPORTED_MODULE_0__["str"]);
    
    
    /***/ }),
    /* 2 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str", function() { return str; });
    var str = 10;
    
    
    /***/ })
    /******/ ]);

    可以看到 {str as n} 也是没什么影响的,通过上面的例子应该基本能理解模块的依赖了

    3. 多个入口模块

    如果不提取多模块之间的公共部分,多个入口模块和单个的不同之处就是多了一个文件而已,它们是独立的。

    所以这里就不多说了

    4. 异步加载模块

    webpack支持使用require.ensure来异步加载模块

    ./main.js

    console.log('main');
    
    setTimeout(() => {
        require([], (require) => {
            let number = require('./number');
    
            console.log(number.n);
        });
    }, 1000);

    ./number.js

    let n = 10;
    
    export {
        n
    };

    webpack.config.js中要加上 publicPath,防止异步模块加载路径出错

    output: {
            publicPath: './dist/',
            path: path.resolve(__dirname, 'dist'),
            filename: '[name].js'
        }

    编译后,生成的 1.js即为异步的模块number

    ./dist/1.js

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
    /* 0 */,
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });
    
    
    let n = 10;
    
    
    
    
    /***/ })
    ]]);

    可以看到,这里首先获取 (window["webpackJsonp"] = window["webpackJsonp"] || []), 再调用 push 传入模块及其依赖

    jsonp类似我们跨域中的动态插入脚本,这里也是一样,动态插入一个script标签,把src设置好就加载这个异步模块了

    push参数中第一个为当前异步模块

    看看 ./dist/main.js

      1 /******/ (function(modules) { // webpackBootstrap
      2 /******/     // install a JSONP callback for chunk loading
      3 /******/     function webpackJsonpCallback(data) {
      4 /******/         var chunkIds = data[0];
      5 /******/         var moreModules = data[1];
      6 /******/
      7 /******/
      8 /******/         // add "moreModules" to the modules object,
      9 /******/         // then flag all "chunkIds" as loaded and fire callback
     10 /******/         var moduleId, chunkId, i = 0, resolves = [];
     11 /******/         for(;i < chunkIds.length; i++) {
     12 /******/             chunkId = chunkIds[i];
     13 /******/             if(installedChunks[chunkId]) {
     14 /******/                 resolves.push(installedChunks[chunkId][0]);
     15 /******/             }
     16 /******/             installedChunks[chunkId] = 0;
     17 /******/         }
     18 /******/         for(moduleId in moreModules) {
     19 /******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
     20 /******/                 modules[moduleId] = moreModules[moduleId];
     21 /******/             }
     22 /******/         }
     23 /******/         if(parentJsonpFunction) parentJsonpFunction(data);
     24 /******/
     25 /******/         while(resolves.length) {
     26 /******/             resolves.shift()();
     27 /******/         }
     28 /******/
     29 /******/     };
     30 /******/
     31 /******/
     32 /******/     // The module cache
     33 /******/     var installedModules = {};
     34 /******/
     35 /******/     // object to store loaded and loading chunks
     36 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
     37 /******/     // Promise = chunk loading, 0 = chunk loaded
     38 /******/     var installedChunks = {
     39 /******/         0: 0
     40 /******/     };
     41 /******/
     42 /******/
     43 /******/
     44 /******/     // script path function
     45 /******/     function jsonpScriptSrc(chunkId) {
     46 /******/         return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js"
     47 /******/     }
     48 /******/
     49 /******/     // The require function
     50 /******/     function __webpack_require__(moduleId) {
     51 /******/
     52 /******/         // Check if module is in cache
     53 /******/         if(installedModules[moduleId]) {
     54 /******/             return installedModules[moduleId].exports;
     55 /******/         }
     56 /******/         // Create a new module (and put it into the cache)
     57 /******/         var module = installedModules[moduleId] = {
     58 /******/             i: moduleId,
     59 /******/             l: false,
     60 /******/             exports: {}
     61 /******/         };
     62 /******/
     63 /******/         // Execute the module function
     64 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     65 /******/
     66 /******/         // Flag the module as loaded
     67 /******/         module.l = true;
     68 /******/
     69 /******/         // Return the exports of the module
     70 /******/         return module.exports;
     71 /******/     }
     72 /******/
     73 /******/     // This file contains only the entry chunk.
     74 /******/     // The chunk loading function for additional chunks
     75 /******/     __webpack_require__.e = function requireEnsure(chunkId) {
     76 /******/         var promises = [];
     77 /******/
     78 /******/
     79 /******/         // JSONP chunk loading for javascript
     80 /******/
     81 /******/         var installedChunkData = installedChunks[chunkId];
     82 /******/         if(installedChunkData !== 0) { // 0 means "already installed".
     83 /******/
     84 /******/             // a Promise means "currently loading".
     85 /******/             if(installedChunkData) {
     86 /******/                 promises.push(installedChunkData[2]);
     87 /******/             } else {
     88 /******/                 // setup Promise in chunk cache
     89 /******/                 var promise = new Promise(function(resolve, reject) {
     90 /******/                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
     91 /******/                 });
     92 /******/                 promises.push(installedChunkData[2] = promise);
     93 /******/
     94 /******/                 // start chunk loading
     95 /******/                 var head = document.getElementsByTagName('head')[0];
     96 /******/                 var script = document.createElement('script');
     97 /******/                 var onScriptComplete;
     98 /******/
     99 /******/                 script.charset = 'utf-8';
    100 /******/                 script.timeout = 120;
    101 /******/                 if (__webpack_require__.nc) {
    102 /******/                     script.setAttribute("nonce", __webpack_require__.nc);
    103 /******/                 }
    104 /******/                 script.src = jsonpScriptSrc(chunkId);
    105 /******/
    106 /******/                 onScriptComplete = function (event) {
    107 /******/                     // avoid mem leaks in IE.
    108 /******/                     script.onerror = script.onload = null;
    109 /******/                     clearTimeout(timeout);
    110 /******/                     var chunk = installedChunks[chunkId];
    111 /******/                     if(chunk !== 0) {
    112 /******/                         if(chunk) {
    113 /******/                             var errorType = event && (event.type === 'load' ? 'missing' : event.type);
    114 /******/                             var realSrc = event && event.target && event.target.src;
    115 /******/                             var error = new Error('Loading chunk ' + chunkId + ' failed.
    (' + errorType + ': ' + realSrc + ')');
    116 /******/                             error.type = errorType;
    117 /******/                             error.request = realSrc;
    118 /******/                             chunk[1](error);
    119 /******/                         }
    120 /******/                         installedChunks[chunkId] = undefined;
    121 /******/                     }
    122 /******/                 };
    123 /******/                 var timeout = setTimeout(function(){
    124 /******/                     onScriptComplete({ type: 'timeout', target: script });
    125 /******/                 }, 120000);
    126 /******/                 script.onerror = script.onload = onScriptComplete;
    127 /******/                 head.appendChild(script);
    128 /******/             }
    129 /******/         }
    130 /******/         return Promise.all(promises);
    131 /******/     };
    132 /******/
    133 /******/     // expose the modules object (__webpack_modules__)
    134 /******/     __webpack_require__.m = modules;
    135 /******/
    136 /******/     // expose the module cache
    137 /******/     __webpack_require__.c = installedModules;
    138 /******/
    139 /******/     // define getter function for harmony exports
    140 /******/     __webpack_require__.d = function(exports, name, getter) {
    141 /******/         if(!__webpack_require__.o(exports, name)) {
    142 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
    143 /******/         }
    144 /******/     };
    145 /******/
    146 /******/     // define __esModule on exports
    147 /******/     __webpack_require__.r = function(exports) {
    148 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    149 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    150 /******/         }
    151 /******/         Object.defineProperty(exports, '__esModule', { value: true });
    152 /******/     };
    153 /******/
    154 /******/     // create a fake namespace object
    155 /******/     // mode & 1: value is a module id, require it
    156 /******/     // mode & 2: merge all properties of value into the ns
    157 /******/     // mode & 4: return value when already ns object
    158 /******/     // mode & 8|1: behave like require
    159 /******/     __webpack_require__.t = function(value, mode) {
    160 /******/         if(mode & 1) value = __webpack_require__(value);
    161 /******/         if(mode & 8) return value;
    162 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    163 /******/         var ns = Object.create(null);
    164 /******/         __webpack_require__.r(ns);
    165 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    166 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
    167 /******/         return ns;
    168 /******/     };
    169 /******/
    170 /******/     // getDefaultExport function for compatibility with non-harmony modules
    171 /******/     __webpack_require__.n = function(module) {
    172 /******/         var getter = module && module.__esModule ?
    173 /******/             function getDefault() { return module['default']; } :
    174 /******/             function getModuleExports() { return module; };
    175 /******/         __webpack_require__.d(getter, 'a', getter);
    176 /******/         return getter;
    177 /******/     };
    178 /******/
    179 /******/     // Object.prototype.hasOwnProperty.call
    180 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    181 /******/
    182 /******/     // __webpack_public_path__
    183 /******/     __webpack_require__.p = "./dist/";
    184 /******/
    185 /******/     // on error function for async loading
    186 /******/     __webpack_require__.oe = function(err) { console.error(err); throw err; };
    187 /******/
    188 /******/     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
    189 /******/     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
    190 /******/     jsonpArray.push = webpackJsonpCallback;
    191 /******/     jsonpArray = jsonpArray.slice();
    192 /******/     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
    193 /******/     var parentJsonpFunction = oldJsonpFunction;
    194 /******/
    195 /******/
    196 /******/     // Load entry module and return exports
    197 /******/     return __webpack_require__(__webpack_require__.s = 0);
    198 /******/ })
    199 /************************************************************************/
    200 /******/ ([
    201 /* 0 */
    202 /***/ (function(module, exports, __webpack_require__) {
    203 
    204 
    205 
    206 console.log('main');
    207 
    208 setTimeout(() => {
    209     __webpack_require__.e(/* AMD require */ 1).then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = []; ((require) => {
    210         let number = __webpack_require__(1);
    211 
    212         console.log(number.n);
    213     }).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);}).catch(__webpack_require__.oe);
    214 }, 1000);
    215 
    216 
    217 /***/ })
    218 /******/ ]);

    这下蹦出了许多代码,从这里开始会比较绕,需要有耐心!

    按照代码执行顺序来分析,思路就清晰了

    38行中定义了installedChunks这个新变量,它指代依赖模块(不仅包括此处的异步模块,也包括后续会说到的公共模块,runtime模块等),而上面installedModules指的是所有的模块

    /******/     // object to store loaded and loading chunks
    /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
    /******/     // Promise = chunk loading, 0 = chunk loaded
    /******/     var installedChunks = {
    /******/         0: 0
    /******/     };

    前面的0表示模块ID,在这里指的就是 ./main.js这个入口模块了,它初始的状态就被webpack设置成已加载

    /******/     // script path function
    /******/     function jsonpScriptSrc(chunkId) {
    /******/         return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js"
    /******/     }

    这里就是异步模块的路径了,({}[chunkId]||chunkId) 这个只是为了防止出错做的处理

    __webpack_require__ 函数的内容没变

    75行多了一个 __webpack_require__.e 用来加载异步模块,这个稍后再讲

    继续到182行开始

    /******/     // __webpack_public_path__
    /******/     __webpack_require__.p = "./dist/";
    /******/
    /******/     // on error function for async loading
    /******/     __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;
    /******/
    /******/
    /******/     // Load entry module and return exports
    /******/     return __webpack_require__(__webpack_require__.s = 0);

    这里的publicPath就是我们刚刚设置的

    __webpack_require__.oe 只是用于处理错误

    初始会判断是否有window["webpackJsonp"]存在,有的话就缓存起来,并将this的指向设置好 jsonpArray.push.bind(jsonpArray)

    要理清楚 jsonpArray.push ,它不是简单的数组,所以有些绕,它指向了第3行webpackJsonpCallback这个函数

    如果初始已经有待加载的依赖模块,则在for循环中直接加载。此处初始阶段是没有值的,所以可以直接略过

    要看明白webpackJsonpCallback这个函数,得从调用它的地方开始,在216行中开始调用

    setTimeout(() => {
        __webpack_require__.e(/* AMD require */ 1).then(function() { var __WEBPACK_AMD_REQUIRE_ARRAY__ = []; ((require) => {
            let number = __webpack_require__(1);
    
            console.log(number.n);
        }).apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);}).catch(__webpack_require__.oe);
    }, 1000);
    /******/     // This file contains only the entry chunk.
    /******/     // The chunk loading function for additional chunks
    /******/     __webpack_require__.e = function requireEnsure(chunkId) {
    /******/         var promises = []; // promise队列,支持模块加载完成后多个异步回调
    /******/
    /******/
    /******/         // JSONP chunk loading for javascript
    /******/
    /******/         var installedChunkData = installedChunks[chunkId];
                    // 未加载
    /******/         if(installedChunkData !== 0) { // 0 means "already installed".
    /******/
    /******/             // a Promise means "currently loading".
                        // 加载中,则支持下一个回调加入
    /******/             if(installedChunkData) {
    /******/                 promises.push(installedChunkData[2]);
    /******/             } else {
                            // 初始化一个promise来加载
    /******/                 // setup Promise in chunk cache
    /******/                 var promise = new Promise(function(resolve, reject) {
                                // 将resolve和reject存入模块中,方便其他地方调用
    /******/                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
    /******/                 });
                            // installedChunkData的第三项即为一个promise对象,并存入promises队列中
    /******/                 promises.push(installedChunkData[2] = promise);
    /******/
    /******/                 // start chunk loading
    /******/                 var head = document.getElementsByTagName('head')[0];
    /******/                 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);
    /******/
    /******/                 onScriptComplete = function (event) {
    /******/                     // avoid mem leaks in IE.
    /******/                     script.onerror = script.onload = null;
    /******/                     clearTimeout(timeout);
    /******/                     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;
    /******/                             var error = new Error('Loading chunk ' + chunkId + ' failed.
    (' + errorType + ': ' + realSrc + ')');
    /******/                             error.type = errorType;
    /******/                             error.request = realSrc;
                                        // 调用reject
    /******/                             chunk[1](error);
    /******/                         }
    /******/                         installedChunks[chunkId] = undefined;
    /******/                     }
    /******/                 };
    /******/                 var timeout = setTimeout(function(){
    /******/                     onScriptComplete({ type: 'timeout', target: script });
    /******/                 }, 120000);
    /******/                 script.onerror = script.onload = onScriptComplete;
                            // 在head标签中插入脚本
    /******/                 head.appendChild(script);
    /******/             }
    /******/         }
    /******/         return Promise.all(promises);
    /******/     };

    一秒钟后加载这个异步模块 ./1.js ,该模块加载完成后就开始执行

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
    /* 0 */,
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });
    
    
    let n = 10;
    
    
    
    
    /***/ })
    ]]);

    此时的 window["webpackJsonp"] 已经被这句代码影响,jsonpArray.push = webpackJsonpCallback; 所以push实际上调用的是 webpackJsonpCallback函数

    /******/     // install a JSONP callback for chunk loading
    /******/     function webpackJsonpCallback(data) {
    /******/         var chunkIds = data[0]; // 依赖的模块ID,此时是[1]
    /******/         var moreModules = data[1]; // 依赖的模块内容
    /******/
    /******/
    /******/         // add "moreModules" to the modules object,
    /******/         // then flag all "chunkIds" as loaded and fire callback
    /******/         var moduleId, chunkId, i = 0, resolves = [];
                    // 遍历依赖的模块进行加载
    /******/         for(;i < chunkIds.length; i++) {
    /******/             chunkId = chunkIds[i];
    /******/             if(installedChunks[chunkId]) {
    /******/                 resolves.push(installedChunks[chunkId][0]); // 存储将要执行的resolve
    /******/             }
    /******/             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()(); // 执行所有resolve
    /******/         }
    /******/
    /******/     };

    如果多依赖一个呢

    ./main.js

    console.log('main');
    
    setTimeout(() => {
        require(['./str'], (require) => {
            let number = require('./number');
    
            console.log(number.n);
        });
    }, 1000);

    这时只有 ./1.js改变了,差不不大,一样的道理

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
    /* 0 */,
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str", function() { return str; });
    var str = 10;
    
    
    /***/ }),
    /* 2 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });
    
    
    let n = 10;
    
    
    
    
    /***/ })
    ]]);

    5. 提取公共模块

    ./webpack.config.js

    entry: {
            main: './main',
            test: './test'
        },
    
    optimization: {
            // 提取公共部分为common.js,使劲地提取吧.. 
            splitChunks: {
                name: 'common',
                chunks: 'all',
                minSize: 1
            }
        },

    ./main.js

    import './chunk';
    
    import {n} from './number';
    
    console.log('main', n);

    ./test.js

    import './chunk';
    
    console.log('test');

    编译后

    ./dist/common.js

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
    /* 0 */,
    /* 1 */
    /***/ (function(module, exports) {
    
    console.log('chunk');
    
    
    /***/ })
    ]]);

    可以看到 chunk模块(ID为1)被共用,被提取出来

    再看看 ./dist/test.js

      1 /******/ (function(modules) { // webpackBootstrap
      2 /******/     // install a JSONP callback for chunk loading
      3 /******/     function webpackJsonpCallback(data) {
      4 /******/         var chunkIds = data[0];
      5 /******/         var moreModules = data[1];
      6 /******/         var executeModules = data[2];
      7 /******/
      8 /******/         // add "moreModules" to the modules object,
      9 /******/         // then flag all "chunkIds" as loaded and fire callback
     10 /******/         var moduleId, chunkId, i = 0, resolves = [];
     11 /******/         for(;i < chunkIds.length; i++) {
     12 /******/             chunkId = chunkIds[i];
     13 /******/             if(installedChunks[chunkId]) {
     14 /******/                 resolves.push(installedChunks[chunkId][0]);
     15 /******/             }
     16 /******/             installedChunks[chunkId] = 0;
     17 /******/         }
     18 /******/         for(moduleId in moreModules) {
     19 /******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
     20 /******/                 modules[moduleId] = moreModules[moduleId];
     21 /******/             }
     22 /******/         }
     23 /******/         if(parentJsonpFunction) parentJsonpFunction(data);
     24 /******/
     25 /******/         while(resolves.length) {
     26 /******/             resolves.shift()();
     27 /******/         }
     28 /******/
     29 /******/         // add entry modules from loaded chunk to deferred list
     30 /******/         deferredModules.push.apply(deferredModules, executeModules || []);
     31 /******/
     32 /******/         // run deferred modules when all chunks ready
     33 /******/         return checkDeferredModules();
     34 /******/     };
     35 /******/     function checkDeferredModules() {
     36 /******/         var result;
     37 /******/         for(var i = 0; i < deferredModules.length; i++) {
     38 /******/             var deferredModule = deferredModules[i];
     39 /******/             var fulfilled = true;
     40 /******/             for(var j = 1; j < deferredModule.length; j++) {
     41 /******/                 var depId = deferredModule[j];
     42 /******/                 if(installedChunks[depId] !== 0) fulfilled = false;
     43 /******/             }
     44 /******/             if(fulfilled) {
     45 /******/                 deferredModules.splice(i--, 1);
     46 /******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
     47 /******/             }
     48 /******/         }
     49 /******/         return result;
     50 /******/     }
     51 /******/
     52 /******/     // The module cache
     53 /******/     var installedModules = {};
     54 /******/
     55 /******/     // object to store loaded and loading chunks
     56 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
     57 /******/     // Promise = chunk loading, 0 = chunk loaded
     58 /******/     var installedChunks = {
     59 /******/         2: 0
     60 /******/     };
     61 /******/
     62 /******/     var deferredModules = [];
     63 /******/
     64 /******/     // The require function
     65 /******/     function __webpack_require__(moduleId) {
     66 /******/
     67 /******/         // Check if module is in cache
     68 /******/         if(installedModules[moduleId]) {
     69 /******/             return installedModules[moduleId].exports;
     70 /******/         }
     71 /******/         // Create a new module (and put it into the cache)
     72 /******/         var module = installedModules[moduleId] = {
     73 /******/             i: moduleId,
     74 /******/             l: false,
     75 /******/             exports: {}
     76 /******/         };
     77 /******/
     78 /******/         // Execute the module function
     79 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     80 /******/
     81 /******/         // Flag the module as loaded
     82 /******/         module.l = true;
     83 /******/
     84 /******/         // Return the exports of the module
     85 /******/         return module.exports;
     86 /******/     }
     87 /******/
     88 /******/
     89 /******/     // expose the modules object (__webpack_modules__)
     90 /******/     __webpack_require__.m = modules;
     91 /******/
     92 /******/     // expose the module cache
     93 /******/     __webpack_require__.c = installedModules;
     94 /******/
     95 /******/     // define getter function for harmony exports
     96 /******/     __webpack_require__.d = function(exports, name, getter) {
     97 /******/         if(!__webpack_require__.o(exports, name)) {
     98 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
     99 /******/         }
    100 /******/     };
    101 /******/
    102 /******/     // define __esModule on exports
    103 /******/     __webpack_require__.r = function(exports) {
    104 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    105 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    106 /******/         }
    107 /******/         Object.defineProperty(exports, '__esModule', { value: true });
    108 /******/     };
    109 /******/
    110 /******/     // create a fake namespace object
    111 /******/     // mode & 1: value is a module id, require it
    112 /******/     // mode & 2: merge all properties of value into the ns
    113 /******/     // mode & 4: return value when already ns object
    114 /******/     // mode & 8|1: behave like require
    115 /******/     __webpack_require__.t = function(value, mode) {
    116 /******/         if(mode & 1) value = __webpack_require__(value);
    117 /******/         if(mode & 8) return value;
    118 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    119 /******/         var ns = Object.create(null);
    120 /******/         __webpack_require__.r(ns);
    121 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    122 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
    123 /******/         return ns;
    124 /******/     };
    125 /******/
    126 /******/     // getDefaultExport function for compatibility with non-harmony modules
    127 /******/     __webpack_require__.n = function(module) {
    128 /******/         var getter = module && module.__esModule ?
    129 /******/             function getDefault() { return module['default']; } :
    130 /******/             function getModuleExports() { return module; };
    131 /******/         __webpack_require__.d(getter, 'a', getter);
    132 /******/         return getter;
    133 /******/     };
    134 /******/
    135 /******/     // Object.prototype.hasOwnProperty.call
    136 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    137 /******/
    138 /******/     // __webpack_public_path__
    139 /******/     __webpack_require__.p = "./dist/";
    140 /******/
    141 /******/     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
    142 /******/     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
    143 /******/     jsonpArray.push = webpackJsonpCallback;
    144 /******/     jsonpArray = jsonpArray.slice();
    145 /******/     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
    146 /******/     var parentJsonpFunction = oldJsonpFunction;
    147 /******/
    148 /******/
    149 /******/     // add entry module to deferred list
    150 /******/     deferredModules.push([3,1]);
    151 /******/     // run deferred modules when ready
    152 /******/     return checkDeferredModules();
    153 /******/ })
    154 /************************************************************************/
    155 /******/ ({
    156 
    157 /***/ 3:
    158 /***/ (function(module, __webpack_exports__, __webpack_require__) {
    159 
    160 "use strict";
    161 __webpack_require__.r(__webpack_exports__);
    162 /* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
    163 /* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_chunk__WEBPACK_IMPORTED_MODULE_0__);
    164 
    165 
    166 
    167 console.log('test');
    168 
    169 
    170 
    171 /***/ })
    172 
    173 /******/ });

    先看150行,初始不再马上加载入口模块,而是先将入口模块和其依赖的公共模块保存起来,再进行处理加载

    /******/     // add entry module to deferred list
    /******/     deferredModules.push([3,1]); // 这里的3为test模块,1为chunk公共模块
    /******/     // run deferred modules when ready
    /******/     return checkDeferredModules();
    /******/     function checkDeferredModules() {
    /******/         var result;
                    // deferredModules的结构长这样 [[3,1]],对每一项进行处理
    /******/         for(var i = 0; i < deferredModules.length; i++) {
    /******/             var deferredModule = deferredModules[i];
    /******/             var fulfilled = true;
                        // 从第二项开始,为依赖的模块
    /******/             for(var j = 1; j < deferredModule.length; j++) {
    /******/                 var depId = deferredModule[j];
                            // 依赖的模块未加载
    /******/                 if(installedChunks[depId] !== 0) fulfilled = false;
    /******/             }
                        // 已经加载,则清除,并开始加载入口模块,deferredModule的第一项即为这里的test入口模块
    /******/             if(fulfilled) {
    /******/                 deferredModules.splice(i--, 1);
    /******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
    /******/             }
    /******/         }
    /******/         return result;
    /******/     }

    注意到这里也有 webpackJsonpCallback 函数,不过它的参数数组中有三项,第三项 var executeModules = data[2]; 暂时还没用到,先略过

    /******/         // add entry modules from loaded chunk to deferred list
    /******/         deferredModules.push.apply(deferredModules, executeModules || []);
    /******/
    /******/         // run deferred modules when all chunks ready
    /******/         return checkDeferredModules();

    上面这个,主要是为了兼容公共模块和入口模块的兼容顺序,什么意思呢?

    假如没有这段代码,那么这样是可行的

    <script type="text/javascript" src="./dist/common.js"></script>
    <script type="text/javascript" src="./dist/main.js"></script>

    但common放后面就不行

    <script type="text/javascript" src="./dist/main.js"></script>
    <script type="text/javascript" src="./dist/common.js"></script>

    common放在后面会导致初始调用checkDeferredModules时 公共模块的fulfilled为false,此时将无法加载入口模块

    所以需要在webpackJsonpCallback中再判断处理一次

    6. 提取runtime运行时模块

    上面代码中,./dist/main.js 和 ./dist/test.js 都有很多运行时的代码,我们可以将其提取出来,一并放到 common.js中

    ./webpack.config.js

    optimization: {
            // 提取runtime代码到common.js文件中
            runtimeChunk: {
                name: 'common'
            },
            // 提取公共部分为common.js,使劲地提取吧..
            splitChunks: {
                name: 'common',
                chunks: 'all',
                minSize: 1
            }
        },

    编译后,看看 ./dist/test.js 干净了许多

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[2],{
    
    /***/ 3:
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
    /* harmony import */ var _chunk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_chunk__WEBPACK_IMPORTED_MODULE_0__);
    
    
    
    console.log('test');
    
    
    
    /***/ })
    
    },[[3,1]]]);

    不过,注意到这里push的参数多了第三项 [[3,1]],根据上面的分析,这个1应该就是公共模块了

    来看看 ./dist/common.js

      1 /******/ (function(modules) { // webpackBootstrap
      2 /******/     // install a JSONP callback for chunk loading
      3 /******/     function webpackJsonpCallback(data) {
      4 /******/         var chunkIds = data[0];
      5 /******/         var moreModules = data[1];
      6 /******/         var executeModules = data[2];
      7 /******/
      8 /******/         // add "moreModules" to the modules object,
      9 /******/         // then flag all "chunkIds" as loaded and fire callback
     10 /******/         var moduleId, chunkId, i = 0, resolves = [];
     11 /******/         for(;i < chunkIds.length; i++) {
     12 /******/             chunkId = chunkIds[i];
     13 /******/             if(installedChunks[chunkId]) {
     14 /******/                 resolves.push(installedChunks[chunkId][0]);
     15 /******/             }
     16 /******/             installedChunks[chunkId] = 0;
     17 /******/         }
     18 /******/         for(moduleId in moreModules) {
     19 /******/             if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
     20 /******/                 modules[moduleId] = moreModules[moduleId];
     21 /******/             }
     22 /******/         }
     23 /******/         if(parentJsonpFunction) parentJsonpFunction(data);
     24 /******/
     25 /******/         while(resolves.length) {
     26 /******/             resolves.shift()();
     27 /******/         }
     28 /******/
     29 /******/         // add entry modules from loaded chunk to deferred list
     30 /******/         deferredModules.push.apply(deferredModules, executeModules || []);
     31 /******/
     32 /******/         // run deferred modules when all chunks ready
     33 /******/         return checkDeferredModules();
     34 /******/     };
     35 /******/     function checkDeferredModules() {
     36 /******/         var result;
     37 /******/         for(var i = 0; i < deferredModules.length; i++) {
     38 /******/             var deferredModule = deferredModules[i];
     39 /******/             var fulfilled = true;
     40 /******/             for(var j = 1; j < deferredModule.length; j++) {
     41 /******/                 var depId = deferredModule[j];
     42 /******/                 if(installedChunks[depId] !== 0) fulfilled = false;
     43 /******/             }
     44 /******/             if(fulfilled) {
     45 /******/                 deferredModules.splice(i--, 1);
     46 /******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
     47 /******/             }
     48 /******/         }
     49 /******/         return result;
     50 /******/     }
     51 /******/
     52 /******/     // The module cache
     53 /******/     var installedModules = {};
     54 /******/
     55 /******/     // object to store loaded and loading chunks
     56 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
     57 /******/     // Promise = chunk loading, 0 = chunk loaded
     58 /******/     var installedChunks = {
     59 /******/         1: 0
     60 /******/     };
     61 /******/
     62 /******/     var deferredModules = [];
     63 /******/
     64 /******/     // The require function
     65 /******/     function __webpack_require__(moduleId) {
     66 /******/
     67 /******/         // Check if module is in cache
     68 /******/         if(installedModules[moduleId]) {
     69 /******/             return installedModules[moduleId].exports;
     70 /******/         }
     71 /******/         // Create a new module (and put it into the cache)
     72 /******/         var module = installedModules[moduleId] = {
     73 /******/             i: moduleId,
     74 /******/             l: false,
     75 /******/             exports: {}
     76 /******/         };
     77 /******/
     78 /******/         // Execute the module function
     79 /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
     80 /******/
     81 /******/         // Flag the module as loaded
     82 /******/         module.l = true;
     83 /******/
     84 /******/         // Return the exports of the module
     85 /******/         return module.exports;
     86 /******/     }
     87 /******/
     88 /******/
     89 /******/     // expose the modules object (__webpack_modules__)
     90 /******/     __webpack_require__.m = modules;
     91 /******/
     92 /******/     // expose the module cache
     93 /******/     __webpack_require__.c = installedModules;
     94 /******/
     95 /******/     // define getter function for harmony exports
     96 /******/     __webpack_require__.d = function(exports, name, getter) {
     97 /******/         if(!__webpack_require__.o(exports, name)) {
     98 /******/             Object.defineProperty(exports, name, { enumerable: true, get: getter });
     99 /******/         }
    100 /******/     };
    101 /******/
    102 /******/     // define __esModule on exports
    103 /******/     __webpack_require__.r = function(exports) {
    104 /******/         if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
    105 /******/             Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
    106 /******/         }
    107 /******/         Object.defineProperty(exports, '__esModule', { value: true });
    108 /******/     };
    109 /******/
    110 /******/     // create a fake namespace object
    111 /******/     // mode & 1: value is a module id, require it
    112 /******/     // mode & 2: merge all properties of value into the ns
    113 /******/     // mode & 4: return value when already ns object
    114 /******/     // mode & 8|1: behave like require
    115 /******/     __webpack_require__.t = function(value, mode) {
    116 /******/         if(mode & 1) value = __webpack_require__(value);
    117 /******/         if(mode & 8) return value;
    118 /******/         if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
    119 /******/         var ns = Object.create(null);
    120 /******/         __webpack_require__.r(ns);
    121 /******/         Object.defineProperty(ns, 'default', { enumerable: true, value: value });
    122 /******/         if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
    123 /******/         return ns;
    124 /******/     };
    125 /******/
    126 /******/     // getDefaultExport function for compatibility with non-harmony modules
    127 /******/     __webpack_require__.n = function(module) {
    128 /******/         var getter = module && module.__esModule ?
    129 /******/             function getDefault() { return module['default']; } :
    130 /******/             function getModuleExports() { return module; };
    131 /******/         __webpack_require__.d(getter, 'a', getter);
    132 /******/         return getter;
    133 /******/     };
    134 /******/
    135 /******/     // Object.prototype.hasOwnProperty.call
    136 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    137 /******/
    138 /******/     // __webpack_public_path__
    139 /******/     __webpack_require__.p = "./dist/";
    140 /******/
    141 /******/     var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
    142 /******/     var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
    143 /******/     jsonpArray.push = webpackJsonpCallback;
    144 /******/     jsonpArray = jsonpArray.slice();
    145 /******/     for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
    146 /******/     var parentJsonpFunction = oldJsonpFunction;
    147 /******/
    148 /******/
    149 /******/     // run deferred modules from other chunks
    150 /******/     checkDeferredModules();
    151 /******/ })
    152 /************************************************************************/
    153 /******/ ([
    154 /* 0 */,
    155 /* 1 */
    156 /***/ (function(module, exports) {
    157 
    158 console.log('chunk');
    159 
    160 
    161 /***/ })
    162 /******/ ]);

    58行直接将 chunk模块设置为已加载了,因为它现在处于common模块中,初始就是已加载

    /******/     var installedChunks = {
    /******/         1: 0
    /******/     };

    而150行上面不再出现 deferredModules的赋值,它由 ./dist/test.js 的第三个参数传入来更新

    var executeModules = data[2];
    .
    .
    .
    /******/         // add entry modules from loaded chunk to deferred list
    /******/         deferredModules.push.apply(deferredModules, executeModules || []);
    /******/
    /******/         // run deferred modules when all chunks ready
    /******/         return checkDeferredModules();

    7. 开发一个loader,加载模块

    loader会参与到模块的编译中,并输出到生成的文件里。这里用个例子来说明一下

    开发一个loader,原理很简单,其实就是传入参数,就可以自行处理了

    ./loader.js

    const loaderUtils = require('loader-utils');
    
    /**
     * 简单的loader
     * @param  {[type]} content [description]
     * @return {[type]}         [description]
     */
    module.exports = function(content) {
        // 获取loader的参数
        let options = loaderUtils.getOptions(this);
    
        console.log('loader-options', options);
        console.log(content.split(/
    |
    |
    /g));
    
        // 做一些处理,并返回即可
        this.callback(null, JSON.stringify(content.split(/
    |
    |
    /g)));
    };

    ./webpack.config.js

    ...
    module: {
            rules: [{
                test: /.css$/,
                loaders: [{
                    loader: path.resolve('./loader.js'),
                    options: {
                        css: 123
                    }
                }]
            }]
        },

    ./test.css

    .home {
        width: 100px;
        height: 200px;
    }

    ./main.js

    import './test.css';
    
    console.log('main');

    编译后

    ./dist/main.js

    (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _test_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
    /* harmony import */ var _test_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_test_css__WEBPACK_IMPORTED_MODULE_0__);
    
    
    
    console.log('main');
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, exports) {
    
    ["",".home {","     100px;","    height: 200px;","}",""]
    
    /***/ })
    ],[[0,0]]]);

    这里的模块0其实就是 ./main.js了,模块1是 test.css,可以看到 css经过loader解析之后,内容是会扔到生成的文件里面的

    [[0,0]] 是webpack初始化生成的,这里不必理会

    8. 开发一个插件plugin,加载模块

    使用一个插件,看看插件是怎么和编译过程结合起来的

    为了简便,这里就自行开发一个简单的插件

    开发插件可以类似webpack那样,基于 tapable进行开发,使用 订阅-发布 模式

    先配置一些 ./webpack.config.js

    const webpack = require('webpack');
    const path = require('path');
    
    const todayPlugin = require('./todayPlugin.js');
    
    module.exports = {
        
        ...
    
        plugins: [
            new todayPlugin({
                test: 123
            })
        ]
    };

    ./todayPlugin.js

    // 使用SyncHook
    const {SyncHook} = require('tapable');
    
    /**
     * 自定义的插件
     */
    class todayPlugin {
        constructor(options) {
            // 获取插件的参数
            this.options = options;
    
            console.log('plugin-options', this.options);
        }
    
        /**
         * 提供webpack对插件进行调用
         * @param  {[type]} compiler [description]
         * @return {[type]}          [description]
         */
        apply(compiler) {
            // 实例化,创建一个hook
            compiler.hooks.todayHook = new SyncHook(['day']);
    
            // 事件订阅,这里的day参数需要和实例化时传递的参数一致
            compiler.hooks.todayHook.tap('logToday', (day) => {
                console.log('today', day);
            });
    
            // 选择在webpack的compiler done触发时做处理
            compiler.hooks.done.tap('setToday', () => {
                // 触发我们的事件(即事件发布)
                compiler.hooks.todayHook.call(new Date);
            });
        }
    }
    
    module.exports = todayPlugin;

    编译后

    在生成的文件中,并没有看到踪迹

    当然了,也不能由此就得出结论插件不会影响到生成的文件,只是看起来如此

    编译结果就分析到这里了,说实话,非常乱 .......

    具体到底是由源码里面哪段代码控制的,就不得而知了,源码实在是庞大,目前定位到两个比较关键的文件,脑壳不疼的时候再看吧

    9. Scope Hoisting 作用域提升

    Scope Hoisting 可以让 Webpack 打包出来的代码文件更小、运行的更快,它也可以称作 “作用域提升”。

    它是由ModuleConcatenationPlugin这个插件操控的,在webpack4中内置了这个插件,我们只需要用这个属性concatenateModules开启即可(mode为production时默认开启)

    ./webpack.config.js

    optimization: {
            concatenateModules: true,
        
    }

    ./main.js

    import {n} from './number';
    
    import {str1} from './str';
    
    console.log('main', str1);

    ./number.js

    let n = 10;
    
    export {
        n
    };

    ./str.js

    export function str() {
        var ss = 'strstr';
    };
    
    export var str1 = 11;

    编译后看看 ./dist/main.js 的后面部分

    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    
    // CONCATENATED MODULE: ./number.js
    
    
    let n = 10;
    
    
    
    // CONCATENATED MODULE: ./str.js
    function str() {
        var ss = 'strstr';
    };
    
    var str1 = 11;
    
    // CONCATENATED MODULE: ./main.js
    
    
    
    
    console.log('main', str1);
    
    
    /***/ })
    /******/ ]);

    非常精简,体积减小了,运行时因为创建的函数作用域也变少了,减小了内存开销

    不过,它只能用作ES6的静态模块分析,分析出模块之间的依赖关系,尽可能地把模块放到同一个函数中。接下来使用require的方式修改以下 ./main.js

    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });
    
    
    let n = 10;
    
    
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    
    // CONCATENATED MODULE: ./str.js
    function str() {
        var ss = 'strstr';
    };
    
    var str1 = 11;
    
    // CONCATENATED MODULE: ./main.js
    
    __webpack_require__(0);
    
    
    
    console.log('main', str1);
    
    
    /***/ })
    /******/ ]);

    可以看到仅ES6的import被提升合并了,所以,在开发时尽量使用ES6的模块方式,在使用了babel编译时也要记得设置配置为 module: false ,防止代码被转换成commonjs规范的东西,导致无法提升合并

    再来看看不设置concatenateModules的时候的输出结果,对比就明显了

    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony import */ var _str__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
    
    // import {n} from './number';
    __webpack_require__(1);
    
    
    
    console.log('main', _str__WEBPACK_IMPORTED_MODULE_0__["str1"]);
    
    
    /***/ }),
    /* 1 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "n", function() { return n; });
    
    
    let n = 10;
    
    
    
    
    /***/ }),
    /* 2 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str", function() { return str; });
    /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "str1", function() { return str1; });
    function str() {
        var ss = 'strstr';
    };
    
    var str1 = 11;
    
    
    /***/ })
    /******/ ]);

    10. Tree Shaking 清除无用代码

    Tree Shaking 可以用来清除无用的代码,同样的,它在production模式下默认开启。

    它作用于ES6的模块,同时需要代码优化压缩工具的参与

    基于此,设置mode为production,编译一下

    ./main.js

    import {n} from './number';
    
    import {str1} from './str';
    
    console.log('main', str1);

    ./str.js

    export function str() {
        var ss = 'strstr';
    };
    
    export var str1 = 11;

    编译后的压缩文件(部分)

    r.p="./dist/",r(r.s=0)}([function(e,t,r){"use strict";r.r(t);console.log("main",11)}]);

    可以看到就剩下str1这个有用到的变量,其他东西都被清除

    最后,改一下webpack的配置,将压缩工具去掉

    optimization: {
            minimize: false,

    再看看生成的文件,str函数没有被清除

    /******/ ([
    /* 0 */
    /***/ (function(module, __webpack_exports__, __webpack_require__) {
    
    "use strict";
    __webpack_require__.r(__webpack_exports__);
    
    // CONCATENATED MODULE: ./str.js
    function str() {
        var ss = 'strstr';
    };
    
    var str1 = 11;
    
    // CONCATENATED MODULE: ./main.js
    
    
    
    
    
    console.log('main', str1);
    
    
    /***/ })
    /******/ ]);
  • 相关阅读:
    在unity中内置一个查询物流信息功能
    socket 广播消息
    Socket通信
    用SecureCRT在windows和CentOS间上传下载文件
    Centos6.5下设置静态IP
    oracle 11g dataguard创建的简单方法
    linux下mysql安装、目录结构、配置
    dba诊断之lock
    oracle11G在linux环境下的卸载操作
    Ubuntu 14.04(32位)安装Oracle 11g(32位)全过程
  • 原文地址:https://www.cnblogs.com/imwtr/p/9770135.html
Copyright © 2011-2022 走看看