配置如下:
入口文件为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);
}
我先说下热刷新的基本流程,让大家有个概念:
要想实现热刷新必须明确一下几点:
- webpack要监听文件修改
- 获取最新的hash值发给浏览器
- 浏览器使用hash值发起请求获取最新的文件
- 服务端拦截浏览器的请求,并从内存获取内容返回给浏览器
- 用动态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方法打印一些日志,至此整个流程执行完毕。