@absolutejs/absolute 0.15.0 → 0.15.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/dev/client/cssUtils.ts +288 -0
- package/dist/dev/client/domDiff.ts +261 -0
- package/dist/dev/client/domState.ts +271 -0
- package/dist/dev/client/errorOverlay.ts +145 -0
- package/dist/dev/client/frameworkDetect.ts +63 -0
- package/dist/dev/client/handlers/html.ts +415 -0
- package/dist/dev/client/handlers/htmx.ts +248 -0
- package/dist/dev/client/handlers/react.ts +80 -0
- package/dist/dev/client/handlers/rebuild.ts +147 -0
- package/dist/dev/client/handlers/svelte.ts +129 -0
- package/dist/dev/client/handlers/vue.ts +254 -0
- package/dist/dev/client/headPatch.ts +213 -0
- package/dist/dev/client/hmrClient.ts +204 -0
- package/dist/dev/client/moduleVersions.ts +57 -0
- package/dist/dev/client/reactRefreshSetup.ts +21 -0
- package/dist/index.js +44 -23
- package/dist/index.js.map +5 -5
- package/package.json +3 -3
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/* HTML + script HMR update handlers */
|
|
2
|
+
|
|
3
|
+
import { patchDOMInPlace } from '../domDiff';
|
|
4
|
+
import {
|
|
5
|
+
saveDOMState,
|
|
6
|
+
restoreDOMState,
|
|
7
|
+
saveFormState,
|
|
8
|
+
restoreFormState,
|
|
9
|
+
saveScrollState,
|
|
10
|
+
restoreScrollState
|
|
11
|
+
} from '../domState';
|
|
12
|
+
import { processCSSLinks, waitForCSSAndUpdate } from '../cssUtils';
|
|
13
|
+
import { patchHeadInPlace } from '../headPatch';
|
|
14
|
+
import { detectCurrentFramework } from '../frameworkDetect';
|
|
15
|
+
import type { ScriptInfo } from '../../../../types/client';
|
|
16
|
+
import { hmrState } from '../../../../types/client';
|
|
17
|
+
|
|
18
|
+
export const handleScriptUpdate = (message: {
|
|
19
|
+
data: { framework?: string; scriptPath?: string };
|
|
20
|
+
}) => {
|
|
21
|
+
console.log('[HMR] Received script-update message');
|
|
22
|
+
const scriptFramework = message.data.framework;
|
|
23
|
+
const currentFw = detectCurrentFramework();
|
|
24
|
+
|
|
25
|
+
if (currentFw !== scriptFramework) {
|
|
26
|
+
console.log(
|
|
27
|
+
'[HMR] Skipping script update - different framework:',
|
|
28
|
+
currentFw,
|
|
29
|
+
'vs',
|
|
30
|
+
scriptFramework
|
|
31
|
+
);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const scriptPath = message.data.scriptPath;
|
|
36
|
+
if (!scriptPath) {
|
|
37
|
+
console.warn('[HMR] No script path in update');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log('[HMR] Hot-reloading script:', scriptPath);
|
|
42
|
+
|
|
43
|
+
const interactiveSelectors =
|
|
44
|
+
'button, [onclick], [onchange], [oninput], details, input, select, textarea';
|
|
45
|
+
document.body.querySelectorAll(interactiveSelectors).forEach(function (el) {
|
|
46
|
+
const cloned = el.cloneNode(true);
|
|
47
|
+
if (el.parentNode) {
|
|
48
|
+
el.parentNode.replaceChild(cloned, el);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const counterSpan = document.querySelector('#counter');
|
|
53
|
+
if (counterSpan) {
|
|
54
|
+
window.__HMR_DOM_STATE__ = {
|
|
55
|
+
count: parseInt(counterSpan.textContent || '0', 10)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cacheBustedPath = scriptPath + '?t=' + Date.now();
|
|
60
|
+
import(/* @vite-ignore */ cacheBustedPath)
|
|
61
|
+
.then(function () {
|
|
62
|
+
console.log('[HMR] Script hot-reloaded successfully');
|
|
63
|
+
})
|
|
64
|
+
.catch(function (err: unknown) {
|
|
65
|
+
console.error(
|
|
66
|
+
'[HMR] Script hot-reload failed, falling back to page reload:',
|
|
67
|
+
err
|
|
68
|
+
);
|
|
69
|
+
window.location.reload();
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const handleHTMLUpdate = (message: {
|
|
74
|
+
data: {
|
|
75
|
+
html?: string | { body?: string; head?: string } | null;
|
|
76
|
+
};
|
|
77
|
+
}) => {
|
|
78
|
+
console.log('[HMR] Received html-update message');
|
|
79
|
+
const htmlFrameworkCheck = detectCurrentFramework();
|
|
80
|
+
console.log('[HMR] Current framework:', htmlFrameworkCheck);
|
|
81
|
+
if (htmlFrameworkCheck !== 'html') {
|
|
82
|
+
console.log('[HMR] Skipping - not on HTML page');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (window.__REACT_ROOT__) {
|
|
87
|
+
window.__REACT_ROOT__ = undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
sessionStorage.setItem('__HMR_ACTIVE__', 'true');
|
|
91
|
+
|
|
92
|
+
const htmlDomState = saveDOMState(document.body);
|
|
93
|
+
|
|
94
|
+
let htmlBody: string | null = null;
|
|
95
|
+
let htmlHead: string | null = null;
|
|
96
|
+
if (typeof message.data.html === 'string') {
|
|
97
|
+
htmlBody = message.data.html;
|
|
98
|
+
} else if (message.data.html && typeof message.data.html === 'object') {
|
|
99
|
+
htmlBody = message.data.html.body || null;
|
|
100
|
+
htmlHead = message.data.html.head || null;
|
|
101
|
+
}
|
|
102
|
+
console.log('[HMR] htmlBody length:', htmlBody ? htmlBody.length : 'null');
|
|
103
|
+
console.log('[HMR] htmlHead:', htmlHead ? 'present' : 'null');
|
|
104
|
+
|
|
105
|
+
if (htmlBody) {
|
|
106
|
+
console.log('[HMR] Processing htmlBody');
|
|
107
|
+
if (htmlHead) {
|
|
108
|
+
console.log('[HMR] Has htmlHead, patching head elements');
|
|
109
|
+
|
|
110
|
+
const doPatchHead = function () {
|
|
111
|
+
patchHeadInPlace(htmlHead!);
|
|
112
|
+
};
|
|
113
|
+
if (hmrState.isFirstHMRUpdate) {
|
|
114
|
+
console.log(
|
|
115
|
+
'[HMR] First update - adding head patch stabilization delay'
|
|
116
|
+
);
|
|
117
|
+
setTimeout(doPatchHead, 50);
|
|
118
|
+
} else {
|
|
119
|
+
doPatchHead();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log('[HMR] Processing CSS links');
|
|
123
|
+
const cssResult = processCSSLinks(htmlHead);
|
|
124
|
+
|
|
125
|
+
const updateBodyAfterCSS = function () {
|
|
126
|
+
updateHTMLBody(htmlBody!, htmlDomState, document.body);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
console.log(
|
|
130
|
+
'[HMR] linksToWaitFor count:',
|
|
131
|
+
cssResult.linksToWaitFor.length
|
|
132
|
+
);
|
|
133
|
+
waitForCSSAndUpdate(cssResult, updateBodyAfterCSS);
|
|
134
|
+
} else {
|
|
135
|
+
console.log('[HMR] No htmlHead, patching body directly');
|
|
136
|
+
const container = document.body;
|
|
137
|
+
if (container) {
|
|
138
|
+
updateHTMLBodyDirect(htmlBody, htmlDomState, container);
|
|
139
|
+
restoreDOMState(container, htmlDomState);
|
|
140
|
+
} else {
|
|
141
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const updateHTMLBody = (
|
|
150
|
+
htmlBody: string,
|
|
151
|
+
htmlDomState: ReturnType<typeof saveDOMState>,
|
|
152
|
+
container: HTMLElement
|
|
153
|
+
) => {
|
|
154
|
+
console.log('[HMR] updateBodyAfterCSS called');
|
|
155
|
+
if (!container) {
|
|
156
|
+
console.log('[HMR] ERROR: document.body not found');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const counterSpan = container.querySelector('#counter');
|
|
161
|
+
const counterValue = counterSpan
|
|
162
|
+
? parseInt(counterSpan.textContent || '0', 10)
|
|
163
|
+
: 0;
|
|
164
|
+
|
|
165
|
+
const savedState = {
|
|
166
|
+
componentState: { count: counterValue },
|
|
167
|
+
forms: saveFormState(),
|
|
168
|
+
scroll: saveScrollState()
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
let body = htmlBody;
|
|
172
|
+
if (counterValue > 0) {
|
|
173
|
+
body = body.replace(
|
|
174
|
+
new RegExp('<span id="counter">0<' + '/span>', 'g'),
|
|
175
|
+
'<span id="counter">' + counterValue + '<' + '/span>'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const existingScripts = collectScripts(container);
|
|
180
|
+
const hmrScript = container.querySelector('script[data-hmr-client]');
|
|
181
|
+
const tempDiv = document.createElement('div');
|
|
182
|
+
tempDiv.innerHTML = body;
|
|
183
|
+
const newScripts = collectScriptsFromElement(tempDiv);
|
|
184
|
+
|
|
185
|
+
const scriptsChanged = didScriptsChange(existingScripts, newScripts);
|
|
186
|
+
const htmlStructureChanged = didHTMLStructureChange(container, tempDiv);
|
|
187
|
+
|
|
188
|
+
if (!htmlStructureChanged && !scriptsChanged) {
|
|
189
|
+
console.log('[HMR] CSS-only change detected - skipping body patch');
|
|
190
|
+
} else {
|
|
191
|
+
patchDOMInPlace(container, body);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (hmrScript && !container.querySelector('script[data-hmr-client]')) {
|
|
195
|
+
container.appendChild(hmrScript);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
requestAnimationFrame(function () {
|
|
199
|
+
restoreFormState(savedState.forms);
|
|
200
|
+
restoreScrollState(savedState.scroll);
|
|
201
|
+
|
|
202
|
+
const newCounterSpan = container.querySelector('#counter');
|
|
203
|
+
if (newCounterSpan && savedState.componentState.count !== undefined) {
|
|
204
|
+
newCounterSpan.textContent = String(
|
|
205
|
+
savedState.componentState.count
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (scriptsChanged || htmlStructureChanged) {
|
|
210
|
+
cloneInteractiveElements(container);
|
|
211
|
+
window.__HMR_DOM_STATE__ = {
|
|
212
|
+
count: savedState.componentState.count || 0
|
|
213
|
+
};
|
|
214
|
+
reExecuteScripts(container, newScripts);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const updateHTMLBodyDirect = (
|
|
221
|
+
htmlBody: string,
|
|
222
|
+
htmlDomState: ReturnType<typeof saveDOMState>,
|
|
223
|
+
container: HTMLElement
|
|
224
|
+
) => {
|
|
225
|
+
const counterSpan = container.querySelector('#counter');
|
|
226
|
+
const counterValue = counterSpan
|
|
227
|
+
? parseInt(counterSpan.textContent || '0', 10)
|
|
228
|
+
: 0;
|
|
229
|
+
|
|
230
|
+
const savedState = {
|
|
231
|
+
componentState: { count: counterValue },
|
|
232
|
+
forms: saveFormState(),
|
|
233
|
+
scroll: saveScrollState()
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
let body = htmlBody;
|
|
237
|
+
if (counterValue > 0) {
|
|
238
|
+
body = body.replace(
|
|
239
|
+
new RegExp('<span id="counter">0<' + '/span>', 'g'),
|
|
240
|
+
'<span id="counter">' + counterValue + '<' + '/span>'
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const existingScripts = collectScripts(container);
|
|
245
|
+
const tempDiv = document.createElement('div');
|
|
246
|
+
tempDiv.innerHTML = body;
|
|
247
|
+
const newScripts = collectScriptsFromElement(tempDiv);
|
|
248
|
+
const scriptsChanged = didScriptsChange(existingScripts, newScripts);
|
|
249
|
+
const hmrScript = container.querySelector('script[data-hmr-client]');
|
|
250
|
+
|
|
251
|
+
patchDOMInPlace(container, body);
|
|
252
|
+
|
|
253
|
+
if (hmrScript && !container.querySelector('script[data-hmr-client]')) {
|
|
254
|
+
container.appendChild(hmrScript);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
requestAnimationFrame(function () {
|
|
258
|
+
restoreFormState(savedState.forms);
|
|
259
|
+
restoreScrollState(savedState.scroll);
|
|
260
|
+
|
|
261
|
+
const newCounterSpan = container.querySelector('#counter');
|
|
262
|
+
if (newCounterSpan && savedState.componentState.count !== undefined) {
|
|
263
|
+
newCounterSpan.textContent = String(
|
|
264
|
+
savedState.componentState.count
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
container
|
|
269
|
+
.querySelectorAll('[data-hmr-listeners-attached]')
|
|
270
|
+
.forEach(function (el) {
|
|
271
|
+
const cloned = el.cloneNode(true) as Element;
|
|
272
|
+
if (el.parentNode) {
|
|
273
|
+
el.parentNode.replaceChild(cloned, el);
|
|
274
|
+
}
|
|
275
|
+
cloned.removeAttribute('data-hmr-listeners-attached');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
removeOldScripts(container);
|
|
279
|
+
newScripts.forEach(function (scriptInfo) {
|
|
280
|
+
const newScript = document.createElement('script');
|
|
281
|
+
const separator = scriptInfo.src.includes('?') ? '&' : '?';
|
|
282
|
+
newScript.src = scriptInfo.src + separator + 't=' + Date.now();
|
|
283
|
+
newScript.type = scriptInfo.type;
|
|
284
|
+
container.appendChild(newScript);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const inlineScripts = container.querySelectorAll('script:not([src])');
|
|
288
|
+
inlineScripts.forEach(function (script) {
|
|
289
|
+
if (!(script as Element).hasAttribute('data-hmr-client')) {
|
|
290
|
+
const newScript = document.createElement('script');
|
|
291
|
+
newScript.textContent = script.textContent || '';
|
|
292
|
+
newScript.type =
|
|
293
|
+
(script as HTMLScriptElement).type || 'text/javascript';
|
|
294
|
+
if (script.parentNode) {
|
|
295
|
+
script.parentNode.replaceChild(newScript, script);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/* Shared helpers for HTML body updates */
|
|
304
|
+
|
|
305
|
+
const collectScripts = (container: HTMLElement) => {
|
|
306
|
+
return Array.from(container.querySelectorAll('script[src]')).map(
|
|
307
|
+
function (script) {
|
|
308
|
+
return {
|
|
309
|
+
src: script.getAttribute('src') || '',
|
|
310
|
+
type: script.getAttribute('type') || 'text/javascript'
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const collectScriptsFromElement = (el: HTMLElement) => {
|
|
317
|
+
return Array.from(el.querySelectorAll('script[src]')).map(
|
|
318
|
+
function (script) {
|
|
319
|
+
return {
|
|
320
|
+
src: script.getAttribute('src') || '',
|
|
321
|
+
type: script.getAttribute('type') || 'text/javascript'
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const didScriptsChange = (
|
|
328
|
+
oldScripts: ScriptInfo[],
|
|
329
|
+
newScripts: ScriptInfo[]
|
|
330
|
+
) => {
|
|
331
|
+
return (
|
|
332
|
+
oldScripts.length !== newScripts.length ||
|
|
333
|
+
oldScripts.some(function (oldScript, idx) {
|
|
334
|
+
const oldSrcBase = oldScript.src.split('?')[0]!.split('&')[0];
|
|
335
|
+
const newScript = newScripts[idx];
|
|
336
|
+
if (!newScript) return true;
|
|
337
|
+
const newSrcBase = newScript.src.split('?')[0]!.split('&')[0];
|
|
338
|
+
return oldSrcBase !== newSrcBase;
|
|
339
|
+
})
|
|
340
|
+
);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const normalizeHTMLForComparison = (element: HTMLElement) => {
|
|
344
|
+
const clone = element.cloneNode(true) as HTMLElement;
|
|
345
|
+
const scripts = clone.querySelectorAll('script');
|
|
346
|
+
scripts.forEach(function (script) {
|
|
347
|
+
if (script.parentNode) {
|
|
348
|
+
script.parentNode.removeChild(script);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
const allElements = clone.querySelectorAll('*');
|
|
352
|
+
allElements.forEach(function (el) {
|
|
353
|
+
el.removeAttribute('data-hmr-listeners-attached');
|
|
354
|
+
});
|
|
355
|
+
if (clone.removeAttribute) {
|
|
356
|
+
clone.removeAttribute('data-hmr-listeners-attached');
|
|
357
|
+
}
|
|
358
|
+
return clone.innerHTML;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const didHTMLStructureChange = (
|
|
362
|
+
container: HTMLElement,
|
|
363
|
+
tempDiv: HTMLElement
|
|
364
|
+
) => {
|
|
365
|
+
return (
|
|
366
|
+
normalizeHTMLForComparison(container) !==
|
|
367
|
+
normalizeHTMLForComparison(tempDiv)
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const cloneInteractiveElements = (container: HTMLElement) => {
|
|
372
|
+
const interactiveSelectors =
|
|
373
|
+
'button, [onclick], [onchange], [oninput], [onsubmit], ' +
|
|
374
|
+
'details, input[type="button"], input[type="submit"], input[type="reset"]';
|
|
375
|
+
container.querySelectorAll(interactiveSelectors).forEach(function (el) {
|
|
376
|
+
const cloned = el.cloneNode(true);
|
|
377
|
+
if (el.parentNode) {
|
|
378
|
+
el.parentNode.replaceChild(cloned, el);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const removeOldScripts = (container: HTMLElement) => {
|
|
384
|
+
const scriptsInNewHTML = container.querySelectorAll('script[src]');
|
|
385
|
+
scriptsInNewHTML.forEach(function (script) {
|
|
386
|
+
if (!(script as Element).hasAttribute('data-hmr-client')) {
|
|
387
|
+
script.remove();
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const reExecuteScripts = (container: HTMLElement, newScripts: ScriptInfo[]) => {
|
|
393
|
+
removeOldScripts(container);
|
|
394
|
+
|
|
395
|
+
newScripts.forEach(function (scriptInfo) {
|
|
396
|
+
const newScript = document.createElement('script');
|
|
397
|
+
const separator = scriptInfo.src.includes('?') ? '&' : '?';
|
|
398
|
+
newScript.src = scriptInfo.src + separator + 't=' + Date.now();
|
|
399
|
+
newScript.type = scriptInfo.type;
|
|
400
|
+
container.appendChild(newScript);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const inlineScripts = container.querySelectorAll('script:not([src])');
|
|
404
|
+
inlineScripts.forEach(function (script) {
|
|
405
|
+
if (!(script as Element).hasAttribute('data-hmr-client')) {
|
|
406
|
+
const newScript = document.createElement('script');
|
|
407
|
+
newScript.textContent = script.textContent || '';
|
|
408
|
+
newScript.type =
|
|
409
|
+
(script as HTMLScriptElement).type || 'text/javascript';
|
|
410
|
+
if (script.parentNode) {
|
|
411
|
+
script.parentNode.replaceChild(newScript, script);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/* HTMX HMR update handler */
|
|
2
|
+
|
|
3
|
+
import { patchDOMInPlace } from '../domDiff';
|
|
4
|
+
import {
|
|
5
|
+
saveDOMState,
|
|
6
|
+
restoreDOMState,
|
|
7
|
+
saveFormState,
|
|
8
|
+
restoreFormState,
|
|
9
|
+
saveScrollState,
|
|
10
|
+
restoreScrollState
|
|
11
|
+
} from '../domState';
|
|
12
|
+
import { processCSSLinks, waitForCSSAndUpdate } from '../cssUtils';
|
|
13
|
+
import { patchHeadInPlace } from '../headPatch';
|
|
14
|
+
import { detectCurrentFramework } from '../frameworkDetect';
|
|
15
|
+
import type { ScriptInfo } from '../../../../types/client';
|
|
16
|
+
import { hmrState } from '../../../../types/client';
|
|
17
|
+
|
|
18
|
+
export const handleHTMXUpdate = (message: {
|
|
19
|
+
data: {
|
|
20
|
+
html?: string | { body?: string; head?: string } | null;
|
|
21
|
+
};
|
|
22
|
+
}) => {
|
|
23
|
+
const htmxFrameworkCheck = detectCurrentFramework();
|
|
24
|
+
if (htmxFrameworkCheck !== 'htmx') return;
|
|
25
|
+
|
|
26
|
+
if (window.__REACT_ROOT__) {
|
|
27
|
+
window.__REACT_ROOT__ = undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
sessionStorage.setItem('__HMR_ACTIVE__', 'true');
|
|
31
|
+
|
|
32
|
+
const htmxDomState = saveDOMState(document.body);
|
|
33
|
+
|
|
34
|
+
let htmxBody: string | null = null;
|
|
35
|
+
let htmxHead: string | null = null;
|
|
36
|
+
if (typeof message.data.html === 'string') {
|
|
37
|
+
htmxBody = message.data.html;
|
|
38
|
+
} else if (message.data.html && typeof message.data.html === 'object') {
|
|
39
|
+
htmxBody = message.data.html.body || null;
|
|
40
|
+
htmxHead = message.data.html.head || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (htmxBody) {
|
|
44
|
+
const capturedBody = htmxBody;
|
|
45
|
+
|
|
46
|
+
const updateHTMXBodyAfterCSS = function () {
|
|
47
|
+
updateHTMXBody(capturedBody, htmxDomState, document.body);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (htmxHead) {
|
|
51
|
+
console.log('[HMR] Has htmxHead, patching head elements');
|
|
52
|
+
|
|
53
|
+
const doPatchHead = function () {
|
|
54
|
+
patchHeadInPlace(htmxHead!);
|
|
55
|
+
};
|
|
56
|
+
if (hmrState.isFirstHMRUpdate) {
|
|
57
|
+
console.log(
|
|
58
|
+
'[HMR] First update - adding head patch stabilization delay'
|
|
59
|
+
);
|
|
60
|
+
setTimeout(doPatchHead, 50);
|
|
61
|
+
} else {
|
|
62
|
+
doPatchHead();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('[HMR] Processing CSS links');
|
|
66
|
+
const cssResult = processCSSLinks(htmxHead);
|
|
67
|
+
|
|
68
|
+
waitForCSSAndUpdate(cssResult, updateHTMXBodyAfterCSS);
|
|
69
|
+
} else {
|
|
70
|
+
updateHTMXBodyAfterCSS();
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const updateHTMXBody = (
|
|
78
|
+
htmxBody: string,
|
|
79
|
+
htmxDomState: ReturnType<typeof saveDOMState>,
|
|
80
|
+
container: HTMLElement
|
|
81
|
+
) => {
|
|
82
|
+
if (!container) return;
|
|
83
|
+
|
|
84
|
+
const countSpan = container.querySelector('#count');
|
|
85
|
+
const countValue = countSpan
|
|
86
|
+
? parseInt(countSpan.textContent || '0', 10)
|
|
87
|
+
: 0;
|
|
88
|
+
|
|
89
|
+
const savedState = {
|
|
90
|
+
componentState: { count: countValue },
|
|
91
|
+
forms: saveFormState(),
|
|
92
|
+
scroll: saveScrollState()
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const existingScripts = collectScripts(container);
|
|
96
|
+
|
|
97
|
+
const tempDiv = document.createElement('div');
|
|
98
|
+
tempDiv.innerHTML = htmxBody;
|
|
99
|
+
|
|
100
|
+
if (savedState.componentState.count !== undefined) {
|
|
101
|
+
const newCounterSpan = tempDiv.querySelector('#count');
|
|
102
|
+
if (newCounterSpan) {
|
|
103
|
+
newCounterSpan.textContent = String(
|
|
104
|
+
savedState.componentState.count
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const patchedBody = tempDiv.innerHTML;
|
|
110
|
+
const newScripts = collectScriptsFromElement(tempDiv);
|
|
111
|
+
const scriptsChanged = didScriptsChange(existingScripts, newScripts);
|
|
112
|
+
|
|
113
|
+
const htmlStructureChanged = didHTMLStructureChange(container, tempDiv);
|
|
114
|
+
|
|
115
|
+
const hmrScript = container.querySelector('script[data-hmr-client]');
|
|
116
|
+
|
|
117
|
+
patchDOMInPlace(container, patchedBody);
|
|
118
|
+
|
|
119
|
+
if (hmrScript && !container.querySelector('script[data-hmr-client]')) {
|
|
120
|
+
container.appendChild(hmrScript);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
requestAnimationFrame(function () {
|
|
124
|
+
restoreFormState(savedState.forms);
|
|
125
|
+
restoreScrollState(savedState.scroll);
|
|
126
|
+
|
|
127
|
+
const newCountSpan = container.querySelector('#count');
|
|
128
|
+
if (newCountSpan && savedState.componentState.count !== undefined) {
|
|
129
|
+
newCountSpan.textContent = String(savedState.componentState.count);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
restoreDOMState(container, htmxDomState);
|
|
133
|
+
|
|
134
|
+
if (scriptsChanged || htmlStructureChanged) {
|
|
135
|
+
container
|
|
136
|
+
.querySelectorAll('[data-hmr-listeners-attached]')
|
|
137
|
+
.forEach(function (el) {
|
|
138
|
+
const cloned = el.cloneNode(true) as Element;
|
|
139
|
+
if (el.parentNode) {
|
|
140
|
+
el.parentNode.replaceChild(cloned, el);
|
|
141
|
+
}
|
|
142
|
+
cloned.removeAttribute('data-hmr-listeners-attached');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const scriptsInNewHTML = container.querySelectorAll('script[src]');
|
|
146
|
+
scriptsInNewHTML.forEach(function (script) {
|
|
147
|
+
if (!(script as Element).hasAttribute('data-hmr-client')) {
|
|
148
|
+
script.remove();
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
newScripts.forEach(function (scriptInfo) {
|
|
153
|
+
const newScript = document.createElement('script');
|
|
154
|
+
const separator = scriptInfo.src.includes('?') ? '&' : '?';
|
|
155
|
+
newScript.src = scriptInfo.src + separator + 't=' + Date.now();
|
|
156
|
+
newScript.type = scriptInfo.type;
|
|
157
|
+
container.appendChild(newScript);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const inlineScripts =
|
|
161
|
+
container.querySelectorAll('script:not([src])');
|
|
162
|
+
inlineScripts.forEach(function (script) {
|
|
163
|
+
if (!(script as Element).hasAttribute('data-hmr-client')) {
|
|
164
|
+
const newScript = document.createElement('script');
|
|
165
|
+
newScript.textContent = script.textContent || '';
|
|
166
|
+
newScript.type =
|
|
167
|
+
(script as HTMLScriptElement).type || 'text/javascript';
|
|
168
|
+
if (script.parentNode) {
|
|
169
|
+
script.parentNode.replaceChild(newScript, script);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (window.htmx) {
|
|
176
|
+
window.htmx.process(container);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
sessionStorage.removeItem('__HMR_ACTIVE__');
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/* Shared helpers */
|
|
183
|
+
|
|
184
|
+
const collectScripts = (container: HTMLElement) => {
|
|
185
|
+
return Array.from(container.querySelectorAll('script[src]')).map(
|
|
186
|
+
function (script) {
|
|
187
|
+
return {
|
|
188
|
+
src: script.getAttribute('src') || '',
|
|
189
|
+
type: script.getAttribute('type') || 'text/javascript'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const collectScriptsFromElement = (el: HTMLElement) => {
|
|
196
|
+
return Array.from(el.querySelectorAll('script[src]')).map(
|
|
197
|
+
function (script) {
|
|
198
|
+
return {
|
|
199
|
+
src: script.getAttribute('src') || '',
|
|
200
|
+
type: script.getAttribute('type') || 'text/javascript'
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const didScriptsChange = (
|
|
207
|
+
oldScripts: ScriptInfo[],
|
|
208
|
+
newScripts: ScriptInfo[]
|
|
209
|
+
) => {
|
|
210
|
+
return (
|
|
211
|
+
oldScripts.length !== newScripts.length ||
|
|
212
|
+
oldScripts.some(function (oldScript, idx) {
|
|
213
|
+
const oldSrcBase = oldScript.src.split('?')[0]!.split('&')[0];
|
|
214
|
+
const newScript = newScripts[idx];
|
|
215
|
+
if (!newScript) return true;
|
|
216
|
+
const newSrcBase = newScript.src.split('?')[0]!.split('&')[0];
|
|
217
|
+
return oldSrcBase !== newSrcBase;
|
|
218
|
+
})
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const normalizeHTMLForComparison = (element: HTMLElement) => {
|
|
223
|
+
const clone = element.cloneNode(true) as HTMLElement;
|
|
224
|
+
const scripts = clone.querySelectorAll('script');
|
|
225
|
+
scripts.forEach(function (script) {
|
|
226
|
+
if (script.parentNode) {
|
|
227
|
+
script.parentNode.removeChild(script);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const allElements = clone.querySelectorAll('*');
|
|
231
|
+
allElements.forEach(function (el) {
|
|
232
|
+
el.removeAttribute('data-hmr-listeners-attached');
|
|
233
|
+
});
|
|
234
|
+
if (clone.removeAttribute) {
|
|
235
|
+
clone.removeAttribute('data-hmr-listeners-attached');
|
|
236
|
+
}
|
|
237
|
+
return clone.innerHTML;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const didHTMLStructureChange = (
|
|
241
|
+
container: HTMLElement,
|
|
242
|
+
tempDiv: HTMLElement
|
|
243
|
+
) => {
|
|
244
|
+
return (
|
|
245
|
+
normalizeHTMLForComparison(container) !==
|
|
246
|
+
normalizeHTMLForComparison(tempDiv)
|
|
247
|
+
);
|
|
248
|
+
};
|