浅读webpack—— 异步加载模块

webpack提供了动态加载模块的接口import(),require.ensure()。
照例准备2个JS,a.js,b.js
a.js

function c(){
    import('./b').then(func=>{ //动态加载模块b
        func()
    })
}
export default c

b.js

export default function(){
    console.log('hello world')
}

webpack编译,生成了2份文件,main.js和1.js。
先看下main.js,依旧是那个自执行函数

(function(modules){
})([
 (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
function c(){
    __webpack_require__.e(/* import() */ 1).then(__webpack_require__.bind(null, 1)).then(func=>{
        func()
    })
}
__webpack_exports__["default"] = (c);
})
])

数组里只有a.js的内容,import函数被转化成了_webpack_require_.e

// 存储已加载或正在加载中的chunk
// undefined 表示chunk还未加载, null = chunk preloaded/prefetched
// Promise表示加载中,0表示已经加载
var installedChunks = {
  0: 0
};
__webpack_require__.e = function requireEnsure(chunkId) {
    var promises = [];
    var installedChunkData = installedChunks[chunkId];
    if(installedChunkData !== 0) { // 0 表示已经被加载
        // Promise表示加载中
        if(installedChunkData) {
            promises.push(installedChunkData[2]); //installedChunkData[2]是Promise
        } else {
            // 在缓存中设置Promise的resolve和reject
            var promise = new Promise(function(resolve, reject) {
                installedChunkData = installedChunks[chunkId] = [resolve, reject];
            });
            promises.push(installedChunkData[2] = promise); //promise赋值到installedChunkData[2]
            // 开始加载JS
            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);
            // create error before stack unwound to get useful stacktrace later
            var error = new Error();
            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;
                        error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
                        error.name = 'ChunkLoadError';
                        error.type = errorType;
                        error.request = realSrc;
                        chunk[1](error);  //chunk[1]是Promise的reject
                    }
                    installedChunks[chunkId] = undefined; //设置状态为未加载
                }
            };
            var timeout = setTimeout(function(){ //超时定时器
                onScriptComplete({ type: 'timeout', target: script });
            }, 120000);
            script.onerror = script.onload = onScriptComplete;
            document.head.appendChild(script);
        }
    }
    return Promise.all(promises);
 };

这里肯定有疑惑,onScriptComplete怎么都是失败的处理,那成功的处理在哪,什么时候会把installedChunks 里的状态变为0——已加载。
回过头看下b.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
 __webpack_exports__["default"] = (function(){
    console.log('hello world')
});
 })
]]);

执行了window["webpackJsonp"]的push方法。
在main.js中

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;

其实就是加载JS成功时执行了webpackJsonpCallback。

function webpackJsonpCallback(data) {
    var chunkIds = data[0];  //获取chunkID,b.js是1
    var moreModules = data[1];
    var moduleId, chunkId, i = 0, resolves = [];
    for(;i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
            resolves.push(installedChunks[chunkId][0]);  //讲promise的resolve存到数组中
        }
        installedChunks[chunkId] = 0; //将状态变为已加载
    }
    for(moduleId in moreModules) {
        if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
            modules[moduleId] = moreModules[moduleId]; //加载后的模块存到modules中
        }
    }
    if(parentJsonpFunction) parentJsonpFunction(data);
    while(resolves.length) {
        resolves.shift()();  //Promise resolve。即import('./b')回调触发
    }
 };

回顾一下整体流程
1、_webpack_require(0) 加载入口
2、_webpack_require
.e('b.js')。动态加载b.js(1.js)。
3、从installedChunks中查找是否已经有该模块,如果已经加载过,直接返回Promise。如果没有,installedChunks[id]设置为一个数组[resolve,reject,Promise],添加script标签来加载JS。
4、超时或者加载失败则会将installedChunks[id]置回undefined。
5、加载JS成功会执行webpackJsonpCallback。installedChunks[id]设为0表示加载成功,同时保存在modules中,触发之前保存在installedChunks[id]的resolve,至此完成流程。

你可能感兴趣的:(浅读webpack—— 异步加载模块)