Skip to content

Commit

Permalink
Allow HMR status handlers to return a Promise
Browse files Browse the repository at this point in the history
The HMR system will wait until the promise settles before continuing.
  • Loading branch information
rockwalrus committed Jun 16, 2021
1 parent e9f9045 commit e852415
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 86 deletions.
164 changes: 84 additions & 80 deletions lib/hmr/HotModuleReplacement.runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var $interceptModuleExecution$ = undefined;
var $moduleCache$ = undefined;
// eslint-disable-next-line no-unused-vars
var $hmrModuleData$ = undefined;
/** @type {() => Promise} */
var $hmrDownloadManifest$ = undefined;
var $hmrDownloadUpdateHandlers$ = undefined;
var $hmrInvalidateModuleHandlers$ = undefined;
Expand Down Expand Up @@ -209,8 +210,12 @@ module.exports = function () {

function setStatus(newStatus) {
currentStatus = newStatus;
var results = [];

for (var i = 0; i < registeredStatusHandlers.length; i++)
registeredStatusHandlers[i].call(null, newStatus);
results[i] = registeredStatusHandlers[i].call(null, newStatus);

return Promise.all(results);
}

function trackBlockingPromise(promise) {
Expand All @@ -219,7 +224,7 @@ module.exports = function () {
setStatus("prepare");
blockingPromises.push(promise);
waitForBlockingPromises(function () {
setStatus("ready");
return setStatus("ready");
});
return promise;
case "prepare":
Expand All @@ -243,47 +248,47 @@ module.exports = function () {
if (currentStatus !== "idle") {
throw new Error("check() is only allowed in idle status");
}
setStatus("check");
return $hmrDownloadManifest$().then(function (update) {
if (!update) {
setStatus(applyInvalidatedModules() ? "ready" : "idle");
return null;
}

setStatus("prepare");

var updatedModules = [];
blockingPromises = [];
currentUpdateApplyHandlers = [];

return Promise.all(
Object.keys($hmrDownloadUpdateHandlers$).reduce(function (
promises,
key
) {
$hmrDownloadUpdateHandlers$[key](
update.c,
update.r,
update.m,
promises,
currentUpdateApplyHandlers,
updatedModules
);
return promises;
},
[])
).then(function () {
return waitForBlockingPromises(function () {
if (applyOnUpdate) {
return internalApply(applyOnUpdate);
} else {
setStatus("ready");
return setStatus("check")
.then($hmrDownloadManifest$)
.then(function (update) {
if (!update) {
return setStatus(applyInvalidatedModules() ? "ready" : "idle");
}

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

return Promise.all(
Object.keys($hmrDownloadUpdateHandlers$).reduce(function (
promises,
key
) {
$hmrDownloadUpdateHandlers$[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;
});
}
});
});
});
});
});
}

function hotApply(options) {
Expand Down Expand Up @@ -312,58 +317,57 @@ module.exports = function () {
.filter(Boolean);

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

// Now in "dispose" phase
setStatus("dispose");

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

// Now in "apply" phase
setStatus("apply");

var error;
var reportError = function (err) {
if (!error) error = err;
};
return setStatus("dispose").then(function () {
results.forEach(function (result) {
if (result.dispose) result.dispose();
});

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]);
// Now in "apply" phase
return setStatus("apply").then(function () {
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]);
}
}
}
});

// handle errors in accept handlers and self accepted module load
if (error) {
return setStatus("fail").then(function () {
throw error;
});
}
}
});

// handle errors in accept handlers and self accepted module load
if (error) {
setStatus("fail");
return Promise.resolve().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;
});
}

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

setStatus("idle");
return Promise.resolve(outdatedModules);
});
}

function applyInvalidatedModules() {
Expand Down
3 changes: 2 additions & 1 deletion test/HotTestCases.template.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ const describeCases = config => {
return JSON.parse(fs.readFileSync(p, "utf-8"));
} else {
const fn = vm.runInThisContext(
"(function(require, module, exports, __dirname, __filename, it, beforeEach, afterEach, expect, self, window, fetch, document, importScripts, Worker, EventSource, NEXT, STATS) {" +
"(function(require, module, exports, __dirname, __filename, it, beforeEach, afterEach, expect, jest, self, window, fetch, document, importScripts, Worker, EventSource, NEXT, STATS) {" +
"global.expect = expect;" +
'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' +
fs.readFileSync(p, "utf-8") +
Expand All @@ -271,6 +271,7 @@ const describeCases = config => {
_beforeEach,
_afterEach,
expect,
jest,
window,
window,
window.fetch,
Expand Down
11 changes: 6 additions & 5 deletions test/hotCases/invalidate/during-idle/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ it("should allow to invalidate and reload a file", () => {
expect(module.hot.status()).toBe("ready");
c.invalidate();
expect(module.hot.status()).toBe("ready");
module.hot.apply();
expect(module.hot.status()).toBe("idle");
expect(a.value).not.toBe(oldA);
expect(b.value).not.toBe(oldB);
expect(c.value).toBe(oldC);
module.hot.apply().then(function () {
expect(module.hot.status()).toBe("idle");
expect(a.value).not.toBe(oldA);
expect(b.value).not.toBe(oldB);
expect(c.value).toBe(oldC);
});
});
3 changes: 3 additions & 0 deletions test/hotCases/status/accept/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = 1;
---
module.exports = 2;
21 changes: 21 additions & 0 deletions test/hotCases/status/accept/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
var value = require("./file");

it("should wait until promises returned by status handlers are fulfilled", (done) => {
var handler = jest.fn(status => {
return Promise.resolve().then(() => {
expect(status).toBe(module.hot.status());
});
});
module.hot.addStatusHandler(handler);
module.hot.accept("./file", () => {
value = require("./file");
done();
});
NEXT(require("../../update")(done, undefined, () => {
expect(module.hot.status()).toBe("idle");

expect(handler.mock.calls).toBe([['check'], ['prepare'], ['dispose'], ['apply'], ['idle']]);
done();
}));
});

0 comments on commit e852415

Please sign in to comment.