fix egde case where a HMR chunk is incorrectly downloaded when loading a unchanged chunk during HMR downloading

This commit is contained in:
Tobias Koppers 2022-03-25 15:16:04 +01:00
parent 86a8bd9618
commit e1179bf9bb
6 changed files with 49 additions and 21 deletions

View File

@ -28,7 +28,8 @@ module.exports = function () {
var currentStatus = "idle"; var currentStatus = "idle";
// while downloading // while downloading
var blockingPromises; var blockingPromises = 0;
var blockingPromisesWaiting = [];
// The update info // The update info
var currentUpdateApplyHandlers; var currentUpdateApplyHandlers;
@ -218,17 +219,28 @@ module.exports = function () {
return Promise.all(results); return Promise.all(results);
} }
function unblock() {
if (--blockingPromises === 0) {
setStatus("ready").then(function () {
if (blockingPromises === 0) {
var list = blockingPromisesWaiting;
blockingPromisesWaiting = [];
for (var i = 0; i < list.length; i++) {
list[i]();
}
}
});
}
}
function trackBlockingPromise(promise) { function trackBlockingPromise(promise) {
switch (currentStatus) { switch (currentStatus) {
case "ready": case "ready":
setStatus("prepare"); setStatus("prepare");
blockingPromises.push(promise); /* fallthrough */
waitForBlockingPromises(function () {
return setStatus("ready");
});
return promise;
case "prepare": case "prepare":
blockingPromises.push(promise); blockingPromises++;
promise.then(unblock, unblock);
return promise; return promise;
default: default:
return promise; return promise;
@ -236,11 +248,11 @@ module.exports = function () {
} }
function waitForBlockingPromises(fn) { function waitForBlockingPromises(fn) {
if (blockingPromises.length === 0) return fn(); if (blockingPromises === 0) return fn();
var blocker = blockingPromises; return new Promise(function (resolve) {
blockingPromises = []; blockingPromisesWaiting.push(function () {
return Promise.all(blocker).then(function () { resolve(fn());
return waitForBlockingPromises(fn); });
}); });
} }
@ -261,7 +273,6 @@ module.exports = function () {
return setStatus("prepare").then(function () { return setStatus("prepare").then(function () {
var updatedModules = []; var updatedModules = [];
blockingPromises = [];
currentUpdateApplyHandlers = []; currentUpdateApplyHandlers = [];
return Promise.all( return Promise.all(
@ -298,7 +309,11 @@ module.exports = function () {
function hotApply(options) { function hotApply(options) {
if (currentStatus !== "ready") { if (currentStatus !== "ready") {
return Promise.resolve().then(function () { return Promise.resolve().then(function () {
throw new Error("apply() is only allowed in ready status"); throw new Error(
"apply() is only allowed in ready status (state: " +
currentStatus +
")"
);
}); });
} }
return internalApply(options); return internalApply(options);

View File

@ -443,15 +443,16 @@ module.exports = function () {
) { ) {
promises.push($loadUpdateChunk$(chunkId, updatedModulesList)); promises.push($loadUpdateChunk$(chunkId, updatedModulesList));
currentUpdateChunks[chunkId] = true; currentUpdateChunks[chunkId] = true;
} else {
currentUpdateChunks[chunkId] = false;
} }
}); });
if ($ensureChunkHandlers$) { if ($ensureChunkHandlers$) {
$ensureChunkHandlers$.$key$Hmr = function (chunkId, promises) { $ensureChunkHandlers$.$key$Hmr = function (chunkId, promises) {
if ( if (
currentUpdateChunks && currentUpdateChunks &&
!$hasOwnProperty$(currentUpdateChunks, chunkId) && $hasOwnProperty$(currentUpdateChunks, chunkId) &&
$hasOwnProperty$($installedChunks$, chunkId) && !currentUpdateChunks[chunkId]
$installedChunks$[chunkId] !== undefined
) { ) {
promises.push($loadUpdateChunk$(chunkId)); promises.push($loadUpdateChunk$(chunkId));
currentUpdateChunks[chunkId] = true; currentUpdateChunks[chunkId] = true;

View File

@ -286,8 +286,9 @@ class JsonpChunkLoadingRuntimeModule extends RuntimeModule {
? Template.asString([ ? Template.asString([
"var currentUpdatedModulesList;", "var currentUpdatedModulesList;",
"var waitingUpdateResolves = {};", "var waitingUpdateResolves = {};",
"function loadUpdateChunk(chunkId) {", "function loadUpdateChunk(chunkId, updatedModulesList) {",
Template.indent([ Template.indent([
"currentUpdatedModulesList = updatedModulesList;",
`return new Promise(${runtimeTemplate.basicFunction( `return new Promise(${runtimeTemplate.basicFunction(
"resolve, reject", "resolve, reject",
[ [

View File

@ -5,22 +5,27 @@ module.hot.accept("./file");
const asyncNext = () => { const asyncNext = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
NEXT((err, stats) => { NEXT((err, stats) => {
if(err) return reject(err); if (err) return reject(err);
resolve(stats); resolve(stats);
}); });
}); });
} };
it("should download the missing update chunk on import", () => { it("should download the missing update chunk on import", () => {
expect(value).toBe(1); expect(value).toBe(1);
return asyncNext().then(() => { return asyncNext().then(() => {
return module.hot.check().then(() => { return module.hot.check().then(() => {
return import("./chunk").then(chunk => { return Promise.all([
import("./chunk"),
import("./unaffected-chunk")
]).then(([chunk, unaffectedChunk]) => {
expect(value).toBe(1); expect(value).toBe(1);
expect(chunk.default).toBe(10); expect(chunk.default).toBe(10);
expect(unaffectedChunk.default).toBe(10);
return module.hot.apply().then(() => { return module.hot.apply().then(() => {
expect(value).toBe(2); expect(value).toBe(2);
expect(chunk.default).toBe(20); expect(chunk.default).toBe(20);
expect(unaffectedChunk.default).toBe(10);
}); });
}); });
}); });

View File

@ -0,0 +1,5 @@
import value from "./unaffected-inner";
module.hot.accept("./unaffected-inner");
export { value as default };

View File

@ -0,0 +1 @@
export default 10;