webpack 源碼解析1

源碼解析1

打包文件解析

安裝 webpack 插件

yarn add webpack webpack-cli -D

新建 src/index.js data.js,寫點(diǎn)寫點(diǎn)內(nèi)容

const data = "webpack 4"

export default data

// index.js
import data from './data'

console.log(data);

我們執(zhí)行開發(fā)環(huán)境打包

"dev": "webpack --mode development",

在 dist 目錄下會(huì)生成一個(gè) main.js 里面有一拖代碼,我們把 注釋,evel 去掉有用的代碼差不多就是下面這一些:

(function (modules) { 
    //  module 緩存的對象
    var installedModules = {};

    function __webpack_require__(moduleId) {
        console.log(moduleId);
        // 檢查 module 是否在緩存中
        if (installedModules[moduleId]) {
            return installedModules[moduleId].exports;
        }
        // 創(chuàng)建一個(gè) module
        var module = installedModules[moduleId] = {
            i: moduleId,
            l: false,
            exports: {}
        };

        // 執(zhí)行 module 里面的方法
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

        // 設(shè)置加載狀態(tài)
        module.l = true;

        // 返回 module
        return module.exports;
    }

    // expose the modules object (__webpack_modules__)
    __webpack_require__.m = modules;

    // __webpack_public_path__
    __webpack_require__.p = "";

    return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
    ({

        "./src/data.js": (function (module, __webpack_exports__, __webpack_require__) {

            __webpack_require__.r(__webpack_exports__);
            const data = "webpack 4";
            /* harmony default export */
            __webpack_exports__["default"] = (data);
        }),

        "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
            __webpack_require__.r(__webpack_exports__);
            /* harmony import */
            var _data__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./data */ "./src/data.js");
            console.log(_data__WEBPACK_IMPORTED_MODULE_0__["default"]);
        })
    });

一看到這些 第一反應(yīng)肯定是 這都是些啥啊,不急,讓我們慢慢來分析扒披。

  1. 首先這是一個(gè) 立即執(zhí)行函數(shù),我們傳入了一個(gè)對象:分別有兩個(gè) key 為 ./src/data.js,./src/index.js,每個(gè)對應(yīng)了一個(gè)函數(shù).
  2. 來到 函數(shù)里面,首先定義了一個(gè) installedModules 的全局對象,它就是來記錄傳入的 key 是否可以復(fù)用
  3. 重點(diǎn)來了 webpack_require, 首先判斷傳入的 moduleId (也就是./src/data.js,./src/index.js) 是否在 installedModules 里面,如果有直接返回對應(yīng)緩存里面的 exports,如果沒有的就創(chuàng)建一個(gè) module 對象,并 執(zhí)行 call,最后返回 module.exports
  4. 函數(shù)最后返回 webpack_require("./src/index.js") 的結(jié)果

webpack_require

ok,我們首先打印下 modules 對象,發(fā)現(xiàn)他是如下這樣的

{
"./src/data.js": ? (module, __webpack_exports__, __webpack_require__)
"./src/index.js": ? (module, __webpack_exports__, __webpack_require__)
}

那么我們現(xiàn)在執(zhí)行 webpack_require函數(shù)傳入入口文件 ./src/index.js ,第一次肯定不在緩存里面,因此會(huì)走創(chuàng)建的過程并執(zhí)行 modules[moduleId].call,看到這里就清晰了. 哦, modules[moduleId] 就是 我們?nèi)肟?./src/data.js 對應(yīng)的函數(shù),現(xiàn)在 call了一下 執(zhí)行

我們現(xiàn)在來看看 ./src/index.js 對應(yīng)的函數(shù)

"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__);
        /* harmony import */
        var _data__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./data */ "./src/data.js");
        console.log(_data__WEBPACK_IMPORTED_MODULE_0__["default"]);
    })

在這個(gè)函數(shù) 它又去 調(diào)用了 webpack_require,并且傳入了 moduleId./src/data.js. 哦,看到這個(gè)就豁然開朗了, 我 index.js 需要用到 data.js 里面的數(shù)據(jù),那么好 我就在 index.js 去 webpack_require 對應(yīng)的模塊,然后 又會(huì)走到 data.js 自己的 call,那我們現(xiàn)在來看看 data.js 對應(yīng)的函數(shù)執(zhí)行

    "./src/data.js": (function (module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__);
        const data = "webpack 4";
        /* harmony default export */
        __webpack_exports__["default"] = (data);
    }),
  1. 定義一個(gè) const data 的變量
  2. 把 data 變量掛載在 webpack_exports["default"] 屬性上, webpack_exports 就是執(zhí)行 call 的時(shí)候 傳入的 module.exports 對象
  3. ./src/index.js 看到 data__WEBPACK_IMPORTED_MODULE_0_ 就等于 webpack_require(./src/data.js) 的返回結(jié)果, webpack_require 函數(shù)返回什么呢,我們在上面看到了 它返回 module.exports
  4. module.exports 上面掛載了 一個(gè) default 屬性 也就是 data,我們輸入它

我們把上面的 代碼 粘貼到瀏覽器發(fā)現(xiàn) 正常輸出了 打印

我們在 data.js 里面再引入 output.js

const output = "我是 output"

export default output

我們執(zhí)行打包,看下入口的傳入

{

    "./src/data.js":
      (function (module, __webpack_exports__, __webpack_require__) {

        "use strict";
        __webpack_require__.r(__webpack_exports__);
        /* harmony import */
        var _output__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/output.js");
        const data = "webpack 4";
        console.log(_output__WEBPACK_IMPORTED_MODULE_0__["default"]);
        __webpack_exports__["default"] = (data);
      }),

    "./src/index.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__);
        var _data__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/data.js");
        console.log(_data__WEBPACK_IMPORTED_MODULE_0__["default"]);
      }),

    "./src/output.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        __webpack_require__.r(__webpack_exports__);
        const output = "我是 output"
        __webpack_exports__["default"] = (output);
      })
  }

看到了嗎澡刹? 我們 index.js 引入了 data.js 模塊里面的變量, data.js 又引入了 output.js 里面的變量,他們加載順序會(huì)如下

  1. 執(zhí)行方法 webpack_require("./src/index.js");
  2. 在對應(yīng)的 "./src/index.js" 里面又會(huì)執(zhí)行 webpack_require("./src/data.js")
  3. 同樣在 webpack_require("./src/output.js") 里面 又會(huì)執(zhí)行 webpack_require("./src/output.js"), 返回 webpack_exports["default"] 并執(zhí)行打印輸出, 再把自身 的 data 變量 掛載到自己 module 的 webpack_exports["default"] 上
  4. ./src/index.js 接受 webpack_require 的返回在 default 屬性上取到 data 并打印輸出

因此是先打印 "我是 output" 再是 webpack 4, 在瀏覽器執(zhí)行發(fā)現(xiàn)和我們推理的打印順序一樣樊卓。

異步加載文件

對于一些異步文件加載我們可以這樣引用

const data = "webpack 4"

import('./output').then(_ => {
    console.log(_.default);
})
export default data

我們現(xiàn)在再來看看打包后的文件,現(xiàn)在有了一個(gè) main.js 和一個(gè) 0.js,先看看 0.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], {
  "./src/output.js": (function (module, __webpack_exports__, __webpack_require__) {
    __webpack_require__.r(__webpack_exports__);
    const output = "我是 output"
    __webpack_exports__["default"] = (output);
  })
}]);

main.js 整理后 主要的 js 如下:

(function (modules) { 
  function webpackJsonpCallback(data) {
    var chunkIds = data[0];
    var moreModules = data[1];

    var moduleId, chunkId, i = 0, resolves = [];
    // 收集模塊 將所有 chunkIds 標(biāo)記為已加載 chunkId=0
    for (; i < chunkIds.length; i++) {
      chunkId = chunkIds[i];
      if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
        // installedChunks[chunkId] = [resolve, reject]
        resolves.push(installedChunks[chunkId][0]);
      }
      installedChunks[chunkId] = 0;
    }
    for (moduleId in moreModules) {
      if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
        modules[moduleId] = moreModules[moduleId];
      }
    }
    if (parentJsonpFunction) parentJsonpFunction(data);
    // 執(zhí)行每個(gè) resolve
    while (resolves.length) {
      resolves.shift()();
    }
  };
  // The module cache
  var installedModules = {};

  var installedChunks = {
    "main": 0
  };

  // script path function
  function jsonpScriptSrc(chunkId) {
    return __webpack_require__.p + "" + ({}[chunkId] || chunkId) + ".js"
  }
  function __webpack_require__(moduleId) {
      ...
  }

  // 根據(jù)  chunkId 加載
  __webpack_require__.e = function requireEnsure(chunkId) {
    var promises = [];
    var installedChunkData = installedChunks[chunkId];
    // // 0 標(biāo)志已緩存.
    if (installedChunkData !== 0) {

      //  Promise 當(dāng)前正在加載
      if (installedChunkData) {
        promises.push(installedChunkData[2]);
      } else {
        // 設(shè)置緩存
        var promise = new Promise(function (resolve, reject) {
          installedChunkData = installedChunks[chunkId] = [resolve, reject];
        });
        promises.push(installedChunkData[2] = promise);
        // start chunk loading
        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];
          console.log(chunk);
          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);
  };

  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;
  // Load entry module and return exports
  return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
  ({
    "./src/data.js": (function (module, __webpack_exports__, __webpack_require__) {
      const data = "webpack 4"
      __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./output */ "./src/output.js")).then(_ => { console.log(_.default); })
      __webpack_exports__["default"] = (data);
    }),

    "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
     ...
    })
  });

首先入口文件 還是 ./src/index.js,它會(huì)再去加載 ./src/data.js 這和我們上面的同步 步驟完全一致. 但是在 data.js 中使用了異步加載的方法,對于的打包文件中 可以看到使用了 webpack_require.e 這樣的一個(gè)方法,我們來仔細(xì)分析下這個(gè)方法

webpack_require.e

webpack_require.e 方法會(huì)接受一個(gè) chunkId,也就是我們打包的 "0.js"的 0,然后依次做了以下操作

  1. 申明一個(gè)總 promises,判斷 chunkId 是否已經(jīng)被緩存過, 0 標(biāo)志著緩存
  2. 設(shè)置一個(gè) promise,并設(shè)置 installedChunks[chunkId] 分別添加 resolve,reject 以及 promise 本身,并把 promise 添加到總的 promises
  3. 創(chuàng)建一個(gè) script 標(biāo)簽,并設(shè)置超時(shí)時(shí)間為 120 秒
  4. 處理 script 加載異常的情況
  5. 執(zhí)行 document.head.appendChild(script) 把script 追加到頁面上;
  6. 然后 return Promise.all(promises);

我們有多少個(gè)異步就創(chuàng)建了多少個(gè) promise 并維護(hù)在了 installedChunks 對象里面, 我們在 ./src/data.js 加載異步的時(shí)候 執(zhí)行了

__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./output */ "./src/output.js")).then(_ => { console.log(_.default); })

webpack_require.e 返回了 Promise.all(promises),只有 promises 里面的每一個(gè)promise 執(zhí)行 resolve 外面大的才能 resolve,關(guān)鍵這里的代碼 我們并沒有看到 每個(gè)小的 promise 執(zhí)行 resolve啊?沉桌??

webpackJsonpCallback

我們順著主代碼往下看, 有這么一段 代碼

 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;

啥意思呢這段代碼, window["webpackJsonp"] 我們看著很熟悉, 它和 0.js 好像是一樣的東西

window[“webpackJsonp”].push 指向 webpackJsonpCallback 函數(shù)。我們在 執(zhí)行上面操作的時(shí)候會(huì)去創(chuàng)建一個(gè)標(biāo)簽,里面的內(nèi)容就是 window["webpackJsonp"] || []).push,異步文件通過這個(gè)指針压彭,把內(nèi)容傳進(jìn) webpackJsonpCallback 函數(shù)內(nèi)。

webpackJsonpCallback 做了那些事情呢

  1. 收集模塊 將所有 chunkIds 標(biāo)記為已加載 chunkId=0
  2. 創(chuàng)建一個(gè) resolves 數(shù)組并添加 對應(yīng) chunkId 的 第 0 項(xiàng),也就是我們上面的 installedChunks[chunkId] 添加的第一項(xiàng) resolve
  3. 把異步組件的入口掛載到 modules 對象上
  4. while 把 每個(gè) promise 執(zhí)行 resolve()

于是乎 也跟 下面這段代碼對應(yīng)上了

__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./output */ "./src/output.js")).then(_ => { console.log(_.default); })

總結(jié)

webpack 打包文件加載流程如下:

  1. 執(zhí)行入口文件 webpack_require("./src/index.js"),執(zhí)行 modules[moduleId].call
  2. 如果文件里面有依賴別人 則繼續(xù)調(diào)用 webpack_require 并傳入對應(yīng)的入口,把返回結(jié)果掛載到 default
  3. 如果是異步加載 則 先執(zhí)行 webpack_require.e 創(chuàng)建 script 標(biāo)簽,設(shè)置超時(shí)時(shí)間為120,并返回Promisea.all()
  4. 通過 webpackJsonpCallback 函數(shù) 去依次執(zhí)行 promise 的 resolve,然后就可以執(zhí)行 then 方法了,然后里面再傳入一個(gè) webpack_require.bind() 的函數(shù) 再次 then 后 就可以拿到 對應(yīng)模塊的變量
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渗常,一起剝皮案震驚了整個(gè)濱河市壮不,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌皱碘,老刑警劉巖询一,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異癌椿,居然都是意外死亡健蕊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門踢俄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缩功,“玉大人,你說我怎么就攤上這事都办〉招浚” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵脆丁,是天一觀的道長世舰。 經(jīng)常有香客問我,道長槽卫,這世上最難降的妖魔是什么跟压? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮歼培,結(jié)果婚禮上震蒋,老公的妹妹穿的比我還像新娘。我一直安慰自己躲庄,他們只是感情好查剖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著噪窘,像睡著了一般笋庄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天直砂,我揣著相機(jī)與錄音菌仁,去河邊找鬼。 笑死静暂,一個(gè)胖子當(dāng)著我的面吹牛济丘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洽蛀,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摹迷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了郊供?” 一聲冷哼從身側(cè)響起峡碉,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颂碘,沒想到半個(gè)月后异赫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡头岔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鼠证。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峡竣。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖量九,靈堂內(nèi)的尸體忽然破棺而出适掰,到底是詐尸還是另有隱情,我是刑警寧澤荠列,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布类浪,位于F島的核電站,受9級特大地震影響肌似,放射性物質(zhì)發(fā)生泄漏费就。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一川队、第九天 我趴在偏房一處隱蔽的房頂上張望力细。 院中可真熱鬧,春花似錦固额、人聲如沸眠蚂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逝慧。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笛臣,已是汗流浹背栅干。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捐祠,地道東北人碱鳞。 一個(gè)月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像踱蛀,于是被迫代替她去往敵國和親窿给。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355