webpack2異步加載套路

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代碼中,包含了異步chunkidchunk name的映射關係。須要異步加載相應的chunk時,經過生成script標籤,而後插入到DOM中完成chunk的加載。經過JSONP,runtime中定義好函數,chunk加載完成後即會當即執行這個函數。web

從編譯生成後的代碼來看,webpack 1.xchunk的加載到執行的過程處理的比較粗糙,僅僅是經過添加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看下編譯後的代碼

相關文章
相關標籤/搜索