最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Webpack學(xué)習(xí)之動(dòng)態(tài)import原理及源碼分析

 更新時(shí)間:2023年04月26日 09:29:44   作者:runnerdancer  
這篇文章主要為大家介紹了Webpack學(xué)習(xí)之動(dòng)態(tài)import原理及源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

在開(kāi)始之前,先給我的mini-react打個(gè)廣告。對(duì)react源碼感興趣的朋友,走過(guò)路過(guò)的朋友點(diǎn)個(gè)star

在平時(shí)的開(kāi)發(fā)中,我們經(jīng)常使用 import()實(shí)現(xiàn)代碼分割和懶加載。在低版本的瀏覽器中并不支持動(dòng)態(tài) import(),那 webpack 是如何實(shí)現(xiàn) import() polyfill 的?

原理分析

我們先來(lái)看看下面的 demo

function component() {
  const btn = document.createElement("button");
  btn.onclick = () => {
    import("./a.js").then((res) => {
      console.log("動(dòng)態(tài)加載a.js..", res);
    });
  };
  btn.innerHTML = "Button";
  return btn;
}
document.body.appendChild(component());

點(diǎn)擊按鈕,動(dòng)態(tài)加載 a.js腳本,查看瀏覽器網(wǎng)絡(luò)請(qǐng)求可以發(fā)現(xiàn),a.js請(qǐng)求返回的內(nèi)容如下:

簡(jiǎn)單看,實(shí)際上返回的就是下面這個(gè)東西:

(self["webpackChunkwebpack_demo"] =
  self["webpackChunkwebpack_demo"] || []).push([
  ["src_a_js"],
  {
    "./src/a.js": () => {},
  },
]);

從上面可以看出 3 點(diǎn)信息:

  • 1.webpackChunkwebpack_demo 是掛到全局 window 對(duì)象上的屬性
  • 2.webpackChunkwebpack_demo 是個(gè)數(shù)組
  • 3.webpackChunkwebpack_demo 有個(gè) push 方法,用于添加動(dòng)態(tài)的模塊。當(dāng)a.js腳本請(qǐng)求成功后,這個(gè)方法會(huì)自動(dòng)執(zhí)行。

再來(lái)看看 main.js 返回的內(nèi)容

仔細(xì)觀察,動(dòng)態(tài) import 經(jīng)過(guò) webpack 編譯后,變成了下面的一坨東西:

__webpack_require__.e("src_a_js")
  .then(__webpack_require__.bind(__webpack_require__, "./src/a.js"))
  .then((res) => {
    console.log("動(dòng)態(tài)加載a.js..", res);
  });

上面代碼中,__webpack_require__ 用于執(zhí)行模塊,比如上面我們通過(guò)webpackChunkwebpack_demo.push添加的模塊,里面的./src/a.js函數(shù)就是在__webpack_require__里面執(zhí)行的。

__webpack_require__.e函數(shù)就是用來(lái)動(dòng)態(tài)加載遠(yuǎn)程腳本。因此,從上面的代碼中我們可以看出:

  • 首先 webpack 將動(dòng)態(tài) import 編譯成 __webpack_require__.e 函數(shù)
  • __webpack_require__.e函數(shù)加載遠(yuǎn)程的腳本,加載完成后調(diào)用 __webpack_require__ 函數(shù)
  • __webpack_require__函數(shù)負(fù)責(zé)調(diào)用遠(yuǎn)程腳本返回來(lái)的模塊,獲取腳本里面導(dǎo)出的對(duì)象并返回

源碼分析及實(shí)現(xiàn)

如何動(dòng)態(tài)加載遠(yuǎn)程模塊

在開(kāi)始之前,我們先來(lái)看下如何使用 script 標(biāo)簽加載遠(yuǎn)程模塊

var inProgress = {};
// url: "http://localhost:8080/src_a_js.main.js"
// done: 加載完成的回調(diào)
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) 函數(shù)比較簡(jiǎn)單,就是通過(guò)創(chuàng)建 script 標(biāo)簽加載遠(yuǎn)程腳本,加載完成后執(zhí)行 done 回調(diào)。inProgress用于避免多次創(chuàng)建 script 標(biāo)簽。比如我們多次調(diào)用loadScript('http://localhost:8080/src_a_js.main.js', done)時(shí),應(yīng)該只創(chuàng)建一次 script 標(biāo)簽,不需要每次都創(chuàng)建。這也是為什么我們調(diào)用多次 import('a.js'),瀏覽器 network 請(qǐng)求只看到家在一次腳本的原因

實(shí)際上,這就是 webpack 用于加載遠(yuǎn)程模塊的極簡(jiǎn)版本。

__webpack_require__.e 函數(shù)的實(shí)現(xiàn)

首先我們使用installedChunks對(duì)象保存動(dòng)態(tài)加載的模塊。key 是 chunkId

// 存儲(chǔ)已經(jīng)加載和正在加載的chunks,此對(duì)象存儲(chǔ)的是動(dòng)態(tài)import的chunk,對(duì)象的key是chunkId,值為
// 以下幾種:
// undefined: chunk not loaded
// null: chunk preloaded/prefetched
// [resolve, reject, Promise]: chunk loading
// 0: chunk loaded
var installedChunks = {
  main: 0,
};

由于 import() 返回的是一個(gè) promise,然后import()經(jīng)過(guò) webpack 編譯后就是一個(gè)__webpack_require__.e函數(shù),因此可以得出__webpack_require__.e返回的也是一個(gè) 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.如果多次調(diào)用了__webpack_require__.e函數(shù),即多次調(diào)用import('a.js')加載相同的模塊,只要第一次的加載還沒(méi)完成,就直接使用第一次的Promise
  if (installedChunkData) {
    promise = installedChunkData[2];
  } else {
    promise = new Promise((resolve, reject) => {
      // 2.注意,此時(shí)的resolve,reject還沒(méi)執(zhí)行
      installedChunkData = installedChunks[chunkId] = [resolve, reject];
    });
    installedChunkData[2] = promise; //3. 此時(shí)的installedChunkData 為[resolve, reject, promise]
    var url = scriptUrl + chunkId;
    var error = new Error();
    // 4.在script標(biāo)簽加載完成或者加載失敗后執(zhí)行l(wèi)oadingEnded方法
    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.執(zhí)行上面的reject,那resolve在哪里執(zhí)行呢?
        }
      }
    };
    loadScript(url, loadingEnded, "chunk-" + chunkId, chunkId);
  }
  return promise;
};

__webpack_require__.e的主要邏輯在ensureChunk方法中,注意該方法里面的第 1 到第 5 個(gè)注釋。這個(gè)方法創(chuàng)建一個(gè) promise,并調(diào)用loadScript方法加載動(dòng)態(tài)模塊。需要特別主要的是,返回的 promise 的 resolve 方法并不是在 script 標(biāo)簽加載完成后改變。如果腳本加載錯(cuò)誤或者超時(shí),會(huì)在 loadingEnded 方法里調(diào)用 promise 的 reject 方法。

實(shí)際上,promise 的 resolve 方法是在腳本請(qǐng)求完成后,在 self["webpackChunkwebpack_demo"].push()執(zhí)行的時(shí)候調(diào)用的

如何執(zhí)行遠(yuǎn)程模塊?

遠(yuǎn)程模塊是通過(guò)self["webpackChunkwebpack_demo"].push()函數(shù)執(zhí)行的

前面我們提到,a.js請(qǐng)求返回的內(nèi)容是一個(gè)self["webpackChunkwebpack_demo"].push()函數(shù)。當(dāng)請(qǐng)求完成,會(huì)自動(dòng)執(zhí)行這個(gè)函數(shù)。實(shí)際上,這就是一個(gè) jsonp 的回調(diào)方式。該方法的實(shí)現(xiàn)如下:

var webpackJsonpCallback = (data) => {
  var [chunkIds, moreModules] = data;
  var moduleId,
    chunkId,
    i = 0;
  for (moduleId in moreModules) {
    // 1.__webpack_require__.m存儲(chǔ)的是所有的模塊,包括靜態(tài)模塊和動(dòng)態(tài)模塊
    __webpack_require__.m[moduleId] = moreModules[moduleId];
  }
  for (; i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    if (installedChunks[chunkId]) {
      // 2.調(diào)用ensureChunk方法生成的promise的resolve回調(diào)
      installedChunks[chunkId][0]();
    }
    // 3.將該模塊標(biāo)記為0,表示已經(jīng)加載過(guò)
    installedChunks[chunkId] = 0;
  }
};
self["webpackChunkwebpack_demo"] = [];
self["webpackChunkwebpack_demo"].push = webpackJsonpCallback.bind(null);

所有通過(guò)import()加載的模塊,經(jīng)過(guò) webpack 編譯后,都會(huì)被 self["webpackChunkwebpack_demo"].push()包裹。

總結(jié)

在 webpack 構(gòu)建編譯階段,import()會(huì)被編譯成類(lèi)似__webpack_require__.e("src_a_js").then(__webpack_require__.bind(__webpack_require__, "./src/a.js"))的調(diào)用方式

__webpack_require__
  .e("src_a_js")
  .then(__webpack_require__.bind(__webpack_require__, "./src/a.js"))
  .then((res) => {
    console.log("動(dòng)態(tài)加載a.js..", res);
  });

__webpack_require__.e()方法會(huì)創(chuàng)建一個(gè) script 標(biāo)簽用于請(qǐng)求腳本,方法執(zhí)行完返回一個(gè) promise,此時(shí)的 promise 狀態(tài)還沒(méi)改變。

script 標(biāo)簽被添加到 document.head 后,觸發(fā)瀏覽器網(wǎng)絡(luò)請(qǐng)求。請(qǐng)求成功后,動(dòng)態(tài)的腳本會(huì)自動(dòng)執(zhí)行,此時(shí)self["webpackChunkwebpack_demo"].push()方法執(zhí)行,將動(dòng)態(tài)的模塊添加到__webpack_require__.m屬性中。同時(shí)調(diào)用 promise 的 resolve 方法改變狀態(tài),模塊加載完成。

腳本執(zhí)行完成后,最后執(zhí)行 script 標(biāo)簽的 onload 回調(diào)。onload 回調(diào)主要是用于處理腳本加載失敗或者超時(shí)的場(chǎng)景,并調(diào)用 promise 的 reject 回調(diào),表示腳本加載失敗

以上就是Webpack學(xué)習(xí)之動(dòng)態(tài)import原理及源碼分析的詳細(xì)內(nèi)容,更多關(guān)于Webpack動(dòng)態(tài)import的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

鱼台县| 宣化县| 临城县| 象山县| 宁都县| 韩城市| 拜城县| 陈巴尔虎旗| 凤山县| 五莲县| 万州区| 华阴市| 镇坪县| 观塘区| 临漳县| 禄丰县| 凤冈县| 毕节市| 阿拉善盟| 阿勒泰市| 勐海县| 疏勒县| 建始县| 新建县| 且末县| 安丘市| 泾源县| 丹东市| 龙南县| 富民县| 汉沽区| 山西省| 旌德县| 汶川县| 玉山县| 仪征市| 深泽县| 延长县| 沙田区| 静海县| 襄汾县|