浅读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,至此完成流程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容