該函數主要做用就是建立一個<script>標籤,而後將chunkId對應的文件經過該標籤加載。javascript
源代碼以下:java
1 __webpack_require__.e = function requireEnsure(chunkId) {
2 var promises = []; 3 4 // JSONP chunk loading for javascript 5 6 var installedChunkData = installedChunks[chunkId]; 7 if (installedChunkData !== 0) { // 0 means "already installed". 8 9 // a Promise means "currently loading". 10 if (installedChunkData) { 11 promises.push(installedChunkData[2]); 12 } else { 13 // setup Promise in chunk cache 14 var promise = new Promise(function (resolve, reject) { 15 installedChunkData = installedChunks[chunkId] = [resolve, reject]; 16 }); 17 promises.push(installedChunkData[2] = promise); 18 19 // start chunk loading 20 var script = document.createElement('script'); 21 var onScriptComplete; 22 23 script.charset = 'utf-8'; 24 script.timeout = 120; 25 if (__webpack_require__.nc) { 26 script.setAttribute("nonce", __webpack_require__.nc); 27 } 28 script.src = jsonpScriptSrc(chunkId); 29 30 // create error before stack unwound to get useful stacktrace later 31 var error = new Error(); 32 onScriptComplete = function (event) { 33 // avoid mem leaks in IE. 34 script.onerror = script.onload = null; 35 clearTimeout(timeout); 36 var chunk = installedChunks[chunkId]; 37 if (chunk !== 0) { 38 if (chunk) { 39 var errorType = event && (event.type === 'load' ? 'missing' : event.type); 40 var realSrc = event && event.target && event.target.src; 41 error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')'; 42 error.type = errorType; 43 error.request = realSrc; 44 chunk[1](error); 45 } 46 installedChunks[chunkId] = undefined; 47 } 48 }; 49 var timeout = setTimeout(function () { 50 onScriptComplete({type: 'timeout', target: script}); 51 }, 120000); 52 script.onerror = script.onload = onScriptComplete; 53 document.head.appendChild(script); 54 } 55 } 56 return Promise.all(promises); 57 };
主要作了以下幾個事情:webpack
1)判斷chunkId對應的模塊是否已經加載了,若是已經加載了,就再也不從新加載;web
2)若是模塊沒有被加載過,但模塊處於正在被加載的過程,再也不重複加載,直接將加載模塊的promise返回。json
爲何會出現這種狀況?數組
例如:咱們將index.js中加載print.js文件的地方改造爲下邊屢次經過ES6的import加載print.js文件:promise
1 button.onclick = (
2 e => { 3 4 import('./print').then( 5 module => { 6 var print = module.default; 7 print(); 8 } 9 ); 10 11 import('./print').then( 12 module => { 13 var print = module.default; 14 print(); 15 } 16 ) 17 } 18 );
從上邊代碼能夠看出,當第一import加載print.js文件時,尚未resolve,就又執行第二個import文件了,而爲了不重複加載該文件,就經過將這裏的判斷,避免了重複加載。緩存
3)若是模塊沒有被加載過,也不處於加載過程,就建立一個promise,並將resolve、reject、promise構成的數組存儲在上邊說過的installedChunks緩存對象屬性中。而後建立一個script標籤加載對應的文件,加載超時時間是2分鐘。若是script文件加載失敗,觸發reject(對應源碼中:chunk[1](error),chunk[1]就是上邊緩存的數組的第二個元素reject),並將installedChunks緩存對象中對應key的值設置爲undefined,標識其沒有被加載。app
4)最後返回promise異步
注意:源碼中,這裏返回的是Promise.all(promises),分析代碼發現promises好像只可能有一個元素。可能還沒遇到多個promises的場景吧。留待後續研究。
整個app.bundle.js文件是一個自執行函數,該函數中執行的代碼以下:
1 var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 2 var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); // 複製一個數組的push方法,這個方法的this是jsonpArray 3 jsonpArray.push = webpackJsonpCallback; // TODO: 爲何要複寫push,而不是直接增長一個新方法名? 4 jsonpArray = jsonpArray.slice(); // 拷貝一個新數組 5 for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 6 var parentJsonpFunction = oldJsonpFunction;
這段代碼主要作了以下幾個事情:
1)定義了一個全局變量webpackJsonp,改變量是一個數組,該數組變量的原生push方法被複寫爲webpackJsonpCallback方法,該方法是懶加載實現的一個核心方法,具體代碼會在下邊分析。
該全局變量在懶加載文件中被用到。在print.bundle.js中:
1 (window["webpackJsonp"] = window["webpackJsonp"] || []).push([ // 注意:這個push實際是webpackJsonpCallback方法 2 ["print"], 3 { 4 "./src/print.js": (function(module, __webpack_exports__, __webpack_require__) {...}) 5 } 6 ]);
2)將數組的原生push方法備份,賦值給parentJsonpFunction變量保存。
注意:該方法的this是全局變量webpackJsonp,也就是說parentJsonpFunction('111')後,全局數組變量webpackJsonp就增長了一個'111'元素。
該方法在webpackJsonpCallback中會用到,是將懶加載文件的內容保存到全局變量webpackJsonp中。
3)上邊第一步中複寫push的緣由?
多是由於在懶加載文件中,調用了複寫後的push,執行了原生push的功能,所以,爲了更形象的表達該意思,所以直接複寫了push。
但我的認爲這個不太好,不易讀。直接新增一個_push或者extendPush,這樣是否是讀起來就很簡單了。
該函數是懶加載的一個比較核心代碼。其代碼以下:
1 function webpackJsonpCallback(data) { 2 var chunkIds = data[0]; 3 var moreModules = data[1]; 4 5 // add "moreModules" to the modules object, 6 // then flag all "chunkIds" as loaded and fire callback 7 var moduleId, chunkId, i = 0, resolves = []; 8 for (; i < chunkIds.length; i++) { 9 chunkId = chunkIds[i]; 10 if (installedChunks[chunkId]) { 11 resolves.push(installedChunks[chunkId][0]); 12 } 13 installedChunks[chunkId] = 0; 14 } 15 for (moduleId in moreModules) { 16 if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { 17 modules[moduleId] = moreModules[moduleId]; 18 } 19 } 20 if (parentJsonpFunction) parentJsonpFunction(data); 21 22 while (resolves.length) { 23 resolves.shift()(); 24 } 25 };
參數說明:
參數是一個數組。有兩個元素:第一個元素是要懶加載文件中全部模塊的chunkId組成的數組;第二個參數是一個對象,對象的屬性和值分別是要加載模塊的moduleId和模塊代碼函數。
該函數主要作的事情以下:
1)遍歷參數中的chunkId:
判斷installedChunks緩存變量中對應chunkId的屬性值:若是是真,說明模塊正在加載,由於從上邊分析中能夠知道,installedChunks[chunkId]只有一種狀況是真,那就是在對應的模塊正在加載時,會將加載模塊建立的promise的三個信息搞成一個數組[resolve, reject, proimise]賦值給installedChunks[chunkId]。將resolve存入resolves變量中。
將installedChunks中對應的chunkId置爲0,標識該模塊已經被加載過了。
2)遍歷參數中模塊對象全部屬性:
將模塊代碼函數存儲到modules中,該modules是入口文件app.bundle.js中自執行函數的參數。
這一步很是關鍵,由於執行模塊加載函數__webpack_require__時,獲取模塊代碼時,就是經過moduleId從modules中查找對應模塊代碼。
3)調用parentJsonpFunction(原生push方法)將整個懶加載文件的數據存入全局數組變量window.webpackJsonp。
4)遍歷resolves,執行全部promise的resolve:
當執行了promise的resolve後,纔會走到promise.then的成功回調中,查看源碼能夠看到:
1 button.onclick = ( 2 e => { 3 __webpack_require__.e("print") 4 .then(__webpack_require__.bind(null, "./src/print.js")) 5 .then( 6 module => { 7 var print = module.default; 8 print(); 9 } 10 ) 11 } 12 );
resolve後,執行了兩個then回調:
第一個回調是調用__webpack_require__函數,傳入的參數是懶加載文件中的一個模塊的moduleId,而這個moduleId就是上邊存入到modules變量其中一個。這樣就經過__webpack_require__執行了模塊的代碼。並將模塊的返回值,傳遞給第二個then的回調函數;
第二個回調函數是真正的onclick回調函數的業務代碼。
5)重要思考:
從這個函數能夠看出:
調用__webpack_require__.e('print')方法,實際只是將對應的print.bundle.js文件加載和建立了一個異步的promise(由於並不知道何時這個文件才能執行完,所以須要一個異步promise,而promise的resolve會在對應的文件加載時執行,這樣就能實現異步文件加載了),並無將懶加載文件中保存的模塊代碼執行。
在加載對應print.bundle.js文件代碼時,經過調用webpackJsonpCallback函數,實現觸發加載文件時建立的promise的resolve。
resolve觸發後,會執行promise的then回調,這個回調經過__webpack_require__函數執行了真正須要模塊的代碼(注意:若是print.bundle.js中有不少模塊,只會執行用到的模塊代碼,而不是執行全部模塊的代碼),執行完後將模塊的exports返回給promise的下一個then函數,該函數也就是真正的業務代碼了。
綜上,能夠看出,webpack實際是經過promise,巧妙的實現了模塊的懶加載功能。