Skip to content

Commit

Permalink
add invalidate method to HMR
Browse files Browse the repository at this point in the history
  • Loading branch information
sokra committed Apr 14, 2020
1 parent 4c644bf commit a53bb8f
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 6 deletions.
76 changes: 70 additions & 6 deletions lib/HotModuleReplacement.runtime.js
Expand Up @@ -109,6 +109,7 @@ module.exports = function() {
_declinedDependencies: {},
_selfAccepted: false,
_selfDeclined: false,
_selfInvalidated: false,
_disposeHandlers: [],
_main: hotCurrentChildModule !== moduleId,

Expand Down Expand Up @@ -139,6 +140,29 @@ module.exports = function() {
var idx = hot._disposeHandlers.indexOf(callback);
if (idx >= 0) hot._disposeHandlers.splice(idx, 1);
},
invalidate: function() {
this._selfInvalidated = true;
switch (hotStatus) {
case "idle":
hotUpdate = {};
hotUpdate[moduleId] = modules[moduleId];
hotSetStatus("ready");
break;
case "ready":
hotApplyInvalidatedModule(moduleId);
break;
case "prepare":
case "check":
case "dispose":
case "apply":
(hotQueuedInvalidatedModules =
hotQueuedInvalidatedModules || []).push(moduleId);
break;
default:
// ignore requests in error states
break;
}
},

// Management API
check: hotCheck,
Expand Down Expand Up @@ -180,7 +204,7 @@ module.exports = function() {
var hotDeferred;

// The update info
var hotUpdate, hotUpdateNewHash;
var hotUpdate, hotUpdateNewHash, hotQueuedInvalidatedModules;

function toModuleId(id) {
var isNumber = +id + "" === id;
Expand All @@ -195,7 +219,7 @@ module.exports = function() {
hotSetStatus("check");
return hotDownloadManifest(hotRequestTimeout).then(function(update) {
if (!update) {
hotSetStatus("idle");
hotSetStatus(hotApplyInvalidatedModules() ? "ready" : "idle");
return null;
}
hotRequestedFilesMap = {};
Expand Down Expand Up @@ -288,6 +312,11 @@ module.exports = function() {
if (hotStatus !== "ready")
throw new Error("apply() is only allowed in ready status");
options = options || {};
return hotApplyInternal(options);
}

function hotApplyInternal(options) {
hotApplyInvalidatedModules();

var cb;
var i;
Expand All @@ -310,7 +339,11 @@ module.exports = function() {
var moduleId = queueItem.id;
var chain = queueItem.chain;
module = installedModules[moduleId];
if (!module || module.hot._selfAccepted) continue;
if (
!module ||
(module.hot._selfAccepted && !module.hot._selfInvalidated)
)
continue;
if (module.hot._selfDeclined) {
return {
type: "self-declined",
Expand Down Expand Up @@ -478,10 +511,13 @@ module.exports = function() {
installedModules[moduleId] &&
installedModules[moduleId].hot._selfAccepted &&
// removed self-accepted modules should not be required
appliedUpdate[moduleId] !== warnUnexpectedRequire
appliedUpdate[moduleId] !== warnUnexpectedRequire &&
// when called invalidate self-accepting is not possible
!installedModules[moduleId].hot._selfInvalidated
) {
outdatedSelfAcceptedModules.push({
module: moduleId,
parents: installedModules[moduleId].parents.slice(),
errorHandler: installedModules[moduleId].hot._selfAccepted
});
}
Expand Down Expand Up @@ -554,7 +590,11 @@ module.exports = function() {
// Now in "apply" phase
hotSetStatus("apply");

hotCurrentHash = hotUpdateNewHash;
if (hotUpdateNewHash !== undefined) {
hotCurrentHash = hotUpdateNewHash;
hotUpdateNewHash = undefined;
}
hotUpdate = undefined;

// insert new code
for (moduleId in appliedUpdate) {
Expand Down Expand Up @@ -607,7 +647,8 @@ module.exports = function() {
for (i = 0; i < outdatedSelfAcceptedModules.length; i++) {
var item = outdatedSelfAcceptedModules[i];
moduleId = item.module;
hotCurrentParents = [moduleId];
hotCurrentParents = item.parents;
hotCurrentChildModule = moduleId;
try {
$require$(moduleId);
} catch (err) {
Expand Down Expand Up @@ -649,9 +690,32 @@ module.exports = function() {
return Promise.reject(error);
}

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

hotSetStatus("idle");
return new Promise(function(resolve) {
resolve(outdatedModules);
});
}

function hotApplyInvalidatedModules() {
if (hotQueuedInvalidatedModules) {
if (!hotUpdate) hotUpdate = {};
hotQueuedInvalidatedModules.forEach(hotApplyInvalidatedModule);
hotQueuedInvalidatedModules = undefined;
return true;
}
}

function hotApplyInvalidatedModule(moduleId) {
if (!Object.prototype.hasOwnProperty.call(hotUpdate, moduleId))
hotUpdate[moduleId] = modules[moduleId];
}
};
7 changes: 7 additions & 0 deletions test/hotCases/invalidate/conditional-accept/data.json
@@ -0,0 +1,7 @@
{ "a": 1, "b": 1 }
---
{ "a": 2, "b": 1 }
---
{ "a": 2, "b": 2 }
---
{ "a": 3, "b": 3 }
48 changes: 48 additions & 0 deletions test/hotCases/invalidate/conditional-accept/index.js
@@ -0,0 +1,48 @@
import "./data.json";
import mod1 from "./module1";
import mod2 from "./module2";
import { value1, value2 } from "./store";

it("should invalidate a self-accepted module", function(done) {
expect(mod1).toBe(1);
expect(mod2).toBe(1);
expect(value1).toBe(1);
expect(value2).toBe(1);
let step = 0;
module.hot.accept("./module1");
module.hot.accept("./module2");
module.hot.accept("./data.json", () =>
setTimeout(() => {
switch (step) {
case 0:
step++;
expect(mod1).toBe(1);
expect(mod2).toBe(1);
expect(value1).toBe(2);
expect(value2).toBe(2);
NEXT(require("../../update")(done));
break;
case 1:
step++;
expect(mod1).toBe(2);
expect(mod2).toBe(2);
expect(value1).toBe(2);
expect(value2).toBe(2);
NEXT(require("../../update")(done));
break;
case 2:
step++;
expect(mod1).toBe(3);
expect(mod2).toBe(3);
expect(value1).toBe(3);
expect(value2).toBe(3);
done();
break;
default:
done(new Error("should not happen"));
break;
}
}, 100)
);
NEXT(require("../../update")(done));
});
16 changes: 16 additions & 0 deletions test/hotCases/invalidate/conditional-accept/module1.js
@@ -0,0 +1,16 @@
import data from "./data.json";
import { setValue1 } from "./store";

setValue1(data.a);

export default data.b;

if (module.hot.data && module.hot.data.ok && module.hot.data.b !== data.b) {
module.hot.invalidate();
} else {
module.hot.dispose(d => {
d.ok = true;
d.b = data.b;
});
module.hot.accept();
}
16 changes: 16 additions & 0 deletions test/hotCases/invalidate/conditional-accept/module2.js
@@ -0,0 +1,16 @@
import data from "./data.json";
import { setValue2 } from "./store";

setValue2(data.a);

export default data.b;

const b = data.b;

module.hot.accept(["./data.json"], () => {
if (data.b !== b) {
module.hot.invalidate();
return;
}
setValue2(data.a);
});
9 changes: 9 additions & 0 deletions test/hotCases/invalidate/conditional-accept/store.js
@@ -0,0 +1,9 @@
export let value1 = 0;
export function setValue1(v) {
value1 = v;
}

export let value2 = 0;
export function setValue2(v) {
value2 = v;
}
5 changes: 5 additions & 0 deletions test/hotCases/invalidate/during-idle/a.js
@@ -0,0 +1,5 @@
export function invalidate() {
module.hot.invalidate();
}

export const value = {};
7 changes: 7 additions & 0 deletions test/hotCases/invalidate/during-idle/b.js
@@ -0,0 +1,7 @@
export function invalidate() {
module.hot.invalidate();
}

export const value = {};

module.hot.accept();
11 changes: 11 additions & 0 deletions test/hotCases/invalidate/during-idle/c.js
@@ -0,0 +1,11 @@
export function invalidate() {
module.hot.invalidate();
}

export const value = module.hot.data ? module.hot.data.value : {};

module.hot.dispose(data => {
data.value = value;
});

module.hot.accept();
19 changes: 19 additions & 0 deletions test/hotCases/invalidate/during-idle/index.js
@@ -0,0 +1,19 @@
import { a, b, c } from "./module";

it("should allow to invalidate and reload a file", () => {
const oldA = a.value;
const oldB = b.value;
const oldC = c.value;
expect(module.hot.status()).toBe("idle");
a.invalidate();
expect(module.hot.status()).toBe("ready");
b.invalidate();
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);
});
7 changes: 7 additions & 0 deletions test/hotCases/invalidate/during-idle/module.js
@@ -0,0 +1,7 @@
import * as a from "./a";
import * as b from "./b";
import * as c from "./c";

export { a, b, c };

module.hot.accept(["./a", "./b", "./c"]);

0 comments on commit a53bb8f

Please sign in to comment.