webpack
提供的一個很是強大的功能就是code spliting(代碼切割)
。javascript
在webpack 1.x
中提供了java
require.ensure([], () => { let module = require('./page1/module'); // do something }, 'module1')
利用require.ensure
這個API
使得webpack
單獨將這個文件打包成一個能夠異步加載的chunk
.webpack
具體的套路見我寫的另外一篇blog: webpack分包及異步加載套路git
一句話總結就是:github
在輸出的runtime
代碼中,包含了異步chunk
的id
及chunk name
的映射關係。須要異步加載相應的chunk
時,經過生成script
標籤,而後插入到DOM
中完成chunk
的加載。經過JSONP
,runtime
中定義好函數,chunk
加載完成後即會當即執行這個函數。web
從編譯生成後的代碼來看,webpack 1.x
從chunk
的加載到執行的過程處理的比較粗糙,僅僅是經過添加script
標籤,異步加載chunk
後,完成函數的執行。bootstrap
這個過程中,若是出現了chunk
加載不成功時,這種狀況下應該如何去容錯呢?segmentfault
在webpack2
中相比於webpack1.x
在這個點的處理上是將chunk
的加載包裹在了promise
當中,那麼這個過程變的可控起來。具體的webpack2
實現套路也是本文想要去說明的地方。數組
webpack
提供的異步加載函數是promise
/******/ // This file contains only the entry chunk. /******/ // The chunk loading function for additional chunks // runtime代碼裏面只包含了入口的chunk // 這個函數的主要做用: // 1. 異步加載chunk // 2. 提供對於chunk加載失敗或者處於加載中的處理 // 其中chunk加載狀態的判斷是根據installedChunks對象chunkId是數字0仍是數組來進行判斷的 /******/ __webpack_require__.e = function requireEnsure(chunkId) { // 數字0表明chunk加載成功 /******/ if(installedChunks[chunkId] === 0) /******/ return Promise.resolve(); /******/ // an Promise means "currently loading". // 若是installedChunks[chunkId]爲一個數組 /******/ if(installedChunks[chunkId]) { // 返回一個promise對象 /******/ return installedChunks[chunkId][2]; /******/ } /******/ // start chunk loading // 經過生成script標籤來異步加載chunk.文件名是根據接受的chunkId來確認的 /******/ var head = document.getElementsByTagName('head')[0]; /******/ var script = document.createElement('script'); /******/ script.type = 'text/javascript'; /******/ script.charset = 'utf-8'; /******/ script.async = true; // 超時時間爲120s /******/ script.timeout = 120000; /******/ if (__webpack_require__.nc) { /******/ script.setAttribute("nonce", __webpack_require__.nc); /******/ } // 須要加載的文件名 /******/ script.src = __webpack_require__.p + "js/register/" + ({"2":"index"}[chunkId]||chunkId) + ".js"; // 120s的定時器,超時後觸發onScriptComplete回調 /******/ var timeout = setTimeout(onScriptComplete, 120000); // chunk加載完畢後的回調 /******/ script.onerror = script.onload = onScriptComplete; /******/ function onScriptComplete() { /******/ // avoid mem leaks in IE. /******/ script.onerror = script.onload = null; // 清空定時器 /******/ clearTimeout(timeout); // 獲取這個chunk的加載狀態 // 若爲數字0,表示加載成功 // 若爲一個數組, 調用數組的第2個元素(第二個元素爲promise內傳入的reject函數),使得promise捕獲拋出的錯誤。reject(new Error('xxx')) /******/ var chunk = installedChunks[chunkId]; /******/ if(chunk !== 0) { /******/ if(chunk) chunk[1](new Error('Loading chunk ' + chunkId + ' failed.')); /******/ installedChunks[chunkId] = undefined; /******/ } /******/ }; // 每次須要進行異步加載chunk時,會將這個chunk的加載狀態進行初始化爲一個數組,並以key/value的形式保存在installedChunks裏 // 這個數組爲[resolve, reject, promise]; /******/ var promise = new Promise(function(resolve, reject) { /******/ installedChunks[chunkId] = [resolve, reject]; /******/ }); /******/ installedChunks[chunkId][2] = promise; /******/ head.appendChild(script); //返回promise /******/ return promise; /******/ };
咱們再來看看路由配置文件編譯後生成的代碼index.js
, 特別注意下__webpack_require__.e
這個異步加載函數:
Router .home('path1') .addRoute({ path: 'path1', animate: 'zoomIn', viewBox: '.public-path1-container', template: __webpack_require__(5), // 掛載controller pageInit: function pageInit() { var _this = this; console.time('route async path1'); // 異步加載0.js(這個文件是webpack經過code spliting本身生成的文件名) // 具體異步加載代碼的封裝見?分析 // 其中0.js包含了包含了path1這個路由下的業務代碼 // __webpack_require__.e(0) 起的做用僅爲加載chunk以及提供對於chunk加載失敗錯誤的拋出 // 具體的業務代碼的觸發是經過__webpack_require_e(0).then(__webpack_require__.bind(null, 8)).then(function(module) { ... })進行觸發 // __webpack_require__.bind(null, 8) 返回的是module[8]暴露出來的module // 這段代碼執行時,首先初始化一個module對象 // module = { // i: moduleId, // 模塊id // l: false, // 加載狀態 // exports: {} // 須要暴露的對象 // } // 經過異步加載的chunk最後暴露出來的對象是做爲了module.exports.default屬性 // 所以在第二個方法中傳入的對象的default屬性纔是你模塊8真正所暴露的對象 __webpack_require__.e/* import() */(0).then(__webpack_require__.bind(null, 8)).then(function (module) { var controller = module.default; Router.registerCtrl('path1', new controller(_this.viewBox)); // 添加錯誤處理函數,用以捕獲前面可能拋出的錯誤 }).catch(function (e) { return console.log('chunk loading failed'); }); }, // 進入路由跳轉以前 beforeEnter: function beforeEnter() {}, // 路由跳轉前 beforeLeave: function beforeLeave() {} }) .addRoute({ path: 'path2', viewBox: '.public-path2-container', animate: 'zoomIn', template: __webpack_require__(6), pageInit: function pageInit() { var _this2 = this; __webpack_require__.e/* import() */(1).then(__webpack_require__.bind(null, 9)).then(function (module) { console.time('route async path2'); var controller = module.default; Router.registerCtrl('path2', new controller(_this2.viewBox)); }).catch(function (e) { return console.log('chunk loading failed'); }); }, beforeEnter: function beforeEnter() {}, beforeLeave: function beforeLeave() {} }); Router.bootstrap();
總結一下就是:
webpack2
相比於webpack1.x
將異步加載chunk
的過程封裝在了promise
當中,若是chunk
加載超時或者失敗會拋出錯誤,這時咱們能夠針對拋出的錯誤作相應的錯誤處理。
此外還應該注意下,webpack2
異步加載chunk
是基於原生的promise
。若是部分環境暫時還不支持原生promise
時須要提供polyfill
。另外就是require.ensure
能夠接受第三個參數用以給chunk
命名,可是import
這個API
沒有提供這個方法
更多的細節你們能夠運行demo看下編譯後的代碼