带你分析webpack(v5.74.0)热更新原理

配置如下:
入口文件为index.js:

import { c } from './b'
const add = document.createElement('button')
add.innerText = '新增'

const div = document.createElement('div')
div.innerText = Math.random()
add.addEventListener('click', function () {
  const div = document.createElement('div')
  div.innerText = Math.random()
  document.body.append(div)
})
document.body.appendChild(add)

if (module.hot) {
  //一单module.hot为true,说明开启了HMR功能
  module.hot.accept(['./b.js'], function () {
    c()
  })
}

b.js文件:

export const c = function () {
  const node = document.createElement('div')
  node.innerText = 1156
  node.style.color = 'red'
  document.body.appendChild(node)
}

export const d = function () {
  console.log(1114111);
}

我先说下热刷新的基本流程,让大家有个概念:

要想实现热刷新必须明确一下几点:

  1. webpack要监听文件修改
  2. 获取最新的hash值发给浏览器
  3. 浏览器使用hash值发起请求获取最新的文件
  4. 服务端拦截浏览器的请求,并从内存获取内容返回给浏览器
  5. 用动态script的方式渲染拉取的文件

首先在wbpack/hot/dev-server.js中会监听webpackHotUpdate事件,该事件是由文件改变触发的,可以获取当前的hash值并触发check方法

var hotEmitter = require("./emitter");
hotEmitter.on("webpackHotUpdate", function (currentHash) {
	lastHash = currentHash;
	if (!upToDate() && module.hot.status() === "idle") {
		log("info", "[HMR] Checking for updates on the server...");
		check();
	}
});

接着会调用wbpack/hot/dev-server.js模块的module.hot.check方法。该方法是浏览器端在加载wbpack/hot/dev-server.js中就已经定义。

var check = function check() {
module.hot
	.check(true)
	.then(function (updatedModules) {
		if (!updatedModules) {
			window.location.reload();
			return;
		}

		if (!upToDate()) {
			check();
		}

		__webpack_require__(/*! ./log-apply-result */ "./node_modules/webpack/hot/log-apply-result.js")(updatedModules, updatedModules);
	})
};

首先会调用setStatus方法将状态设置为check状态,

function hotCheck(applyOnUpdate) {
if (currentStatus !== "idle") {
   throw new Error("check() is only allowed in idle status");
 }
 return setStatus("check")
   .then(__webpack_require__.hmrM)
   .then(function (update) {
     if (!update) {
       return setStatus(applyInvalidatedModules() ? "ready" : "idle").then(
         function () {
           return null;
         }
       );
     }

     return setStatus("prepare").then(function () {
       var updatedModules = [];
       currentUpdateApplyHandlers = [];

       return Promise.all(
         Object.keys(__webpack_require__.hmrC).reduce(function (
           promises,
           key
         ) {
           __webpack_require__.hmrC[key](
             update.c,
             update.r,
             update.m,
             promises,
             currentUpdateApplyHandlers,
             updatedModules
           );
           return promises;
         },
           [])
            ).then(function () {
              return waitForBlockingPromises(function () {
                if (applyOnUpdate) {
                  return internalApply(applyOnUpdate);
                } else {
                  return setStatus("ready").then(function () {
                    return updatedModules;
                  });
                }
              });
            });
          });
        });
    }

该方法设置当前状态并返回一个Promise:

function setStatus(newStatus) {
	 currentStatus = newStatus;
	 var results = [];
	
	 for (var i = 0; i < registeredStatusHandlers.length; i++)
	   results[i] = registeredStatusHandlers[i].call(null, newStatus);
	
	 return Promise.all(results);
}

随后调用**webpack_require.hmrM**方法:

__webpack_require__.hmrM = () => {
  if (typeof fetch === "undefined") throw new Error("No browser support: need fetch API");
  return fetch(__webpack_require__.p + __webpack_require__.hmrF()).then((response) => {
    if (response.status === 404) return; // no update available
    if (!response.ok) throw new Error("Failed to fetch update manifest " + response.statusText);
    return response.json();
  });
};

该方法会先使用fetch获取某个资源,然后再返回json格式。获取该url之前会获取**webpack_require.p + webpack_require.hmrF(),其中webpack_require.p**是http://localhost:8080/,.hmrF()方法如下:

(() => {
    __webpack_require__.hmrF = () => ("testxx." + __webpack_require__.h() + ".hot-update.json");
  })();

该方法是获取要更新的文件json。返回值为testxx.eb91f4fb22db17abdae7.hot-update.json。该资源返回:{“c”:[“testxx”],“r”:[],“m”:[]}。本人只修改了b模块的内容,由于b模块和testxx模块是打包到一个文件中,所以要更新的模块为testxx。此时获取完资源后会继续执行hotCheck的setStatus(“prepare”)方法。执行完后执行then方法:

return Promise.all(
   Object.keys(__webpack_require__.hmrC).reduce(function (
     promises,
     key
   ) {
     __webpack_require__.hmrC[key](
       update.c,
       update.r,
       update.m,
       promises,
       currentUpdateApplyHandlers,
       updatedModules
     );
     return promises;
   },
     [])
 )

该方法获取__webpack_require__.hmrC的key值,并执行__webpack_require__.hmrC[key]函数。此时key为jsonp。代码如下:

__webpack_require__.hmrC.jsonp = function (
  chunkIds,
  removedChunks,
  removedModules,
  promises,
  applyHandlers,
  updatedModulesList
) {
  applyHandlers.push(applyHandler);
  currentUpdateChunks = {};
  currentUpdateRemovedChunks = removedChunks;
  currentUpdate = removedModules.reduce(function (obj, key) {
    obj[key] = false;
    return obj;
  }, {});
  currentUpdateRuntime = [];
  chunkIds.forEach(function (chunkId) {
    if (
      __webpack_require__.o(installedChunks, chunkId) &&
      installedChunks[chunkId] !== undefined
    ) {
      promises.push(loadUpdateChunk(chunkId, updatedModulesList));
      currentUpdateChunks[chunkId] = true;
    } else {
      currentUpdateChunks[chunkId] = false;
    }
  });
  if (__webpack_require__.f) {
    __webpack_require__.f.jsonpHmr = function (chunkId, promises) {
      if (
        currentUpdateChunks &&
        __webpack_require__.o(currentUpdateChunks, chunkId) &&
        !currentUpdateChunks[chunkId]
      ) {
        promises.push(loadUpdateChunk(chunkId));
        currentUpdateChunks[chunkId] = true;
      }
    };
  }
};

该段最主要是调用loadUpdateChunk方法:

function loadUpdateChunk(chunkId, updatedModulesList) {
  currentUpdatedModulesList = updatedModulesList;
  return new Promise((resolve, reject) => {
    waitingUpdateResolves[chunkId] = resolve;
    // start update chunk loading
    var url = __webpack_require__.p + __webpack_require__.hu(chunkId);
    // create error before stack unwound to get useful stacktrace later
    var error = new Error();
    var loadingEnded = (event) => {
      if (waitingUpdateResolves[chunkId]) {
        waitingUpdateResolves[chunkId] = undefined
        var errorType = event && (event.type === 'load' ? 'missing' : event.type);
        var realSrc = event && event.target && event.target.src;
        error.message = 'Loading hot update chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
        error.name = 'ChunkLoadError';
        error.type = errorType;
        error.request = realSrc;
        reject(error);
      }
    };
    __webpack_require__.l(url, loadingEnded);
  });
}

该方法首先拼成要获取的文件url,根据hash值和chunkId组成hot-update.js。随后调用__webpack_require__.l(url, loadingEnded)方法,该方法如下:

__webpack_require__.l = (url, done, key, chunkId) => {
 if (inProgress[url]) { inProgress[url].push(done); return; }
  var script, needAttach;
  if (key !== undefined) {
    var scripts = document.getElementsByTagName("script");
    for (var i = 0; i < scripts.length; i++) {
      var s = scripts[i];
      if (s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
    }
  }
  if (!script) {
    needAttach = true;
    script = document.createElement('script');

    script.charset = 'utf-8';
    script.timeout = 120;
    if (__webpack_require__.nc) {
      script.setAttribute("nonce", __webpack_require__.nc);
    }
    script.setAttribute("data-webpack", dataWebpackPrefix + key);
    script.src = url;
  }
  inProgress[url] = [done];
  var onScriptComplete = (prev, event) => {
    // avoid mem leaks in IE.
    script.onerror = script.onload = null;
    clearTimeout(timeout);
    var doneFns = inProgress[url];  
    delete inProgress[url];
    script.parentNode && script.parentNode.removeChild(script);
    doneFns && doneFns.forEach((fn) => (fn(event)));
    if (prev) return prev(event);
  }
    ;
  var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
  script.onerror = onScriptComplete.bind(null, script.onerror);
  script.onload = onScriptComplete.bind(null, script.onload);
  needAttach && document.head.appendChild(script);
};

很明显该方法是创建了一个script,并将hot-update.js的url设置给script,并且有个超时timeout和正常onload的回调onScriptComplete。获取要更新的资源如下:

"use strict";
self["webpackHotUpdatetest_webpack"]("testxx",{

/***/ "./src/b.js":
/*!******************!*\
  !*** ./src/b.js ***!
  \******************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"c\": () => (/* binding */ c),\n/* harmony export */   \"d\": () => (/* binding */ d)\n/* harmony export */ });\nconst c = function () {\n  const node = document.createElement('div')\n  node.innerText = 1156\n  node.style.color = 'red'\n  document.body.appendChild(node)\n}\n\nconst d = function () {\n  console.log(1114111);\n}\n\n\n\n\n//# sourceURL=webpack://test-webpack/./src/b.js?");

/***/ })

},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ 	__webpack_require__.h = () => ("cfd4f2a109ca784f3597")
/******/ })();
/******/ 
/******/ }
);

首先会调用webpackHotUpdatetest_webpack方法,第一个参数是模块名,第二个是对象模块路径和内容,第三个是下次更新的hash值:

self["webpackHotUpdatetest_webpack"] = (chunkId, moreModules, runtime) => {
   for (var moduleId in moreModules) {
     if (__webpack_require__.o(moreModules, moduleId)) {
       currentUpdate[moduleId] = moreModules[moduleId];
       if (currentUpdatedModulesList) currentUpdatedModulesList.push(moduleId);
     }
   }
   if (runtime) currentUpdateRuntime.push(runtime);
   if (waitingUpdateResolves[chunkId]) {
     waitingUpdateResolves[chunkId]();
     waitingUpdateResolves[chunkId] = undefined;
   }
 };

该方法主要是设置要更新的模块路径和更新内容,随后执行loadUpdateChunk的resolve方法,表示资源的获取完成。执行完该方法后再次回到hotCheck的setStatus(“prepare”)方法的第二个then回调。代码如下:

return waitForBlockingPromises(function () {
 if (applyOnUpdate) {
    return internalApply(applyOnUpdate);
  } else {
    return setStatus("ready").then(function () {
      return updatedModules;
    });
  }
});

主要执行waitForBlockingPromises方法,fn就是刚刚传进来的函数:

function waitForBlockingPromises(fn) {
  if (blockingPromises === 0) return fn();
  return new Promise(function (resolve) {
    blockingPromisesWaiting.push(function () {
      resolve(fn());
    });
  });
}

此时blockingPromises为0,直接执行fn函数,blockingPromises的含义后面会提到。applyOnUpdate就是一开始调用hotCheck传入的true。紧接着执行internalApply方法。

function internalApply(options) {
 options = options || {};

  applyInvalidatedModules();

  var results = currentUpdateApplyHandlers.map(function (handler) {
    return handler(options);
  });
  currentUpdateApplyHandlers = undefined;

  var errors = results
    .map(function (r) {
      return r.error;
    })
    .filter(Boolean);

  if (errors.length > 0) {
    return setStatus("abort").then(function () {
      throw errors[0];
    });
  }

// Now in "dispose" phase
var disposePromise = setStatus("dispose");

    results.forEach(function (result) {
      if (result.dispose) result.dispose();
    });

      // Now in "apply" phase
      var applyPromise = setStatus("apply");

      var error;
      var reportError = function (err) {
        if (!error) error = err;
      };

      var outdatedModules = [];
      results.forEach(function (result) {
        if (result.apply) {
          var modules = result.apply(reportError);
          if (modules) {
            for (var i = 0; i < modules.length; i++) {
              outdatedModules.push(modules[i]);
            }
          }
        }
      });

      return Promise.all([disposePromise, applyPromise]).then(function () {
        // handle errors in accept handlers and self accepted module load
        if (error) {
          return setStatus("fail").then(function () {
            throw error;
          });
        }

        if (queuedInvalidatedModules) {
          return internalApply(options).then(function (list) {
            outdatedModules.forEach(function (moduleId) {
              if (list.indexOf(moduleId) < 0) list.push(moduleId);
            });
            return list;
          });
        }

        return setStatus("idle").then(function () {
          return outdatedModules;
        });
      });
    }

该方法最主要执行applyInvalidatedModules方法:

function applyInvalidatedModules() {
  if (queuedInvalidatedModules) {
    if (!currentUpdateApplyHandlers) currentUpdateApplyHandlers = [];
    Object.keys(__webpack_require__.hmrI).forEach(function (key) {
      queuedInvalidatedModules.forEach(function (moduleId) {
        __webpack_require__.hmrI[key](
          moduleId,
          currentUpdateApplyHandlers
        );
      });
    });
    queuedInvalidatedModules = undefined;
    return true;
  }
}

由于queuedInvalidatedModules为undefined,暂且跳过。然后继续执行currentUpdateApplyHandlers数组里面的方法。这个数组里面的方法就是一开始我们执行__webpack_require__.hmrI.jsonp方法放入的applyHandler方法:

function applyHandler(options) {
 if (__webpack_require__.f) delete __webpack_require__.f.jsonpHmr;
 currentUpdateChunks = undefined;
 function getAffectedModuleEffects(updateModuleId) {
   var outdatedModules = [updateModuleId];
   var outdatedDependencies = {};

   var queue = outdatedModules.map(function (id) {
     return {
       chain: [id],
       id: id
     };
   });
   while (queue.length > 0) {
     var queueItem = queue.pop();
     var moduleId = queueItem.id;
     var chain = queueItem.chain;
     var module = __webpack_require__.c[moduleId];
     if (
       !module ||
       (module.hot._selfAccepted && !module.hot._selfInvalidated)
     )
       continue;
     if (module.hot._selfDeclined) {
       return {
         type: "self-declined",
         chain: chain,
         moduleId: moduleId
       };
     }
     if (module.hot._main) {
       return {
         type: "unaccepted",
         chain: chain,
         moduleId: moduleId
       };
     }
     for (var i = 0; i < module.parents.length; i++) {
       var parentId = module.parents[i];
       var parent = __webpack_require__.c[parentId];
       if (!parent) continue;
       if (parent.hot._declinedDependencies[moduleId]) {
         return {
           type: "declined",
           chain: chain.concat([parentId]),
           moduleId: moduleId,
           parentId: parentId
         };
       }
       if (outdatedModules.indexOf(parentId) !== -1) continue;
       if (parent.hot._acceptedDependencies[moduleId]) {
         if (!outdatedDependencies[parentId])
           outdatedDependencies[parentId] = [];
         addAllToSet(outdatedDependencies[parentId], [moduleId]);
         continue;
       }
       delete outdatedDependencies[parentId];
       outdatedModules.push(parentId);
       queue.push({
         chain: chain.concat([parentId]),
         id: parentId
       });
     }
   }

   return {
     type: "accepted",
     moduleId: updateModuleId,
     outdatedModules: outdatedModules,
     outdatedDependencies: outdatedDependencies
   };
 }

 function addAllToSet(a, b) {
   for (var i = 0; i < b.length; i++) {
     var item = b[i];
     if (a.indexOf(item) === -1) a.push(item);
   }
 }

 // at begin all updates modules are outdated
 // the "outdated" status can propagate to parents if they don't accept the children
 var outdatedDependencies = {};
 var outdatedModules = [];
 var appliedUpdate = {};

 var warnUnexpectedRequire = function warnUnexpectedRequire(module) {
   console.warn(
     "[HMR] unexpected require(" + module.id + ") to disposed module"
   );
 };

 for (var moduleId in currentUpdate) {
   if (__webpack_require__.o(currentUpdate, moduleId)) {
     var newModuleFactory = currentUpdate[moduleId];
     /** @type {TODO} */
     var result;
     if (newModuleFactory) {
       result = getAffectedModuleEffects(moduleId);
     } else {
       result = {
         type: "disposed",
         moduleId: moduleId
       };
     }
     /** @type {Error|false} */
     var abortError = false;
     var doApply = false;
     var doDispose = false;
     var chainInfo = "";
     if (result.chain) {
       chainInfo = "\nUpdate propagation: " + result.chain.join(" -> ");
     }
     switch (result.type) {
       case "self-declined":
         if (options.onDeclined) options.onDeclined(result);
         if (!options.ignoreDeclined)
           abortError = new Error(
             "Aborted because of self decline: " +
             result.moduleId +
             chainInfo
           );
         break;
       case "declined":
         if (options.onDeclined) options.onDeclined(result);
         if (!options.ignoreDeclined)
           abortError = new Error(
             "Aborted because of declined dependency: " +
             result.moduleId +
             " in " +
             result.parentId +
             chainInfo
           );
         break;
       case "unaccepted":
         if (options.onUnaccepted) options.onUnaccepted(result);
         if (!options.ignoreUnaccepted)
           abortError = new Error(
             "Aborted because " + moduleId + " is not accepted" + chainInfo
           );
         break;
       case "accepted":
         if (options.onAccepted) options.onAccepted(result);
         doApply = true;
         break;
       case "disposed":
         if (options.onDisposed) options.onDisposed(result);
         doDispose = true;
         break;
       default:
         throw new Error("Unexception type " + result.type);
     }
     if (abortError) {
       return {
         error: abortError
       };
     }
     if (doApply) {
       appliedUpdate[moduleId] = newModuleFactory;
       addAllToSet(outdatedModules, result.outdatedModules);
       for (moduleId in result.outdatedDependencies) {
         if (__webpack_require__.o(result.outdatedDependencies, moduleId)) {
           if (!outdatedDependencies[moduleId])
             outdatedDependencies[moduleId] = [];
           addAllToSet(
             outdatedDependencies[moduleId],
             result.outdatedDependencies[moduleId]
           );
         }
       }
     }
     if (doDispose) {
       addAllToSet(outdatedModules, [result.moduleId]);
       appliedUpdate[moduleId] = warnUnexpectedRequire;
     }
   }
 }
 currentUpdate = undefined;

 // Store self accepted outdated modules to require them later by the module system
 var outdatedSelfAcceptedModules = [];
 for (var j = 0; j < outdatedModules.length; j++) {
   var outdatedModuleId = outdatedModules[j];
   var module = __webpack_require__.c[outdatedModuleId];
   if (
     module &&
     (module.hot._selfAccepted || module.hot._main) &&
     // removed self-accepted modules should not be required
     appliedUpdate[outdatedModuleId] !== warnUnexpectedRequire &&
     // when called invalidate self-accepting is not possible
     !module.hot._selfInvalidated
   ) {
     outdatedSelfAcceptedModules.push({
       module: outdatedModuleId,
       require: module.hot._requireSelf,
       errorHandler: module.hot._selfAccepted
     });
   }
 }

 var moduleOutdatedDependencies;

 return {
   dispose: function () {
     currentUpdateRemovedChunks.forEach(function (chunkId) {
       delete installedChunks[chunkId];
     });
     currentUpdateRemovedChunks = undefined;

     var idx;
     var queue = outdatedModules.slice();
     while (queue.length > 0) {
       var moduleId = queue.pop();
       var module = __webpack_require__.c[moduleId];
       if (!module) continue;

       var data = {};

       // Call dispose handlers
       var disposeHandlers = module.hot._disposeHandlers;
       for (j = 0; j < disposeHandlers.length; j++) {
         disposeHandlers[j].call(null, data);
       }
       __webpack_require__.hmrD[moduleId] = data;

       // disable module (this disables requires from this module)
       module.hot.active = false;

       // remove module from cache
       delete __webpack_require__.c[moduleId];

       // when disposing there is no need to call dispose handler
       delete outdatedDependencies[moduleId];

       // remove "parents" references from all children
       for (j = 0; j < module.children.length; j++) {
         var child = __webpack_require__.c[module.children[j]];
         if (!child) continue;
         idx = child.parents.indexOf(moduleId);
         if (idx >= 0) {
           child.parents.splice(idx, 1);
         }
       }
     }

     // remove outdated dependency from module children
     var dependency;
     for (var outdatedModuleId in outdatedDependencies) {
       if (__webpack_require__.o(outdatedDependencies, outdatedModuleId)) {
         module = __webpack_require__.c[outdatedModuleId];
         if (module) {
           moduleOutdatedDependencies =
             outdatedDependencies[outdatedModuleId];
           for (j = 0; j < moduleOutdatedDependencies.length; j++) {
             dependency = moduleOutdatedDependencies[j];
             idx = module.children.indexOf(dependency);
             if (idx >= 0) module.children.splice(idx, 1);
           }
         }
       }
     }
   },
   apply: function (reportError) {
     // insert new code
     for (var updateModuleId in appliedUpdate) {
       if (__webpack_require__.o(appliedUpdate, updateModuleId)) {
         __webpack_require__.m[updateModuleId] = appliedUpdate[updateModuleId];
       }
     }

     // run new runtime modules
     for (var i = 0; i < currentUpdateRuntime.length; i++) {
       currentUpdateRuntime[i](__webpack_require__);
     }

     // call accept handlers
     for (var outdatedModuleId in outdatedDependencies) {
       if (__webpack_require__.o(outdatedDependencies, outdatedModuleId)) {
         var module = __webpack_require__.c[outdatedModuleId];
         if (module) {
           moduleOutdatedDependencies =
             outdatedDependencies[outdatedModuleId];
           var callbacks = [];
           var errorHandlers = [];
           var dependenciesForCallbacks = [];
           for (var j = 0; j < moduleOutdatedDependencies.length; j++) {
             var dependency = moduleOutdatedDependencies[j];
             var acceptCallback =
               module.hot._acceptedDependencies[dependency];
             var errorHandler =
               module.hot._acceptedErrorHandlers[dependency];
             if (acceptCallback) {
               if (callbacks.indexOf(acceptCallback) !== -1) continue;
               callbacks.push(acceptCallback);
               errorHandlers.push(errorHandler);
               dependenciesForCallbacks.push(dependency);
             }
           }
           for (var k = 0; k < callbacks.length; k++) {
             try {
               callbacks[k].call(null, moduleOutdatedDependencies);
             } catch (err) {
               if (typeof errorHandlers[k] === "function") {
                 try {
                   errorHandlers[k](err, {
                     moduleId: outdatedModuleId,
                     dependencyId: dependenciesForCallbacks[k]
                   });
                 } catch (err2) {
                   if (options.onErrored) {
                     options.onErrored({
                       type: "accept-error-handler-errored",
                       moduleId: outdatedModuleId,
                       dependencyId: dependenciesForCallbacks[k],
                       error: err2,
                       originalError: err
                     });
                   }
                   if (!options.ignoreErrored) {
                     reportError(err2);
                     reportError(err);
                   }
                 }
               } else {
                 if (options.onErrored) {
                   options.onErrored({
                     type: "accept-errored",
                     moduleId: outdatedModuleId,
                     dependencyId: dependenciesForCallbacks[k],
                     error: err
                   });
                 }
                 if (!options.ignoreErrored) {
                   reportError(err);
                 }
               }
             }
           }
         }
       }
     }

     // Load self accepted modules
     for (var o = 0; o < outdatedSelfAcceptedModules.length; o++) {
       var item = outdatedSelfAcceptedModules[o];
       var moduleId = item.module;
       try {
         item.require(moduleId);
       } catch (err) {
              if (typeof item.errorHandler === "function") {
                try {
                  item.errorHandler(err, {
                    moduleId: moduleId,
                    module: __webpack_require__.c[moduleId]
                  });
                } catch (err2) {
                  if (options.onErrored) {
                    options.onErrored({
                      type: "self-accept-error-handler-errored",
                      moduleId: moduleId,
                      error: err2,
                      originalError: err
                    });
                  }
                  if (!options.ignoreErrored) {
                    reportError(err2);
                    reportError(err);
                  }
                }
              } else {
                if (options.onErrored) {
                  options.onErrored({
                    type: "self-accept-errored",
                    moduleId: moduleId,
                    error: err
                  });
                }
                if (!options.ignoreErrored) {
                  reportError(err);
                }
              }
            }
          }

          return outdatedModules;
        }
      };
    }

首先主要是执行getAffectedModuleEffects方法:

function getAffectedModuleEffects(updateModuleId) {
    var outdatedModules = [updateModuleId];
    var outdatedDependencies = {};

    var queue = outdatedModules.map(function (id) {
      return {
        chain: [id],
        id: id
      };
    });
    while (queue.length > 0) {
      var queueItem = queue.pop();
      var moduleId = queueItem.id;
      var chain = queueItem.chain;
      var module = __webpack_require__.c[moduleId];
      if (
        !module ||
        (module.hot._selfAccepted && !module.hot._selfInvalidated)
      )
        continue;
      if (module.hot._selfDeclined) {
        return {
          type: "self-declined",
          chain: chain,
          moduleId: moduleId
        };
      }
      if (module.hot._main) {
        return {
          type: "unaccepted",
          chain: chain,
          moduleId: moduleId
        };
      }
      for (var i = 0; i < module.parents.length; i++) {
        var parentId = module.parents[i];
        var parent = __webpack_require__.c[parentId];
        if (!parent) continue;
        if (parent.hot._declinedDependencies[moduleId]) {
          return {
            type: "declined",
            chain: chain.concat([parentId]),
            moduleId: moduleId,
            parentId: parentId
          };
        }
        if (outdatedModules.indexOf(parentId) !== -1) continue;
        if (parent.hot._acceptedDependencies[moduleId]) {
          if (!outdatedDependencies[parentId])
            outdatedDependencies[parentId] = [];
          addAllToSet(outdatedDependencies[parentId], [moduleId]);
          continue;
        }
        delete outdatedDependencies[parentId];
        outdatedModules.push(parentId);
        queue.push({
          chain: chain.concat([parentId]),
          id: parentId
        });
      }
    }

    return {
      type: "accepted",
      moduleId: updateModuleId,
      outdatedModules: outdatedModules,
      outdatedDependencies: outdatedDependencies
    };
  }

获取b.js模块的父模块,并设置到outdatedDependencies:

{
    "./src/index.js": [
        "./src/b.js"
    ]
}

然后该方法会判断module.hot的类型,如果修改的是入口文件的话,那么就走到module.hot._main。如果直接修改b.js文件,那么就会走到最后一步。由于只修改了b模块所以最后getAffectedModuleEffects返回了一个type为accepted的对象:

{
    "type": "accepted",
    "moduleId": "./src/b.js",
    "outdatedModules": [
        "./src/b.js"
    ],
    "outdatedDependencies": {
        "./src/index.js": [
            "./src/b.js"
        ]
    }
}

执行完applyHandler最终返回的是带有apply和dispose方法的对象。此时回到internalApply方法,继续执行设置状态为 setStatus(“dispose”),并执行刚刚返回的对象的dispose方法result.dispose()。接着继续开始执行result.apply方法。apply方法用新拉取的模块b.js替换了__webpack_require__.m模块里面的旧b.js文件。并调用currentUpdateRuntime方法设置__webpack_require__.h的下一次更新的hash值。还获取了module.hot._acceptedDependencies[dependency]方法,该方法是我们在index.js文件中定义的:

module.hot.accept(['./b.js'], function () {
    c()
  })

接下来循环callbacks,并执行callback,callback函数如下:

__WEBPACK_OUTDATED_DEPENDENCIES__ => { /* harmony import */ _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b.js */ "./src/b.js");
(function () {
    (0,_b__WEBPACK_IMPORTED_MODULE_0__.c)()
  })(__WEBPACK_OUTDATED_DEPENDENCIES__); }

你是不是以为会先执行__webpack_require__方法,其实不然,因为在第一次导入b模块的时候就已经执行过执行__webpack_require__方法,该方法中的__webpack_require__.i.forEach(function (handler) { handler(execOptions); })方法会用该方法的fn替换__webpack_require__。再次使用__webpack_require__获取该模块自然调用的就是被替换的createRequire里面fn方法。然后执行require(request)获取模块,由于require里面获取了__webpack_require__的引用,所以会执行__webpack_require__方法。然后执行__webpack_require__.i数组里的方法,数组里的内容为:

__webpack_require__.i.push(function (options) {
      var module = options.module;
      var require = createRequire(options.require, options.id);
      module.hot = createModuleHotObject(options.id, module);
      module.parents = currentParents;
      module.children = [];
      currentParents = [];
      options.require = require;
    });

最后执行execOptions.factory方法,该方法是修改后的b模块内容。最后执行accept的回调函数。执行完这些后回到internalApply方法执行Promise.all方法setStatus(“idle”)并返回outdatedModules。此时又回到dev-server.js文件module.hot.check(true)执行完毕,开始执行then方法打印一些日志,至此整个流程执行完毕。

你可能感兴趣的:(webpack,javascript,前端)