html-bundle 6.3.1 → 6.4.0
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/.github/dependabot.yml +5 -5
- package/README.md +34 -3
- package/dist/bundle.mjs +69 -18
- package/dist/hmr-client.d.ts +40 -0
- package/dist/hmr-client.js +446 -0
- package/dist/utils.d.mts +14 -2
- package/dist/utils.mjs +45 -178
- package/package.json +3 -1
- package/src/bundle.mts +67 -18
- package/src/hmr-client.ts +577 -0
- package/src/utils.mts +61 -186
- package/tests/bundle.test.mjs +62 -10
- package/tests/hmr-client.test.mjs +245 -0
- package/tests/hmr-server.test.mjs +175 -0
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// html-bundle HMR client runtime.
|
|
2
|
+
//
|
|
3
|
+
// This is a browser TypeScript module. The DOM lib is enabled in tsconfig.json,
|
|
4
|
+
// so the main `tsc` build type-checks it alongside the Node code and compiles it
|
|
5
|
+
// to dist/hmr-client.js next to this module's output. At inject time
|
|
6
|
+
// `buildHMRClient()` in utils.mts substitutes the `__HMR_*` tokens, then the
|
|
7
|
+
// result is injected as an inline `<script type="module">` into every built HTML
|
|
8
|
+
// page and bundled by esbuild so the `hydro-js` import resolves. Keeping it as a
|
|
9
|
+
// real file (instead of a generated template string) means backslashes/backticks
|
|
10
|
+
// are literal, avoiding the template-literal escape corruption a generated string
|
|
11
|
+
// is prone to.
|
|
12
|
+
import { render as hydroRender, html, setShouldSetReactivity } from "hydro-js";
|
|
13
|
+
// hydro-js's `render` is typed against its own `html()` output; this client
|
|
14
|
+
// feeds it arbitrary DOM nodes and uses `false` for `where` (a detached render),
|
|
15
|
+
// so widen the signature. The assertion is type-only — esbuild strips it and the
|
|
16
|
+
// runtime binding stays hydro-js's `render`.
|
|
17
|
+
const render = hydroRender;
|
|
18
|
+
const FILE = "__HMR_FILE__";
|
|
19
|
+
const ID = "__HMR_ID__";
|
|
20
|
+
const SRC = "__HMR_SRC__";
|
|
21
|
+
const REGION = '[data-hmr="__HMR_ID__"]';
|
|
22
|
+
const CLIENT = 'script[data-hmr-client="__HMR_ID__"]';
|
|
23
|
+
window.isHMR = true;
|
|
24
|
+
// One hub per browsing context, created by whichever page loads first. Every
|
|
25
|
+
// composed sub-page (index.html + fetched fragments) registers into it and stays
|
|
26
|
+
// live, so a single shared EventSource dispatches every file's changes to the
|
|
27
|
+
// right region — instead of each page fighting over one connection.
|
|
28
|
+
const hub = window.__htmlBundleHMR || (window.__htmlBundleHMR = createHub(SRC));
|
|
29
|
+
hub.currentUnit = FILE;
|
|
30
|
+
if (!window.htmlBundleHMR) {
|
|
31
|
+
// Public opt-in API for user code running inside HMR-managed pages.
|
|
32
|
+
// window.htmlBundleHMR.dispose(cb) — cleanup before this unit re-runs
|
|
33
|
+
// window.htmlBundleHMR.accept(cb) — hook after this unit is patched
|
|
34
|
+
// window.htmlBundleHMR.data — object persisted across hot updates
|
|
35
|
+
window.htmlBundleHMR = {
|
|
36
|
+
accept(callback) {
|
|
37
|
+
hub.addAccept(hub.currentUnit, callback);
|
|
38
|
+
},
|
|
39
|
+
dispose(callback) {
|
|
40
|
+
hub.addDispose(hub.currentUnit, callback);
|
|
41
|
+
},
|
|
42
|
+
get data() {
|
|
43
|
+
return hub.dataFor(hub.currentUnit);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
hub.register(FILE, ID, { patch });
|
|
48
|
+
// --- per-page patching (closes over this page's own hydro-js instance) --------
|
|
49
|
+
function patch(message) {
|
|
50
|
+
const previousScroll = window.scrollY;
|
|
51
|
+
if (isFullDocument(message.html)) {
|
|
52
|
+
patchDocument(message.previousHtml, message.html);
|
|
53
|
+
if (FILE === SRC + "/index.html") {
|
|
54
|
+
dispatchEvent(new Event("popstate"));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
patchFragment(message.html);
|
|
59
|
+
}
|
|
60
|
+
window.scrollTo(0, previousScroll);
|
|
61
|
+
}
|
|
62
|
+
function patchFragment(htmlText) {
|
|
63
|
+
const incoming = parseHTML(htmlText);
|
|
64
|
+
// hydro-js returns a DocumentFragment for multi-root markup but the element
|
|
65
|
+
// itself for a single root — normalise to a flat list of top-level nodes.
|
|
66
|
+
const nextNodes = incoming.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
|
|
67
|
+
incoming.nodeType === Node.DOCUMENT_NODE
|
|
68
|
+
? Array.from(incoming.childNodes)
|
|
69
|
+
: [incoming];
|
|
70
|
+
const regions = Array.from(document.querySelectorAll(REGION));
|
|
71
|
+
// Replace each existing region in place, drop the surplus, append the rest.
|
|
72
|
+
regions.forEach((where, index) => {
|
|
73
|
+
if (index < nextNodes.length) {
|
|
74
|
+
render(nextNodes[index], where, false);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
where.remove();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
for (let rest = regions.length; rest < nextNodes.length; rest++) {
|
|
81
|
+
if (regions.length) {
|
|
82
|
+
const template = document.createElement("template");
|
|
83
|
+
nextNodes[regions.length - 1].after(template);
|
|
84
|
+
render(nextNodes[rest], template, false);
|
|
85
|
+
template.remove();
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
render(nextNodes[rest], false, false);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function isFullDocument(htmlText) {
|
|
93
|
+
const trimmed = htmlText.trimStart().toLowerCase();
|
|
94
|
+
return trimmed.startsWith("<!doctype html") || trimmed.startsWith("<html");
|
|
95
|
+
}
|
|
96
|
+
function parseHTML(htmlText) {
|
|
97
|
+
try {
|
|
98
|
+
return html `${htmlText}`;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
setShouldSetReactivity(false);
|
|
102
|
+
const parsed = html `${htmlText}`;
|
|
103
|
+
setShouldSetReactivity(true);
|
|
104
|
+
return parsed;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function patchDocument(previousHtml, nextHtml) {
|
|
108
|
+
const previousText = previousHtml ||
|
|
109
|
+
hub.lastHTML.get(FILE) ||
|
|
110
|
+
document.documentElement.outerHTML;
|
|
111
|
+
const previousDocument = getDocumentParts(parseHTML(previousText));
|
|
112
|
+
const nextDocument = getDocumentParts(parseHTML(nextHtml));
|
|
113
|
+
if (!previousDocument.html || !nextDocument.html) {
|
|
114
|
+
render(parseHTML(nextHtml), document.documentElement, false);
|
|
115
|
+
hub.lastHTML.set(FILE, nextHtml);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
patchAttributes(document.documentElement, nextDocument.html);
|
|
119
|
+
if (previousDocument.head && nextDocument.head && document.head) {
|
|
120
|
+
patchChildren(previousDocument.head, nextDocument.head, document.head);
|
|
121
|
+
patchAttributes(document.head, nextDocument.head);
|
|
122
|
+
}
|
|
123
|
+
if (previousDocument.body && nextDocument.body && document.body) {
|
|
124
|
+
patchChildren(previousDocument.body, nextDocument.body, document.body);
|
|
125
|
+
patchAttributes(document.body, nextDocument.body);
|
|
126
|
+
}
|
|
127
|
+
hub.lastHTML.set(FILE, nextHtml);
|
|
128
|
+
}
|
|
129
|
+
function getDocumentParts(parsed) {
|
|
130
|
+
const htmlNode = (parsed instanceof HTMLHtmlElement
|
|
131
|
+
? parsed
|
|
132
|
+
: parsed.querySelector?.("html") || parsed);
|
|
133
|
+
return {
|
|
134
|
+
html: htmlNode,
|
|
135
|
+
head: htmlNode.querySelector?.("head"),
|
|
136
|
+
body: htmlNode.querySelector?.("body"),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function patchChildren(previousParent, nextParent, liveParent) {
|
|
140
|
+
const previousNodes = comparableNodes(previousParent);
|
|
141
|
+
const nextNodes = comparableNodes(nextParent);
|
|
142
|
+
let previousIndex = 0;
|
|
143
|
+
let nextIndex = 0;
|
|
144
|
+
let liveIndex = 0;
|
|
145
|
+
while (nextIndex < nextNodes.length) {
|
|
146
|
+
const previousNode = previousNodes[previousIndex];
|
|
147
|
+
const nextNode = nextNodes[nextIndex];
|
|
148
|
+
const liveNode = nextLiveNode(liveParent, liveIndex);
|
|
149
|
+
if (!liveNode) {
|
|
150
|
+
liveParent.append(cloneForRender(nextNode));
|
|
151
|
+
nextIndex++;
|
|
152
|
+
liveIndex++;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (!previousNode) {
|
|
156
|
+
render(cloneForRender(nextNode), liveNode, false);
|
|
157
|
+
nextIndex++;
|
|
158
|
+
liveIndex++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const previousMatch = findStaticMatch(previousNodes, nextNode, previousIndex + 1);
|
|
162
|
+
const nextMatch = findStaticMatch(nextNodes, previousNode, nextIndex + 1);
|
|
163
|
+
if (previousMatch !== -1 && nextMatch === -1) {
|
|
164
|
+
liveNode.remove();
|
|
165
|
+
previousIndex++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (nextMatch !== -1) {
|
|
169
|
+
liveNode.before(cloneForRender(nextNode));
|
|
170
|
+
nextIndex++;
|
|
171
|
+
liveIndex++;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (sameNodeIdentity(previousNode, nextNode)) {
|
|
175
|
+
patchNode(previousNode, nextNode, liveNode);
|
|
176
|
+
previousIndex++;
|
|
177
|
+
nextIndex++;
|
|
178
|
+
liveIndex++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
render(cloneForRender(nextNode), liveNode, false);
|
|
182
|
+
previousIndex++;
|
|
183
|
+
nextIndex++;
|
|
184
|
+
liveIndex++;
|
|
185
|
+
}
|
|
186
|
+
while (previousIndex < previousNodes.length &&
|
|
187
|
+
nextLiveNode(liveParent, liveIndex)) {
|
|
188
|
+
nextLiveNode(liveParent, liveIndex).remove();
|
|
189
|
+
previousIndex++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function patchNode(previousNode, nextNode, liveNode) {
|
|
193
|
+
if (sameStaticNode(previousNode, nextNode))
|
|
194
|
+
return;
|
|
195
|
+
if (previousNode.nodeType !== nextNode.nodeType ||
|
|
196
|
+
liveNode.nodeType !== nextNode.nodeType) {
|
|
197
|
+
render(cloneForRender(nextNode), liveNode, false);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (nextNode.nodeType === Node.TEXT_NODE) {
|
|
201
|
+
liveNode.nodeValue = nextNode.nodeValue;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (nextNode.nodeType !== Node.ELEMENT_NODE) {
|
|
205
|
+
render(cloneForRender(nextNode), liveNode, false);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (nextNode.localName === "script") {
|
|
209
|
+
patchScript(previousNode, nextNode, liveNode);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
patchAttributes(liveNode, nextNode);
|
|
213
|
+
patchChildren(previousNode, nextNode, liveNode);
|
|
214
|
+
}
|
|
215
|
+
// Re-running only the scripts whose content actually changed is the "hot swap":
|
|
216
|
+
// unchanged scripts keep their state, changed ones re-execute with fresh code.
|
|
217
|
+
function patchScript(previousScript, nextScript, liveScript) {
|
|
218
|
+
if (previousScript.isEqualNode(nextScript))
|
|
219
|
+
return;
|
|
220
|
+
const clone = cloneScript(nextScript);
|
|
221
|
+
const source = clone.getAttribute("src");
|
|
222
|
+
if (source)
|
|
223
|
+
clone.setAttribute("src", bust(source));
|
|
224
|
+
render(clone, liveScript, false);
|
|
225
|
+
}
|
|
226
|
+
function cloneForRender(node) {
|
|
227
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
228
|
+
node.localName === "script") {
|
|
229
|
+
return cloneScript(node);
|
|
230
|
+
}
|
|
231
|
+
return node.cloneNode(true);
|
|
232
|
+
}
|
|
233
|
+
// Cloning via document.createElement forces the browser to (re-)execute the
|
|
234
|
+
// script; a plain cloneNode of a parsed <script> would not run.
|
|
235
|
+
function cloneScript(script) {
|
|
236
|
+
const clone = document.createElement("script");
|
|
237
|
+
for (const attr of Array.from(script.attributes)) {
|
|
238
|
+
clone.setAttribute(attr.name, attr.value);
|
|
239
|
+
}
|
|
240
|
+
clone.textContent = script.textContent;
|
|
241
|
+
return clone;
|
|
242
|
+
}
|
|
243
|
+
function patchAttributes(liveElement, nextElement) {
|
|
244
|
+
for (const attr of Array.from(liveElement.attributes)) {
|
|
245
|
+
if (!nextElement.hasAttribute(attr.name))
|
|
246
|
+
liveElement.removeAttribute(attr.name);
|
|
247
|
+
}
|
|
248
|
+
for (const attr of Array.from(nextElement.attributes)) {
|
|
249
|
+
if (liveElement.getAttribute(attr.name) !== attr.value) {
|
|
250
|
+
liveElement.setAttribute(attr.name, attr.value);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function sameStaticNode(previousNode, nextNode) {
|
|
255
|
+
return cloneComparable(previousNode).isEqualNode(cloneComparable(nextNode));
|
|
256
|
+
}
|
|
257
|
+
function sameNodeIdentity(previousNode, nextNode) {
|
|
258
|
+
return (previousNode.nodeType === nextNode.nodeType &&
|
|
259
|
+
previousNode.nodeName === nextNode.nodeName);
|
|
260
|
+
}
|
|
261
|
+
function findStaticMatch(nodes, needle, start) {
|
|
262
|
+
for (let index = start; index < nodes.length; index++) {
|
|
263
|
+
if (sameStaticNode(nodes[index], needle))
|
|
264
|
+
return index;
|
|
265
|
+
}
|
|
266
|
+
return -1;
|
|
267
|
+
}
|
|
268
|
+
// Ignore the injected HMR client script when diffing so it never counts as a
|
|
269
|
+
// real change.
|
|
270
|
+
function cloneComparable(node) {
|
|
271
|
+
const clone = node.cloneNode(true);
|
|
272
|
+
clone.querySelectorAll?.(CLIENT).forEach((script) => script.remove());
|
|
273
|
+
if (clone.matches?.(CLIENT))
|
|
274
|
+
clone.remove();
|
|
275
|
+
return clone;
|
|
276
|
+
}
|
|
277
|
+
function comparableNodes(parent) {
|
|
278
|
+
return Array.from(parent.childNodes).filter((node) => {
|
|
279
|
+
return !(node.nodeType === Node.ELEMENT_NODE && node.matches?.(CLIENT));
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function nextLiveNode(parent, index) {
|
|
283
|
+
return comparableNodes(parent)[index];
|
|
284
|
+
}
|
|
285
|
+
function bust(url) {
|
|
286
|
+
return url.split("?")[0] + "?v=" + Date.now();
|
|
287
|
+
}
|
|
288
|
+
// --- shared hub (created once by the first page to load) ----------------------
|
|
289
|
+
function createHub(src) {
|
|
290
|
+
const registry = new Map();
|
|
291
|
+
const accepts = new Map();
|
|
292
|
+
const disposers = new Map();
|
|
293
|
+
const store = new Map();
|
|
294
|
+
const throttle = new Map();
|
|
295
|
+
let source;
|
|
296
|
+
let reconnectTimer;
|
|
297
|
+
let reloadTimer;
|
|
298
|
+
let shouldReconnect = true;
|
|
299
|
+
const hub = {
|
|
300
|
+
currentUnit: null,
|
|
301
|
+
lastHTML: new Map(),
|
|
302
|
+
register(file, id, handler) {
|
|
303
|
+
registry.set(file, Object.assign({ id }, handler));
|
|
304
|
+
connect();
|
|
305
|
+
},
|
|
306
|
+
addAccept(file, callback) {
|
|
307
|
+
push(accepts, file, callback);
|
|
308
|
+
},
|
|
309
|
+
addDispose(file, callback) {
|
|
310
|
+
push(disposers, file, callback);
|
|
311
|
+
},
|
|
312
|
+
dataFor(file) {
|
|
313
|
+
if (!store.has(file))
|
|
314
|
+
store.set(file, {});
|
|
315
|
+
return store.get(file);
|
|
316
|
+
},
|
|
317
|
+
dispatch(message) {
|
|
318
|
+
if (message.type === "html") {
|
|
319
|
+
const entry = registry.get(message.file);
|
|
320
|
+
if (!entry)
|
|
321
|
+
return;
|
|
322
|
+
hub.currentUnit = message.file;
|
|
323
|
+
run(disposers, message.file);
|
|
324
|
+
try {
|
|
325
|
+
entry.patch(message);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
console.error("[html-bundle HMR] patch failed", error);
|
|
329
|
+
}
|
|
330
|
+
run(accepts, message.file);
|
|
331
|
+
}
|
|
332
|
+
else if (message.type === "css") {
|
|
333
|
+
once("css", bustStylesheets);
|
|
334
|
+
}
|
|
335
|
+
else if (message.type === "asset") {
|
|
336
|
+
once("asset:" + message.file, () => bustAsset(message.file));
|
|
337
|
+
}
|
|
338
|
+
else if (message.type === "full-reload") {
|
|
339
|
+
reloadPage();
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
function push(map, key, value) {
|
|
344
|
+
if (!map.has(key))
|
|
345
|
+
map.set(key, []);
|
|
346
|
+
map.get(key).push(value);
|
|
347
|
+
}
|
|
348
|
+
function run(map, key) {
|
|
349
|
+
const callbacks = map.get(key);
|
|
350
|
+
if (!callbacks || !callbacks.length)
|
|
351
|
+
return;
|
|
352
|
+
map.set(key, []);
|
|
353
|
+
for (const callback of callbacks) {
|
|
354
|
+
try {
|
|
355
|
+
callback();
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
console.error("[html-bundle HMR] callback failed", error);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Coalesce bursts of identical events (e.g. a save that touches many files).
|
|
363
|
+
function once(key, action) {
|
|
364
|
+
const now = performance.now();
|
|
365
|
+
if (!throttle.has(key) || now - throttle.get(key) > 100) {
|
|
366
|
+
throttle.set(key, now);
|
|
367
|
+
action();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function reloadPage() {
|
|
371
|
+
// Own timer (not reconnectTimer) so an SSE reconnect can't cancel a pending
|
|
372
|
+
// reload and vice versa.
|
|
373
|
+
clearTimeout(reloadTimer);
|
|
374
|
+
reloadTimer = setTimeout(() => window.location.reload(), 20);
|
|
375
|
+
}
|
|
376
|
+
function bustStylesheets() {
|
|
377
|
+
document
|
|
378
|
+
.querySelectorAll('link[rel="stylesheet"][href]')
|
|
379
|
+
.forEach((link) => {
|
|
380
|
+
const href = link.getAttribute("href").split("?")[0];
|
|
381
|
+
link.setAttribute("href", href + "?v=" + Date.now());
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function bustAsset(file) {
|
|
385
|
+
if (file.endsWith(".css")) {
|
|
386
|
+
bustStylesheets();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const prefix = src + "/";
|
|
390
|
+
const relative = file.indexOf(prefix) === 0 ? file.slice(prefix.length) : file;
|
|
391
|
+
const version = "?v=" + Date.now();
|
|
392
|
+
bustAttribute("img[src]", "src", relative, version);
|
|
393
|
+
bustAttribute("script[src]", "src", relative, version);
|
|
394
|
+
bustAttribute("link[href]", "href", relative, version);
|
|
395
|
+
bustContains("source[srcset]", "srcset", relative, version);
|
|
396
|
+
bustAttribute("img[data-src]", "data-src", relative, version);
|
|
397
|
+
}
|
|
398
|
+
function bustAttribute(selector, attribute, relative, version) {
|
|
399
|
+
document.querySelectorAll(selector).forEach((node) => {
|
|
400
|
+
const value = node.getAttribute(attribute);
|
|
401
|
+
if (value && value.split("?")[0] === relative) {
|
|
402
|
+
node.setAttribute(attribute, value.split("?")[0] + version);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function bustContains(selector, attribute, relative, version) {
|
|
407
|
+
document.querySelectorAll(selector).forEach((node) => {
|
|
408
|
+
const value = node.getAttribute(attribute);
|
|
409
|
+
if (value && value.split("?")[0].indexOf(relative) !== -1) {
|
|
410
|
+
node.setAttribute(attribute, value.split("?")[0] + version);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function connect() {
|
|
415
|
+
if (source)
|
|
416
|
+
return;
|
|
417
|
+
shouldReconnect = true;
|
|
418
|
+
source = new EventSource("/hmr");
|
|
419
|
+
source.addEventListener("message", (event) => {
|
|
420
|
+
let message;
|
|
421
|
+
try {
|
|
422
|
+
message = JSON.parse(event.data);
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
hub.dispatch(message);
|
|
428
|
+
});
|
|
429
|
+
source.addEventListener("error", () => {
|
|
430
|
+
source.close();
|
|
431
|
+
source = undefined;
|
|
432
|
+
if (shouldReconnect) {
|
|
433
|
+
clearTimeout(reconnectTimer);
|
|
434
|
+
reconnectTimer = setTimeout(connect, 1000);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
window.addEventListener("pagehide", () => {
|
|
438
|
+
shouldReconnect = false;
|
|
439
|
+
clearTimeout(reconnectTimer);
|
|
440
|
+
if (source)
|
|
441
|
+
source.close();
|
|
442
|
+
source = undefined;
|
|
443
|
+
}, { once: true });
|
|
444
|
+
}
|
|
445
|
+
return hub;
|
|
446
|
+
}
|
package/dist/utils.d.mts
CHANGED
|
@@ -9,10 +9,22 @@ export declare const bundleConfig: Config;
|
|
|
9
9
|
export declare function fileCopy(file: string): Promise<void>;
|
|
10
10
|
export declare function createDir(file: string): Promise<string | undefined>;
|
|
11
11
|
export declare function getBuildPath(file: string): string;
|
|
12
|
-
export
|
|
12
|
+
export type HMREvent = {
|
|
13
|
+
type: "html";
|
|
13
14
|
file: string;
|
|
14
15
|
html?: string;
|
|
15
|
-
|
|
16
|
+
previousHtml?: string;
|
|
17
|
+
} | {
|
|
18
|
+
type: "css";
|
|
19
|
+
file: string;
|
|
20
|
+
} | {
|
|
21
|
+
type: "asset";
|
|
22
|
+
file: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: "full-reload";
|
|
25
|
+
file: string;
|
|
26
|
+
};
|
|
27
|
+
export declare let serverSentEvents: undefined | ((event: HMREvent) => void);
|
|
16
28
|
export declare function createDefaultServer(isSecure: boolean): Promise<[Router, Server | HTTPSServer]>;
|
|
17
29
|
export declare function getPostCSSConfig(): Promise<postcssrc.Result | {
|
|
18
30
|
plugins: (typeof cssnano)[];
|