探究 webpack 如何實現模塊異步加載

探究 webpack 如何實現模塊異步加載

之前一篇文章講了 webpack 如何實現模塊的同步加載,在此基礎上,今天接着講如何實現異步加載模塊。

一、源碼

還是用原來的例子,但是主文件中改成按需加載(異步加載)

原來:import func from './func.js'
現在:const func = () => import('./func.js')

打包後源碼(main.js 文件):

(function (modules) {
	function webpackJsonpCallback(data) {}

	var installedModules = {};
	var installedChunks = {
		"main": 0
	};

	function __webpack_require__(moduleId) {}

	__webpack_require__.e = function requireEnsure(chunkId) {}

	return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({		//主文件
 "./src/index.js":
	(function (module, __webpack_exports__, __webpack_require__) {
		const func = () => __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/func.js"))
		func.add(1,2)
	})
});

打包後源碼(0.main.js 文件):

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
	"./src/func.js":
		(function(module, __webpack_exports__, __webpack_require__) {
			__webpack_require__.r(__webpack_exports__);
			class func{
			    static add(val1,val2){
			        return val1 + val2
			    }
			}
			__webpack_exports__["default"] = (func);
		})
	}
]);

和之前同步引入打包的文件對比,我們可以發現 main.js 打包內容多了一些函數,同時也分割出一個 0.main.js 文件(裏面的內容就是引入文件內容)。

我們已經知道如何同步加載模塊了(__webpack_require__函數),那現在疑問就有兩點,如何把異步文件加載到當前頁面?如何把異步文件內的模塊插入到主文件模塊對象內?

二、分析

我們現在從主文件源碼觀察

const func = () => __webpack_require__.e(0).then(__webpack_require__.bind(null, "./src/func.js"))

使用了 _webpack_require_.e 函數返回了 Promsie 對象,如果成功則加載引用文件。我們看看 main.js 中的 _webpack_require_.e 做了什麼

(1)異步文件加載到當前頁面

//moduleId 打包出的模塊id
//chunkId 分離出的js文件id
__webpack_require__.e = function requireEnsure(chunkId) {
   	var promises = [];  

    // 用於 javascript 加載 JSONP 的模塊 
    var installedChunkData = installedChunks[chunkId];
    if (installedChunkData !== 0) { // 0 標誌已緩存
        // 1.查找緩存
        if (installedChunkData) {
            promises.push(installedChunkData[2]);
        } else {
        // 2.模塊緩存
            var promise = new Promise(function (resolve, reject) {
                installedChunkData = installedChunks[chunkId] = [resolve, reject];
            });
            promises.push(installedChunkData[2] = promise);

            // 引入script,加載模塊
            var script = document.createElement('script');
            script.src = __webpack_require__.p + "" + chunkId + ".main.js"; //0.main.js

            //onScriptComplete 超時、錯誤處理
            var timeout = setTimeout(function () {
                onScriptComplete({ type: 'timeout', target: script });
            }, 120000);	
            script.onerror = onScriptComplete

            document.head.appendChild(script);
        }
    }
    //加載
    return Promise.all(promises);
};

_webpack_require_.e 這個函數主要做了兩件事,

  • 創建 script 標籤引入 0.main.js 文件

  • 創建一個 Promise 對象,如果 script 引入文件 onload 則返回成功(接下去就是走上面的加載 func.js 文件流程),onerror 或者超時則進行失敗處理。

(2)異步文件的模塊插入到主文件模塊對象

我們在看看異步引用文件 0.main.js

與同步加載相比,多了 window[“webpackJsonp”]

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
	...
	...
}]);

我們再看看主文件中與之相關的代碼

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) 	webpackJsonpCallback(jsonpArray[i]);

在這裏插入圖片描述

我們可以看到 window[“webpackJsonp”] 是一個對象, window[“webpackJsonp”].push 指向 webpackJsonpCallback 函數。異步文件通過這個指針,把內容傳進 webpackJsonpCallback 函數內。

webpackJsonpCallback函數 (因爲類似於jsonp的callback函數,故有此名):

(function (modules) {
	function webpackJsonpCallback(data) {
	    var chunkIds = data[0];
	    var moreModules = data[1];
	
	    var moduleId, chunkId, i = 0, resolves = [];
	    //收集模塊,將所有“chunkIds”標記爲已加載
	    for (; i < chunkIds.length; i++) {
	        chunkId = chunkIds[i];
	        if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
	            resolves.push(installedChunks[chunkId][0]);
	        }
	        installedChunks[chunkId] = 0;
	    }
		//異步文件的模塊內容插入到主文件模塊對象 modules 中
	    for (moduleId in moreModules) {
	        if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
	            modules[moduleId] = moreModules[moduleId];
	        }
	    }
	    //parentJsonpFunction指針對象指向webpackJsonpCallback
	    if (parentJsonpFunction) 
			parentJsonpFunction(data);
		//執行並觸發回調
	    while (resolves.length) {
	        resolves.shift()();
	    }
	};
	......
}

webpackJsonpCallback 首先收集文件內模塊,將所有“chunkIds”標記爲已加載,把該模塊插入主頁面模塊內,然後加載模塊。

主文件模塊就是通過 installedChunks 內的 chunkId 標記判斷異步文件模塊的加載情況從而返回 Promise 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章