您好,登錄后才能下訂單哦!
這篇文章主要介紹“Webpack動態import原理及源碼分析”,在日常操作中,相信很多人在Webpack動態import原理及源碼分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Webpack動態import原理及源碼分析”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
我們先來看看下面的 demo
function component() { const btn = document.createElement("button"); btn.onclick = () => { import("./a.js").then((res) => { console.log("動態加載a.js..", res); }); }; btn.innerHTML = "Button"; return btn; } document.body.appendChild(component());
點擊按鈕,動態加載 a.js
腳本,查看瀏覽器網絡請求可以發現,a.js
請求返回的內容如下:
簡單看,實際上返回的就是下面這個東西:
(self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([ ["src_a_js"], { "./src/a.js": () => {}, }, ]);
從上面可以看出 3 點信息:
1.webpackChunkwebpack_demo 是掛到全局 window 對象上的屬性
2.webpackChunkwebpack_demo 是個數組
3.webpackChunkwebpack_demo 有個 push 方法,用于添加動態的模塊。當a.js
腳本請求成功后,這個方法會自動執行。
再來看看 main.js 返回的內容
仔細觀察,動態 import 經過 webpack 編譯后,變成了下面的一坨東西:
__webpack_require__.e("src_a_js") .then(__webpack_require__.bind(__webpack_require__, "./src/a.js")) .then((res) => { console.log("動態加載a.js..", res); });
上面代碼中,__webpack_require__
用于執行模塊,比如上面我們通過webpackChunkwebpack_demo.push
添加的模塊,里面的./src/a.js
函數就是在__webpack_require__
里面執行的。
__webpack_require__.e
函數就是用來動態加載遠程腳本。因此,從上面的代碼中我們可以看出:
首先 webpack 將動態 import 編譯成 __webpack_require__.e
函數
__webpack_require__.e
函數加載遠程的腳本,加載完成后調用 __webpack_require__
函數
__webpack_require__
函數負責調用遠程腳本返回來的模塊,獲取腳本里面導出的對象并返回
在開始之前,我們先來看下如何使用 script 標簽加載遠程模塊
var inProgress = {}; // url: "http://localhost:8080/src_a_js.main.js" // done: 加載完成的回調 const loadScript = (url, done) => { if (inProgress[url]) { inProgress[url].push(done); return; } const script = document.createElement("script"); script.charset = "utf-8"; script.src = url; inProgress[url] = [done]; var onScriptComplete = (prev, event) => { var doneFns = inProgress[url]; delete inProgress[url]; script.parentNode && script.parentNode.removeChild(script); doneFns && doneFns.forEach((fn) => fn(event)); if (prev) return prev(event); }; script.onload = onScriptComplete.bind(null, script.onload); document.head.appendChild(script); };
loadScript(url, done)
函數比較簡單,就是通過創建 script 標簽加載遠程腳本,加載完成后執行 done 回調。inProgress
用于避免多次創建 script 標簽。比如我們多次調用loadScript('http://localhost:8080/src_a_js.main.js', done)
時,應該只創建一次 script 標簽,不需要每次都創建。這也是為什么我們調用多次 import('a.js')
,瀏覽器 network 請求只看到家在一次腳本的原因
實際上,這就是 webpack 用于加載遠程模塊的極簡版本。
首先我們使用installedChunks
對象保存動態加載的模塊。key 是 chunkId
// 存儲已經加載和正在加載的chunks,此對象存儲的是動態import的chunk,對象的key是chunkId,值為 // 以下幾種: // undefined: chunk not loaded // null: chunk preloaded/prefetched // [resolve, reject, Promise]: chunk loading // 0: chunk loaded var installedChunks = { main: 0, };
由于 import()
返回的是一個 promise,然后import()
經過 webpack 編譯后就是一個__webpack_require__.e
函數,因此可以得出__webpack_require__.e
返回的也是一個 promise,如下所示:
const scriptUrl = document.currentScript.src .replace(/#.*$/, "") .replace(/\?.*$/, "") .replace(/\/[^\/]+$/, "/"); __webpack_require__.e = (chunkId) => { return Promise.resolve(ensureChunk(chunkId, promises)); }; const ensureChunk = (chunkId) => { var installedChunkData = installedChunks[chunkId]; if (installedChunkData === 0) return; let promise; // 1.如果多次調用了__webpack_require__.e函數,即多次調用import('a.js')加載相同的模塊,只要第一次的加載還沒完成,就直接使用第一次的Promise if (installedChunkData) { promise = installedChunkData[2]; } else { promise = new Promise((resolve, reject) => { // 2.注意,此時的resolve,reject還沒執行 installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); installedChunkData[2] = promise; //3. 此時的installedChunkData 為[resolve, reject, promise] var url = scriptUrl + chunkId; var error = new Error(); // 4.在script標簽加載完成或者加載失敗后執行loadingEnded方法 var loadingEnded = (event) => { if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId)) { installedChunkData = installedChunks[chunkId]; if (installedChunkData !== 0) installedChunks[chunkId] = undefined; if (installedChunkData) { console.log("加載失敗....."); installedChunkData[1](error); // 5.執行上面的reject,那resolve在哪里執行呢? } } }; loadScript(url, loadingEnded, "chunk-" + chunkId, chunkId); } return promise; };
__webpack_require__.e
的主要邏輯在ensureChunk
方法中,注意該方法里面的第 1 到第 5 個注釋。這個方法創建一個 promise,并調用loadScript
方法加載動態模塊。需要特別主要的是,返回的 promise 的 resolve 方法并不是在 script 標簽加載完成后改變。如果腳本加載錯誤或者超時,會在 loadingEnded 方法里調用 promise 的 reject 方法。
實際上,promise 的 resolve 方法是在腳本請求完成后,在 self["webpackChunkwebpack_demo"].push()執行的時候調用的
遠程模塊是通過self["webpackChunkwebpack_demo"].push()
函數執行的
前面我們提到,a.js
請求返回的內容是一個self["webpackChunkwebpack_demo"].push()
函數。當請求完成,會自動執行這個函數。實際上,這就是一個 jsonp 的回調方式。該方法的實現如下:
var webpackJsonpCallback = (data) => { var [chunkIds, moreModules] = data; var moduleId, chunkId, i = 0; for (moduleId in moreModules) { // 1.__webpack_require__.m存儲的是所有的模塊,包括靜態模塊和動態模塊 __webpack_require__.m[moduleId] = moreModules[moduleId]; } for (; i < chunkIds.length; i++) { chunkId = chunkIds[i]; if (installedChunks[chunkId]) { // 2.調用ensureChunk方法生成的promise的resolve回調 installedChunks[chunkId][0](); } // 3.將該模塊標記為0,表示已經加載過 installedChunks[chunkId] = 0; } }; self["webpackChunkwebpack_demo"] = []; self["webpackChunkwebpack_demo"].push = webpackJsonpCallback.bind(null);
所有通過import()
加載的模塊,經過 webpack 編譯后,都會被 self["webpackChunkwebpack_demo"].push()
包裹。
在 webpack 構建編譯階段,import()
會被編譯成類似__webpack_require__.e("src_a_js").then(__webpack_require__.bind(__webpack_require__, "./src/a.js"))
的調用方式
__webpack_require__ .e("src_a_js") .then(__webpack_require__.bind(__webpack_require__, "./src/a.js")) .then((res) => { console.log("動態加載a.js..", res); });
__webpack_require__.e()
方法會創建一個 script 標簽用于請求腳本,方法執行完返回一個 promise,此時的 promise 狀態還沒改變。
script 標簽被添加到 document.head 后,觸發瀏覽器網絡請求。請求成功后,動態的腳本會自動執行,此時self["webpackChunkwebpack_demo"].push()
方法執行,將動態的模塊添加到__webpack_require__.m
屬性中。同時調用 promise 的 resolve 方法改變狀態,模塊加載完成。
腳本執行完成后,最后執行 script 標簽的 onload 回調。onload 回調主要是用于處理腳本加載失敗或者超時的場景,并調用 promise 的 reject 回調,表示腳本加載失敗。
到此,關于“Webpack動態import原理及源碼分析”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。