webpack是如何實現(xiàn)模塊化加載的方法
webpack支持的模塊規(guī)范有 AMD 、CommonJS、ES2015 import 等規(guī)范。不管何種規(guī)范大致可以分為同步加載和異步加載兩種情況。本文將介紹webpack是如何實現(xiàn)模塊管理和加載。
同步加載如下:
import a from './a'; console.log(a);
異步加載如下:
import('./a').then(a => console.log(a));
webpacks實現(xiàn)的啟動函數(shù),直接將入口程序module傳入啟動函數(shù)內(nèi),并緩存在閉包內(nèi),如下:
(function(modules){
......
// 加載入口模塊并導出(實現(xiàn)啟動程序)
return __webpack_require__(__webpack_require__.s = 0);
})({
0: (function(module, __webpack_exports__, __webpack_require__) {
module.exports = __webpack_require__(/*! ./src/app.js */"./src/app.js");
})
})
webpack在實現(xiàn)模塊管理上不管服務端還是客戶端大致是一樣,主要由installedChunks記錄已經(jīng)加載的chunk,installedModules記錄已經(jīng)執(zhí)行過的模塊,具體如下:
/**
* module 緩存器
* key 為 moduleId (一般為文件路徑)
* value 為 module 對象 {i: moduleId, l: false, exports: {}}
*/
var installedModules = {};
/**
* chunks加載狀態(tài)記錄器
* key 一般為 chunk 索引
* value undefined:未加載 0:已經(jīng)加載 (客戶端特有 null: 準備加載 [resolve, reject]: 加載中)
*/
var installedChunks = {
"app": 0
}
不管是服務端還是客戶端同步加載的方法都一樣,主要是檢測installedModules中是否已經(jīng)緩存有要加載的module,有則直接返回,否則就創(chuàng)建一個新的module,并執(zhí)行返回module.exports,具體實現(xiàn)如下:
// 編譯后的同步加載
__webpack_require__(/*! ./src/app.js */"./src/app.js");
// 加載模塊的方法,即require方法
function __webpack_require__(moduleId) {
// 檢查當前的module是否已經(jīng)存在緩存中
if(installedModules[moduleId]) {
return installedModules[moduleId].exports; // 直接返回已緩存的 module.exports
}
// 創(chuàng)建一個新的 module, 并添加到緩存中
var module = installedModules[moduleId] = {
i: moduleId,
l: false, // 是否已經(jīng)加載
exports: {} // 暴露的對象
};
// 執(zhí)行當前 module 的方法
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 標記 module 加載完成狀態(tài)
module.l = true;
// 返回 module 暴露的 exports 對象
return module.exports;
}
服務端的異步加載是通過node的require方法加載chunk并返回一個promises對象。所有chunk都是暴露出ids和modules對象,具體實現(xiàn)如下:
// 編譯后的異步加載方法
__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./c.js */ "./src/c.js"))
// chunk 0 代碼如下(即0.js的代碼)
exports.ids = [0];
exports.modules = {
"./src/c.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (function () {
console.log('c');
})
})
}
// 異步加載模塊方法
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
if(installedChunks[chunkId] !== 0) {
var chunk = require("./" + ({}[chunkId]||chunkId) + ".js");
var moreModules = chunk.modules, chunkIds = chunk.ids;
for(var moduleId in moreModules) {
modules[moduleId] = moreModules[moduleId];
}
for(var i = 0; i < chunkIds.length; i++)
installedChunks[chunkIds[i]] = 0;
}
return Promise.all(promises);
}
客戶端的異步加載是通過JSONP原理進行加載資源,將chunk內(nèi)容([chunkIds, modules])存到全局的webpackJsonp數(shù)組中,并改造webpackJsonp的push方法實現(xiàn)監(jiān)聽chunk加載完成事件。具體實現(xiàn)如下:
// 編譯后的異步加載方法
__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./c.js */ "./src/c.js"))
// chunk 0 代碼如下(即0.js的代碼)
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
"./src/c.js": (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_exports__["default"] = (function () {
console.log('c');
});
})
}]);
// 加載成功的回調函數(shù)
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
// 將本次加載回來的 chunk 標記為加載完成狀態(tài),并執(zhí)行回調
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]); // 將chunk成功回調添加到要執(zhí)行的隊列中
}
installedChunks[chunkId] = 0; // 將chunk標記為加載完成
}
// 將本次加載回來的 module 添加到全局的 modules 對象
for(moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
// 判斷 webpackJsonp 數(shù)組原始的push方法是否存在,存在則將數(shù)據(jù)追加到webpackJsonp中
if(parentJsonpFunction) parentJsonpFunction(data);
// 執(zhí)行所有 chunk 回調
while(resolves.length) {
resolves.shift()();
}
};
// 加載完成監(jiān)聽方法的實現(xiàn)
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
// 異步加載模塊方法
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 時表示已經(jīng)安裝完成
if(installedChunkData) { // 加載中
promises.push(installedChunkData[2]);
} else {
// 創(chuàng)建一個回調的Promise,并將Promise緩存到installedChunks中
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
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);
var error = new Error();
onScriptComplete = function (event) { // 加載完成回調
// 避免IE內(nèi)存泄漏。
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);
}
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);
};
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
用canvas 實現(xiàn)個圖片三角化(LOW POLY)效果
這篇文章主要介紹了用canvas 實現(xiàn)個圖片三角化(LOW POLY)效果 的相關資料,需要的朋友可以參考下2016-02-02
JavaScript不使用prototype和new實現(xiàn)繼承機制
這篇文章主要介紹了JavaScript不使用prototype和new實現(xiàn)繼承機制的相關資料,需要的朋友可以參考下2014-12-12
javascript Slip.js實現(xiàn)整屏滑動的手機網(wǎng)頁
Slip.js能做什么?Slip.js可以讓你的手機網(wǎng)站像原生手機軟件一樣慣性滾動,手觸圖片輪換等等,對Slip.js感興趣的小伙伴們可以參考一下2015-11-11

