Allow HMR status handlers to return a Promise

The HMR system will wait until the promise settles before continuing.
This commit is contained in:
Nathan Summers 2021-06-11 15:01:35 -07:00
parent e9f90450d5
commit e852415cd5
5 changed files with 117 additions and 87 deletions

View File

@ -9,6 +9,7 @@ var $interceptModuleExecution$ = undefined;
var $moduleCache$ = undefined; var $moduleCache$ = undefined;
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
var $hmrModuleData$ = undefined; var $hmrModuleData$ = undefined;
/** @type {() => Promise} */
var $hmrDownloadManifest$ = undefined; var $hmrDownloadManifest$ = undefined;
var $hmrDownloadUpdateHandlers$ = undefined; var $hmrDownloadUpdateHandlers$ = undefined;
var $hmrInvalidateModuleHandlers$ = undefined; var $hmrInvalidateModuleHandlers$ = undefined;
@ -209,8 +210,12 @@ module.exports = function () {
function setStatus(newStatus) { function setStatus(newStatus) {
currentStatus = newStatus; currentStatus = newStatus;
var results = [];
for (var i = 0; i < registeredStatusHandlers.length; i++) 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) { function trackBlockingPromise(promise) {
@ -219,7 +224,7 @@ module.exports = function () {
setStatus("prepare"); setStatus("prepare");
blockingPromises.push(promise); blockingPromises.push(promise);
waitForBlockingPromises(function () { waitForBlockingPromises(function () {
setStatus("ready"); return setStatus("ready");
}); });
return promise; return promise;
case "prepare": case "prepare":
@ -243,15 +248,14 @@ module.exports = function () {
if (currentStatus !== "idle") { if (currentStatus !== "idle") {
throw new Error("check() is only allowed in idle status"); throw new Error("check() is only allowed in idle status");
} }
setStatus("check"); return setStatus("check")
return $hmrDownloadManifest$().then(function (update) { .then($hmrDownloadManifest$)
.then(function (update) {
if (!update) { if (!update) {
setStatus(applyInvalidatedModules() ? "ready" : "idle"); return setStatus(applyInvalidatedModules() ? "ready" : "idle");
return null;
} }
setStatus("prepare"); return setStatus("prepare").then(function () {
var updatedModules = []; var updatedModules = [];
blockingPromises = []; blockingPromises = [];
currentUpdateApplyHandlers = []; currentUpdateApplyHandlers = [];
@ -277,13 +281,14 @@ module.exports = function () {
if (applyOnUpdate) { if (applyOnUpdate) {
return internalApply(applyOnUpdate); return internalApply(applyOnUpdate);
} else { } else {
setStatus("ready"); return setStatus("ready").then(function () {
return updatedModules; return updatedModules;
});
} }
}); });
}); });
}); });
});
} }
function hotApply(options) { function hotApply(options) {
@ -312,22 +317,19 @@ module.exports = function () {
.filter(Boolean); .filter(Boolean);
if (errors.length > 0) { if (errors.length > 0) {
setStatus("abort"); return setStatus("abort").then(function () {
return Promise.resolve().then(function () {
throw errors[0]; throw errors[0];
}); });
} }
// Now in "dispose" phase // Now in "dispose" phase
setStatus("dispose"); return setStatus("dispose").then(function () {
results.forEach(function (result) { results.forEach(function (result) {
if (result.dispose) result.dispose(); if (result.dispose) result.dispose();
}); });
// Now in "apply" phase // Now in "apply" phase
setStatus("apply"); return setStatus("apply").then(function () {
var error; var error;
var reportError = function (err) { var reportError = function (err) {
if (!error) error = err; if (!error) error = err;
@ -347,8 +349,7 @@ module.exports = function () {
// handle errors in accept handlers and self accepted module load // handle errors in accept handlers and self accepted module load
if (error) { if (error) {
setStatus("fail"); return setStatus("fail").then(function () {
return Promise.resolve().then(function () {
throw error; throw error;
}); });
} }
@ -362,8 +363,11 @@ module.exports = function () {
}); });
} }
setStatus("idle"); return setStatus("idle").then(function () {
return Promise.resolve(outdatedModules); return outdatedModules;
});
});
});
} }
function applyInvalidatedModules() { function applyInvalidatedModules() {

View File

@ -250,7 +250,7 @@ const describeCases = config => {
return JSON.parse(fs.readFileSync(p, "utf-8")); return JSON.parse(fs.readFileSync(p, "utf-8"));
} else { } else {
const fn = vm.runInThisContext( 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;" + "global.expect = expect;" +
'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' + 'function nsObj(m) { Object.defineProperty(m, Symbol.toStringTag, { value: "Module" }); return m; }' +
fs.readFileSync(p, "utf-8") + fs.readFileSync(p, "utf-8") +
@ -271,6 +271,7 @@ const describeCases = config => {
_beforeEach, _beforeEach,
_afterEach, _afterEach,
expect, expect,
jest,
window, window,
window, window,
window.fetch, window.fetch,

View File

@ -11,9 +11,10 @@ it("should allow to invalidate and reload a file", () => {
expect(module.hot.status()).toBe("ready"); expect(module.hot.status()).toBe("ready");
c.invalidate(); c.invalidate();
expect(module.hot.status()).toBe("ready"); expect(module.hot.status()).toBe("ready");
module.hot.apply(); module.hot.apply().then(function () {
expect(module.hot.status()).toBe("idle"); expect(module.hot.status()).toBe("idle");
expect(a.value).not.toBe(oldA); expect(a.value).not.toBe(oldA);
expect(b.value).not.toBe(oldB); expect(b.value).not.toBe(oldB);
expect(c.value).toBe(oldC); expect(c.value).toBe(oldC);
});
}); });

View File

@ -0,0 +1,3 @@
module.exports = 1;
---
module.exports = 2;

View File

@ -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();
}));
});