如今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.\n(' + 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.\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
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(/\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); /***/ }) /******/ ]);