@absolutejs/absolute 0.19.0-beta.745 → 0.19.0-beta.746

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.
@@ -511,12 +511,27 @@ const tickAngularApp = () => {
511
511
  }
512
512
  };
513
513
 
514
- const runWithViewTransition = (updateFn: () => Promise<void>) => {
514
+ /* HMR updates must be serialized.
515
+ If a second `startViewTransition` runs while a previous one is still in
516
+ flight, the browser aborts the old transition — but the old update
517
+ callback (destroyApp + async bootstrap) keeps running. Two updateFns
518
+ then race over the DOM and Angular state, corrupting HMR and forcing a
519
+ page refresh.
520
+ We avoid that by queuing later updates until the current one finishes.
521
+ If multiple updates queue up, only the latest matters (intermediate ones
522
+ are superseded), so we collapse to a single pending entry. */
523
+ let activeUpdate: Promise<void> | null = null;
524
+ let pendingUpdate: (() => Promise<void>) | null = null;
525
+
526
+ const runOneUpdate = async (updateFn: () => Promise<void>) => {
515
527
  const doc: ViewTransitionDocument = document;
528
+
516
529
  if (typeof doc.startViewTransition !== 'function') {
517
- updateFn().catch((err: unknown) => {
530
+ try {
531
+ await updateFn();
532
+ } catch (err) {
518
533
  console.warn('[HMR] Angular update failed (non-fatal):', err);
519
- });
534
+ }
520
535
 
521
536
  return;
522
537
  }
@@ -531,15 +546,56 @@ const runWithViewTransition = (updateFn: () => Promise<void>) => {
531
546
  /* ignored */
532
547
  }
533
548
 
534
- const removeStyle = () => {
549
+ let updatePromise: Promise<void> = Promise.resolve();
550
+ try {
551
+ const transition = doc.startViewTransition(() => {
552
+ updatePromise = updateFn();
553
+
554
+ return updatePromise;
555
+ });
556
+ // Wait for both the visual transition and the update callback.
557
+ // `transition.finished` rejects with AbortError when a new transition
558
+ // supersedes this one — swallow that since we serialize updates so
559
+ // it shouldn't happen, and even if it does we still want to wait
560
+ // for `updateFn` to complete before releasing the next update.
561
+ await Promise.all([
562
+ transition.finished.catch(() => {
563
+ /* skipped */
564
+ }),
565
+ updatePromise.catch((err) => {
566
+ console.warn('[HMR] Angular update failed (non-fatal):', err);
567
+ })
568
+ ]);
569
+ } catch (err) {
570
+ console.warn('[HMR] Angular update failed (non-fatal):', err);
571
+ // If startViewTransition itself threw, run the update directly so
572
+ // HMR still applies (loses the crossfade but preserves correctness).
573
+ try {
574
+ await updateFn();
575
+ } catch (innerErr) {
576
+ console.warn('[HMR] Angular update failed (non-fatal):', innerErr);
577
+ }
578
+ } finally {
535
579
  if (styleEl && styleEl.parentNode) styleEl.remove();
536
- };
580
+ }
581
+ };
537
582
 
538
- doc.startViewTransition(async () => {
539
- await updateFn();
540
- })
541
- .finished.then(removeStyle)
542
- .catch(removeStyle);
583
+ const runWithViewTransition = (updateFn: () => Promise<void>) => {
584
+ if (activeUpdate) {
585
+ // Supersede any earlier queued update — only the latest matters.
586
+ pendingUpdate = updateFn;
587
+
588
+ return;
589
+ }
590
+
591
+ activeUpdate = runOneUpdate(updateFn).finally(() => {
592
+ activeUpdate = null;
593
+ if (pendingUpdate) {
594
+ const next = pendingUpdate;
595
+ pendingUpdate = null;
596
+ runWithViewTransition(next);
597
+ }
598
+ });
543
599
  };
544
600
 
545
601
  const handleFullUpdate = (message: HMRMessage) => {
package/package.json CHANGED
@@ -349,5 +349,5 @@
349
349
  ]
350
350
  }
351
351
  },
352
- "version": "0.19.0-beta.745"
352
+ "version": "0.19.0-beta.746"
353
353
  }