@absolutejs/absolute 0.19.0-beta.1068 → 0.19.0-beta.1069

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.
@@ -187,9 +187,12 @@ const updateHTMLBody = (
187
187
  const newInlineScripts = collectInlineScripts(tempDiv);
188
188
 
189
189
  const htmlStructureChanged = didHTMLStructureChange(container, tempDiv);
190
+ const inlineScriptsChanged = didInlineScriptsChange(
191
+ oldInlineScripts,
192
+ newInlineScripts
193
+ );
190
194
  const scriptsChanged =
191
- didScriptsChange(existingScripts, newScripts) ||
192
- didInlineScriptsChange(oldInlineScripts, newInlineScripts);
195
+ didScriptsChange(existingScripts, newScripts) || inlineScriptsChanged;
193
196
 
194
197
  if (htmlStructureChanged || scriptsChanged) {
195
198
  patchDOMInPlace(container, htmlBody);
@@ -207,27 +210,22 @@ const updateHTMLBody = (
207
210
  // changed. A pure markup edit (e.g. a heading) leaves inline-script
208
211
  // state and `addEventListener` handlers intact.
209
212
  if (scriptsChanged) {
210
- cloneInteractiveElements(container);
213
+ // Clone (which strips listeners via cloneNode) ONLY when an inline
214
+ // script changed: a changed inline script re-runs in place below
215
+ // and would otherwise double-bind on elements that still carry the
216
+ // old handler. Unchanged inline scripts are skipped on re-exec, and
217
+ // external modules are re-imported via `import()` whose module cache
218
+ // means an unchanged body never re-runs — cloning for those would
219
+ // orphan their listeners forever (the regression this fixes).
220
+ if (inlineScriptsChanged) {
221
+ cloneInteractiveElements(container);
222
+ }
211
223
  reExecuteScripts(container, newScripts, oldInlineScripts);
212
224
  }
213
225
  });
214
226
  sessionStorage.removeItem('__HMR_ACTIVE__');
215
227
  };
216
228
 
217
- const cloneHmrListenerElements = (container: HTMLElement) => {
218
- container
219
- .querySelectorAll('[data-hmr-listeners-attached]')
220
- .forEach((elem) => {
221
- const cloned = elem.cloneNode(true);
222
- if (elem.parentNode) {
223
- elem.parentNode.replaceChild(cloned, elem);
224
- }
225
- if (cloned instanceof Element) {
226
- cloned.removeAttribute('data-hmr-listeners-attached');
227
- }
228
- });
229
- };
230
-
231
229
  const replaceInlineScript = (script: Element) => {
232
230
  if (script.hasAttribute('data-hmr-client')) {
233
231
  return;
@@ -266,7 +264,13 @@ const updateHTMLBodyDirect = (
266
264
  restoreFormState(savedState.forms);
267
265
  restoreScrollState(savedState.scroll);
268
266
 
269
- cloneHmrListenerElements(container);
267
+ // Unlike `updateHTMLBody` (which re-imports externals as ES modules and
268
+ // must not clone unchanged ones), this path rebuilds EVERY external
269
+ // script below as a fresh `<script src?t=>` tag that always
270
+ // re-executes. So a single clone here is correct and necessary: it
271
+ // gives each re-running script a pristine element to bind once, with no
272
+ // double-binding.
273
+ cloneInteractiveElements(container);
270
274
 
271
275
  removeOldScripts(container);
272
276
  newScripts.forEach((scriptInfo) => {
@@ -304,10 +308,7 @@ const collectInlineScripts = (elem: HTMLElement) =>
304
308
  elem.querySelectorAll('script:not([src]):not([data-hmr-client])')
305
309
  ).map((script) => script.textContent || '');
306
310
 
307
- const didInlineScriptsChange = (
308
- oldInline: string[],
309
- newInline: string[]
310
- ) =>
311
+ const didInlineScriptsChange = (oldInline: string[], newInline: string[]) =>
311
312
  oldInline.length !== newInline.length ||
312
313
  oldInline.some((content, idx) => content !== newInline[idx]);
313
314
 
@@ -273,13 +273,31 @@ export const handleSvelteUpdate = (message: {
273
273
  const svelteWindow: SvelteHmrWindow = window;
274
274
  const acceptRegistry = svelteWindow.__SVELTE_HMR_ACCEPT__;
275
275
 
276
- // Save the OLD module's accept callback BEFORE importing.
277
- const acceptFn = acceptRegistry?.[pageModuleUrl];
276
+ // Save the OLD module's accept callback BEFORE importing. The exact
277
+ // key is the broadcast `pageModuleUrl`; on the happy path it matches
278
+ // the key the module server registered. If it doesn't (the registry
279
+ // was repopulated under a slightly different URL key, or `bun --hot`
280
+ // re-evaluated the module and reset `__SVELTE_HMR_ACCEPT__`), fall back
281
+ // to a suffix/basename match so we still find the callback. Exact match
282
+ // always wins, so the happy path is unchanged.
283
+ let acceptFn = acceptRegistry?.[pageModuleUrl];
284
+ if (!acceptFn && acceptRegistry) {
285
+ const wantBase = pageModuleUrl.split('?')[0] ?? pageModuleUrl;
286
+ const hit = Object.keys(acceptRegistry).find(
287
+ (key) =>
288
+ key === wantBase ||
289
+ key.endsWith(wantBase) ||
290
+ wantBase.endsWith(key)
291
+ );
292
+ if (hit) acceptFn = acceptRegistry[hit];
293
+ }
278
294
 
279
295
  import(modulePath)
280
296
  .then((newModule) => {
297
+ let applied = false;
281
298
  if (acceptFn) {
282
299
  acceptFn(newModule);
300
+ applied = true;
283
301
  }
284
302
 
285
303
  /* $.hmr_accept swaps component code in place but re-runs
@@ -290,15 +308,35 @@ export const handleSvelteUpdate = (message: {
290
308
  * bootstrap — so that state carries across (issue #41). */
291
309
  const preserved = window.__HMR_PRESERVED_STATE__;
292
310
  const remount = window.__SVELTE_REMOUNT__;
293
- if (
294
- typeof remount === 'function' &&
295
- preserved &&
296
- Object.keys(preserved).length > 0
297
- ) {
311
+ const hasPreserved =
312
+ preserved && Object.keys(preserved).length > 0;
313
+
314
+ if (applied) {
315
+ if (typeof remount === 'function' && hasPreserved) {
316
+ remount({
317
+ ...(window.__INITIAL_PROPS__ ?? {}),
318
+ ...preserved
319
+ });
320
+ }
321
+ } else if (typeof remount === 'function') {
322
+ /* Resilience fallback: no accept callback was found, so
323
+ * `$.hmr` never wired the freshly imported module in. The
324
+ * import warmed the module cache; remount the page with
325
+ * merged props to actually apply the new code instead of
326
+ * silently doing nothing. */
298
327
  remount({
299
328
  ...(window.__INITIAL_PROPS__ ?? {}),
300
- ...preserved
329
+ ...(preserved ?? {})
301
330
  });
331
+ } else {
332
+ /* Last resort: nothing can apply the update in place. */
333
+ console.warn(
334
+ '[HMR] Svelte accept callback missing and no remount available; reloading'
335
+ );
336
+ window.__HMR_PRESERVED_STATE__ = undefined;
337
+ window.location.reload();
338
+
339
+ return undefined;
302
340
  }
303
341
  window.__HMR_PRESERVED_STATE__ = undefined;
304
342