最新消息: 新版網站上線了?。?!
  • webpack是如何實現模塊化加載的方法

    webpack支持的模塊規范有 AMD 、CommonJS、ES2015 import 等規范。不管何種規范大致可以分為同步加載和異步加載兩種情況。本文將介紹webpack是如何實現模塊管理和加載。

    同步加載如下:

    import a from './a';
    console.log(a);

    異步加載如下:

    import('./a').then(a => console.log(a));

    webpacks實現的啟動函數,直接將入口程序module傳入啟動函數內,并緩存在閉包內,如下:

    (function(modules){
      ......
      // 加載入口模塊并導出(實現啟動程序)
      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在實現模塊管理上不管服務端還是客戶端大致是一樣,主要由installedChunks記錄已經加載的chunk,installedModules記錄已經執行過的模塊,具體如下:

    /**
     * module 緩存器
     * key 為 moduleId (一般為文件路徑)
     * value 為 module 對象 {i: moduleId, l: false, exports: {}}
     */
    var installedModules = {};
    /**
     * chunks加載狀態記錄器
     * key 一般為 chunk 索引
     * value undefined:未加載 0:已經加載 (客戶端特有 null: 準備加載 [resolve, reject]: 加載中)
     */
    var installedChunks = {
      "app": 0
    }
    

    不管是服務端還是客戶端同步加載的方法都一樣,主要是檢測installedModules中是否已經緩存有要加載的module,有則直接返回,否則就創建一個新的module,并執行返回module.exports,具體實現如下:

    // 編譯后的同步加載
    __webpack_require__(/*! ./src/app.js */"./src/app.js");
    
    // 加載模塊的方法,即require方法
    function __webpack_require__(moduleId) {
      // 檢查當前的module是否已經存在緩存中
      if(installedModules[moduleId]) {
        return installedModules[moduleId].exports; // 直接返回已緩存的 module.exports
      }
      // 創建一個新的 module, 并添加到緩存中
      var module = installedModules[moduleId] = {
        i: moduleId,
        l: false, // 是否已經加載
        exports: {} // 暴露的對象
      };
      // 執行當前 module 的方法
      modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
      // 標記 module 加載完成狀態
      module.l = true;
      // 返回 module 暴露的 exports 對象
      return module.exports;
    }
    
    

    服務端的異步加載是通過node的require方法加載chunk并返回一個promises對象。所有chunk都是暴露出ids和modules對象,具體實現如下:

    // 編譯后的異步加載方法
    __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內容([chunkIds, modules])存到全局的webpackJsonp數組中,并改造webpackJsonp的push方法實現監聽chunk加載完成事件。具體實現如下:

    // 編譯后的異步加載方法
    __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');
        });
      })
    }]);
    
    // 加載成功的回調函數
    function webpackJsonpCallback(data) {
      var chunkIds = data[0];
      var moreModules = data[1];
      
      // 將本次加載回來的 chunk 標記為加載完成狀態,并執行回調
      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成功回調添加到要執行的隊列中
        }
        installedChunks[chunkId] = 0; // 將chunk標記為加載完成
      }
      // 將本次加載回來的 module 添加到全局的 modules 對象
      for(moduleId in moreModules) {
        if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
          modules[moduleId] = moreModules[moduleId];
        }
      }
      // 判斷 webpackJsonp 數組原始的push方法是否存在,存在則將數據追加到webpackJsonp中
      if(parentJsonpFunction) parentJsonpFunction(data);
      // 執行所有 chunk 回調
      while(resolves.length) {
        resolves.shift()();
      }
    };
    
    // 加載完成監聽方法的實現
    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 時表示已經安裝完成
        if(installedChunkData) { // 加載中
          promises.push(installedChunkData[2]);
        } else {
          // 創建一個回調的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內存泄漏。
            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);
    };
    
    

    更多可以查看編譯后的代碼 客戶端、服務端

    以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持谷谷點程序。

    .....

    轉載請注明:谷谷點程序 » webpack是如何實現模塊化加載的方法

    平度胜利路按摩在什么地方多,平度做大活的足疗,平度职教中心女孩微信,平度找曼地方