html-bundle 6.4.0 → 6.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hmr-client.js +21 -1
- package/package.json +1 -1
- package/src/hmr-client.ts +20 -1
- package/tests/hmr-client.test.mjs +26 -0
package/dist/hmr-client.js
CHANGED
|
@@ -20,7 +20,27 @@ const ID = "__HMR_ID__";
|
|
|
20
20
|
const SRC = "__HMR_SRC__";
|
|
21
21
|
const REGION = '[data-hmr="__HMR_ID__"]';
|
|
22
22
|
const CLIENT = 'script[data-hmr-client="__HMR_ID__"]';
|
|
23
|
-
|
|
23
|
+
// The client is injected as the first <head> module so its hub and public API
|
|
24
|
+
// exist before the page's own scripts run. But `window.isHMR` must NOT be
|
|
25
|
+
// observable while those scripts execute for the first time: user code commonly
|
|
26
|
+
// branches on it to run one-time setup exactly once (e.g.
|
|
27
|
+
// `if (!window.isHMR) createRouter()`), relying on the flag being false on the
|
|
28
|
+
// pristine load and true on every hot re-run afterwards. Assigning it eagerly
|
|
29
|
+
// here runs before the page scripts and breaks that contract, leaving one-time
|
|
30
|
+
// init skipped (e.g. an SPA router never mounts, so its outlet stays empty).
|
|
31
|
+
// Flag it only after the initial module scripts have executed:
|
|
32
|
+
// - initial load: readyState is "interactive"; set it on DOMContentLoaded,
|
|
33
|
+
// which fires after the page's own deferred/module scripts.
|
|
34
|
+
// - hot re-render / composed page registered post-load: readyState is
|
|
35
|
+
// "complete"; set it immediately, since we are already past the first load.
|
|
36
|
+
if (document.readyState === "complete") {
|
|
37
|
+
window.isHMR = true;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
document.addEventListener("DOMContentLoaded", () => (window.isHMR = true), {
|
|
41
|
+
once: true,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
24
44
|
// One hub per browsing context, created by whichever page loads first. Every
|
|
25
45
|
// composed sub-page (index.html + fetched fragments) registers into it and stays
|
|
26
46
|
// live, so a single shared EventSource dispatches every file's changes to the
|
package/package.json
CHANGED
package/src/hmr-client.ts
CHANGED
|
@@ -72,7 +72,26 @@ const SRC = "__HMR_SRC__";
|
|
|
72
72
|
const REGION = '[data-hmr="__HMR_ID__"]';
|
|
73
73
|
const CLIENT = 'script[data-hmr-client="__HMR_ID__"]';
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
// The client is injected as the first <head> module so its hub and public API
|
|
76
|
+
// exist before the page's own scripts run. But `window.isHMR` must NOT be
|
|
77
|
+
// observable while those scripts execute for the first time: user code commonly
|
|
78
|
+
// branches on it to run one-time setup exactly once (e.g.
|
|
79
|
+
// `if (!window.isHMR) createRouter()`), relying on the flag being false on the
|
|
80
|
+
// pristine load and true on every hot re-run afterwards. Assigning it eagerly
|
|
81
|
+
// here runs before the page scripts and breaks that contract, leaving one-time
|
|
82
|
+
// init skipped (e.g. an SPA router never mounts, so its outlet stays empty).
|
|
83
|
+
// Flag it only after the initial module scripts have executed:
|
|
84
|
+
// - initial load: readyState is "interactive"; set it on DOMContentLoaded,
|
|
85
|
+
// which fires after the page's own deferred/module scripts.
|
|
86
|
+
// - hot re-render / composed page registered post-load: readyState is
|
|
87
|
+
// "complete"; set it immediately, since we are already past the first load.
|
|
88
|
+
if (document.readyState === "complete") {
|
|
89
|
+
window.isHMR = true;
|
|
90
|
+
} else {
|
|
91
|
+
document.addEventListener("DOMContentLoaded", () => (window.isHMR = true), {
|
|
92
|
+
once: true,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
76
95
|
|
|
77
96
|
// One hub per browsing context, created by whichever page loads first. Every
|
|
78
97
|
// composed sub-page (index.html + fetched fragments) registers into it and stays
|
|
@@ -93,6 +93,32 @@ test("shared hub patches a full-document title change in place", async () => {
|
|
|
93
93
|
assert.equal(document.querySelector("title").textContent, "After");
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
test("window.isHMR is hidden from the page's own initial scripts, then set for hot re-runs", async () => {
|
|
97
|
+
// Regression: the client is injected as the first <head> module so its hub and
|
|
98
|
+
// public API exist before the page's own scripts. It must NOT flip
|
|
99
|
+
// window.isHMR before those scripts run, or one-time guards such as
|
|
100
|
+
// `if (!window.isHMR) createRouter()` get skipped on the pristine load — the
|
|
101
|
+
// symptom being an SPA whose outlet never mounts (an almost-empty page).
|
|
102
|
+
const { window, document, loadPage } = await setup();
|
|
103
|
+
assert.equal(document.readyState, "interactive"); // pristine load, still parsing
|
|
104
|
+
|
|
105
|
+
loadPage("src/index.html", "idx1");
|
|
106
|
+
assert.notEqual(
|
|
107
|
+
window.isHMR,
|
|
108
|
+
true,
|
|
109
|
+
"isHMR must stay falsy while the page's own scripts run",
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// DOMContentLoaded fires after those scripts; HMR mode is now observable so a
|
|
113
|
+
// subsequent hot re-execution can take the isHMR branch and skip re-init.
|
|
114
|
+
document.dispatchEvent(new window.Event("DOMContentLoaded"));
|
|
115
|
+
assert.equal(
|
|
116
|
+
window.isHMR,
|
|
117
|
+
true,
|
|
118
|
+
"isHMR is set once the initial load settles",
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
96
122
|
test("full-document insertion before a composed mount preserves fetched content", async () => {
|
|
97
123
|
const { document, loadPage, reloadCount } = await setup();
|
|
98
124
|
const before =
|