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
 /******/ (function(modules) { // webpackBootstrap
 /******/     // The module cache
 /******/     var installedModules = {};
 /******/
 /******/     // 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,
 /******/             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;
 /******/     }
 /******/
 /******/
 /******/     // expose the modules object (__webpack_modules__)
 /******/     __webpack_require__.m = modules;
 /******/
 /******/     // expose the module cache
 /******/     __webpack_require__.c = installedModules;
 /******/
 /******/     // 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 });
 /******/     };
 /******/
 /******/     // 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;
 /******/     };
 /******/
 /******/     // Object.prototype.hasOwnProperty.call
 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/     // __webpack_public_path__
 /******/     __webpack_require__.p = "";
 /******/
 /******/
 /******/     // Load entry module and return exports
 /******/     return __webpack_require__(__webpack_require__.s = 0);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports) {
 console.log('main');
 /***/ })
 /******/ ]);
可以看到首先是一个匿名函数,在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); //
所以将 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
 /******/ (function(modules) { // webpackBootstrap
 /******/     // install a JSONP callback for chunk loading
 /******/     function webpackJsonpCallback(data) {
 /******/         var chunkIds = data[0];
 /******/         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]);
 /******/             }
 /******/             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()();
 /******/         }
 /******/
 /******/     };
 /******/
 /******/
 /******/     // The module cache
 /******/     var 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
 /******/     };
 /******/
 /******/
 /******/
 /******/     // script path function
 /******/     function jsonpScriptSrc(chunkId) {
 /******/         return __webpack_require__.p + "" + ({}[chunkId]||chunkId) + ".js"
 /******/     }
 /******/
 /******/     // 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,
 /******/             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;
 /******/     }
 /******/
 /******/     // This file contains only the entry chunk.
 /******/     // The chunk loading function for additional chunks
 /******/     __webpack_require__.e = function requireEnsure(chunkId) {
 /******/         var promises = [];
 /******/
 /******/
 /******/         // 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 {
 /******/                 // setup Promise in chunk cache
 /******/                 var promise = new Promise(function(resolve, reject) {
 /******/                     installedChunkData = installedChunks[chunkId] = [resolve, reject];
 /******/                 });
 /******/                 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.\n(' + errorType + ': ' + realSrc + ')');
 /******/                             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;
 /******/                 head.appendChild(script);
 /******/             }
 /******/         }
 /******/         return Promise.all(promises);
 /******/     };
 /******/
 /******/     // expose the modules object (__webpack_modules__)
 /******/     __webpack_require__.m = modules;
 /******/
 /******/     // expose the module cache
 /******/     __webpack_require__.c = installedModules;
 /******/
 /******/     // 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 });
 /******/     };
 /******/
 /******/     // 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;
 /******/     };
 /******/
 /******/     // Object.prototype.hasOwnProperty.call
 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/     // __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);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __webpack_require__) {
 console.log('main');
 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);
 /***/ })
 /******/ ]);
这下蹦出了许多代码,从这里开始会比较绕,需要有耐心!
按照代码执行顺序来分析,思路就清晰了
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.\n(' + 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
 /******/ (function(modules) { // webpackBootstrap
 /******/     // install a JSONP callback for chunk loading
 /******/     function webpackJsonpCallback(data) {
 /******/         var chunkIds = data[0];
 /******/         var moreModules = data[1];
 /******/         var executeModules = data[2];
 /******/
 /******/         // 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]);
 /******/             }
 /******/             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()();
 /******/         }
 /******/
 /******/         // add entry modules from loaded chunk to deferred list
 /******/         deferredModules.push.apply(deferredModules, executeModules || []);
 /******/
 /******/         // run deferred modules when all chunks ready
 /******/         return checkDeferredModules();
 /******/     };
 /******/     function checkDeferredModules() {
 /******/         var result;
 /******/         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;
 /******/             }
 /******/             if(fulfilled) {
 /******/                 deferredModules.splice(i--, 1);
 /******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
 /******/             }
 /******/         }
 /******/         return result;
 /******/     }
 /******/
 /******/     // The module cache
 /******/     var installedModules = {};
 /******/
 /******/     // object to store loaded and loading chunks
 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
 /******/     // Promise = chunk loading, 0 = chunk loaded
 /******/     var installedChunks = {
 /******/         2: 0
 /******/     };
 /******/
 /******/     var deferredModules = [];
 /******/
 /******/     // 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,
 /******/             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;
 /******/     }
 /******/
 /******/
 /******/     // expose the modules object (__webpack_modules__)
 /******/     __webpack_require__.m = modules;
 /******/
 /******/     // expose the module cache
 /******/     __webpack_require__.c = installedModules;
 /******/
 /******/     // 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 });
 /******/     };
 /******/
 /******/     // 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;
 /******/     };
 /******/
 /******/     // Object.prototype.hasOwnProperty.call
 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/     // __webpack_public_path__
 /******/     __webpack_require__.p = "./dist/";
 /******/
 /******/     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;
 /******/
 /******/
 /******/     // add entry module to deferred list
 /******/     deferredModules.push([3,1]);
 /******/     // run deferred modules when ready
 /******/     return checkDeferredModules();
 /******/ })
 /************************************************************************/
 /******/ ({
 /***/ 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');
 /***/ })
 /******/ });
先看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
 /******/ (function(modules) { // webpackBootstrap
 /******/     // install a JSONP callback for chunk loading
 /******/     function webpackJsonpCallback(data) {
 /******/         var chunkIds = data[0];
 /******/         var moreModules = data[1];
 /******/         var executeModules = data[2];
 /******/
 /******/         // 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]);
 /******/             }
 /******/             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()();
 /******/         }
 /******/
 /******/         // add entry modules from loaded chunk to deferred list
 /******/         deferredModules.push.apply(deferredModules, executeModules || []);
 /******/
 /******/         // run deferred modules when all chunks ready
 /******/         return checkDeferredModules();
 /******/     };
 /******/     function checkDeferredModules() {
 /******/         var result;
 /******/         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;
 /******/             }
 /******/             if(fulfilled) {
 /******/                 deferredModules.splice(i--, 1);
 /******/                 result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
 /******/             }
 /******/         }
 /******/         return result;
 /******/     }
 /******/
 /******/     // The module cache
 /******/     var installedModules = {};
 /******/
 /******/     // object to store loaded and loading chunks
 /******/     // undefined = chunk not loaded, null = chunk preloaded/prefetched
 /******/     // Promise = chunk loading, 0 = chunk loaded
 /******/     var installedChunks = {
 /******/         1: 0
 /******/     };
 /******/
 /******/     var deferredModules = [];
 /******/
 /******/     // 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,
 /******/             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;
 /******/     }
 /******/
 /******/
 /******/     // expose the modules object (__webpack_modules__)
 /******/     __webpack_require__.m = modules;
 /******/
 /******/     // expose the module cache
 /******/     __webpack_require__.c = installedModules;
 /******/
 /******/     // 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 });
 /******/     };
 /******/
 /******/     // 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;
 /******/     };
 /******/
 /******/     // Object.prototype.hasOwnProperty.call
 /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/     // __webpack_public_path__
 /******/     __webpack_require__.p = "./dist/";
 /******/
 /******/     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;
 /******/
 /******/
 /******/     // run deferred modules from other chunks
 /******/     checkDeferredModules();
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */,
 /* 1 */
 /***/ (function(module, exports) {
 console.log('chunk');
 /***/ })
 /******/ ]);
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(/\r\n|\r|\n/g));
    // 做一些处理,并返回即可
    this.callback(null, JSON.stringify(content.split(/\r\n|\r|\n/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 {"," width: 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); /***/ })
/******/ ]);
Webpack编译结果浅析的更多相关文章
- 关于webpack编译scss文件
		css加载器文件通常和extract-text-webpack-plugin一块使用,我们可以在源文件src目录下写scss文件,然后通过webpack编译成css文件到输出目录public,这个目录 ... 
- WebPack系列:Webpack编译的代码如何在tomcat中使用时静态资源路径不对的问题如何解决
		问题: 使用webpack+vue做前端,使用tomcat提供api,然后npm run build之后需要将编译,生成如下文件: | index.html \---appserver ... 
- webpack编译流程漫谈
		前言 weback在web构建工具的激烈竞争中逐渐脱引而出. 无论是编译速度.报错提示.可扩展性等都给前端开发者耳目一新的感觉.本篇文章是个人对webpack的一点小研究总结. webpack在开发者 ... 
- webpack模块机制浅析【一】
		webpack模块机制浅析[一] 今天看了看webpack打包后的代码,所以就去分析了下代码的运行机制. 下面这段代码是webpack打包后的最基本的形式,可以说是[骨架] (function(roo ... 
- webpack Code Splitting浅析
		Code Splitting是webpack的一个重要特性,他允许你将代码打包生成多个bundle.对多页应用来说,它是必须的,因为必须要配置多个入口生成多个bundle:对于单页应用来说,如果只打包 ... 
- webpack编译时No PostCSS Config的解决方法
		1. { loader:"postcss-loader", options: { // 如果没有options这个选项将会报错 No PostCSS Config found pl ... 
- GCC编译流程浅析
		GCC-GCC编译流程浅析 序言 对于大多数程序员而言,大家都知道gcc是什么,但是如果不接触到linux平台下的开发,鲜有人真正了解gcc的编译流程,因为windows+IDE的开发模式简直是一条龙 ... 
- webpack编译sass报错找不到module /css-loader/index.js... || 安装node-sass报错
		今天无论在家还是在公司,安装node-sass总是失败,或安装成功了,使用webpack编译sass报错,说找不到module,按照提示的错误我找了node_modules下的css-loader,发 ... 
- webpack 编译完成执行代码
		接收一个项目,由于目录结构的问题,每次编译完成后就需要去修改编译后的 HTML 文件中引用的其它文件的路径. 所以想在编译完成后使用 node 来操作文件修改路径. 然后在 webpack 官网找到了 ... 
随机推荐
- cosfuture logs
			1,RESTClient用于调试接口的插件 2, PHP_AUTH_USER如何发送 $a = base64_encode("username:password"); 注意中间是冒 ... 
- POI导入和导出Excel总结
			POI导入和导出Excel总结 POI使用总结 1.POI读取Excel 打开工作簿的方式有以下两种简单的应用,POI读取和输出工作簿文件都可以通过以下两种方式来声明: //通过输入流的方式打开本 ... 
- Unity使用代码动态给按钮赋值各个状态下的图片
			一个小知识点,怕忘记,所以记录下.废话不多说,直接上代码: 未赋值之前: 使用下面代码赋值: using UnityEngine; using UnityEngine.UI; public class ... 
- MVC+EF 多条件查询
			根据以前的做法是拼接sql语句,这会增加维护成本,因为sql语句里的内容不会报错,所以在使用ef的时候必须要抛弃拼接sql语句的习惯. 构建实例 List<vyw_user> list = ... 
- 前端 javascript
			---恢复内容开始--- JavaScript JavaScript:是一门编译型的语言,比python语言还要弱类型的语言 JavaScript 分为三个部分:Ecmascript.dom.bom ... 
- 【repost】 JS变量重复声明以及忽略var 声明的问题及其背后的原理
			JS的容错率很高,一些其他语言常见的小错误JS都能大度得包容,比如给一个方法传入超出预计的参数.在声明变量之前使用该变量(变量的声明提升解决了这个问题)等等,这里我们就要解剖一下JS变量重复声明以及当 ... 
- 【repost】图解Javascript上下文与作用域
			本文尝试阐述Javascript中的上下文与作用域背后的机制,主要涉及到执行上下文(execution context).作用域链(scope chain).闭包(closure).this等概念. ... 
- python 特别的生成器表达式
			Ⅰ起因 学习python的同学通常会遇到这样一道经典生成器测试题: def gen(): for i in range(4): yield i base = gen() for n in (2,10) ... 
- hdu  1086 You can Solve a Geometry Problem too [线段相交]
			题目:给出一些线段,判断有几个交点. 问题:如何判断两条线段是否相交? 向量叉乘(行列式计算):向量a(x1,y1),向量b(x2,y2): 首先我们要明白一个定理:向量a×向量b(×为向量叉乘),若 ... 
- Redux和React
			export app class Compo1 extends Component{ } Compo1.propType = { a:PropTypes.string, fn:PropTypes.fu ... 
