@absolutejs/absolute 0.19.0-beta.745 → 0.19.0-beta.747
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/angular/index.js +46 -35
- package/dist/angular/index.js.map +3 -3
- package/dist/angular/server.js +42 -31
- package/dist/angular/server.js.map +3 -3
- package/dist/build.js +43 -32
- package/dist/build.js.map +3 -3
- package/dist/dev/client/handlers/angular.ts +131 -18
- package/dist/dev/client/handlers/angularRuntime.ts +25 -4
- package/dist/index.js +46 -35
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
|
@@ -361,6 +361,10 @@ const patchRegisteredComponents = (
|
|
|
361
361
|
return { allPatched, patchedAny };
|
|
362
362
|
};
|
|
363
363
|
|
|
364
|
+
type FastPatchWindow = Window & {
|
|
365
|
+
__ANGULAR_HMR_FAST_PATCH__?: boolean;
|
|
366
|
+
};
|
|
367
|
+
|
|
364
368
|
const attemptFastPatch = async (
|
|
365
369
|
indexPath: string,
|
|
366
370
|
registry: Map<string, unknown>,
|
|
@@ -368,11 +372,21 @@ const attemptFastPatch = async (
|
|
|
368
372
|
sourceFile: string,
|
|
369
373
|
origWarn: typeof console.warn
|
|
370
374
|
) => {
|
|
375
|
+
// The bundled page chunk's top-level code re-bootstraps the Angular app
|
|
376
|
+
// (destroy + bootstrapApplication). For fast-patch we just need to read
|
|
377
|
+
// the freshly-built component classes — not re-bootstrap. Setting this
|
|
378
|
+
// flag tells the chunk to skip its bootstrap section and only run the
|
|
379
|
+
// `export * from '<page-module>'` line. Paired with the guard added in
|
|
380
|
+
// `src/build/compileAngular.ts` HMR template.
|
|
381
|
+
const w = window as FastPatchWindow;
|
|
382
|
+
w.__ANGULAR_HMR_FAST_PATCH__ = true;
|
|
371
383
|
try {
|
|
372
384
|
const newModule = await import(`${indexPath}?t=${Date.now()}`);
|
|
373
385
|
|
|
374
|
-
|
|
375
|
-
|
|
386
|
+
// NG0912 warnings fire during `applyUpdate` (Angular re-registers
|
|
387
|
+
// the new component class while the old one is still live). Keep
|
|
388
|
+
// the suppression active through the patch, restore right before
|
|
389
|
+
// `refresh()` so any non-NG0912 warnings during `tick()` surface.
|
|
376
390
|
const { allPatched, patchedAny } = patchRegisteredComponents(
|
|
377
391
|
newModule,
|
|
378
392
|
registry,
|
|
@@ -380,6 +394,8 @@ const attemptFastPatch = async (
|
|
|
380
394
|
sourceFile
|
|
381
395
|
);
|
|
382
396
|
|
|
397
|
+
console.warn = origWarn;
|
|
398
|
+
|
|
383
399
|
if (!patchedAny) return false;
|
|
384
400
|
if (!allPatched) return false;
|
|
385
401
|
|
|
@@ -391,11 +407,21 @@ const attemptFastPatch = async (
|
|
|
391
407
|
console.warn('[HMR] Angular fast update failed, falling back:', err);
|
|
392
408
|
|
|
393
409
|
return false;
|
|
410
|
+
} finally {
|
|
411
|
+
delete w.__ANGULAR_HMR_FAST_PATCH__;
|
|
394
412
|
}
|
|
395
413
|
};
|
|
396
414
|
|
|
397
|
-
|
|
398
|
-
|
|
415
|
+
/* Fast update — patch live component prototypes without destroying the app.
|
|
416
|
+
Returns true when at least one registered component was successfully
|
|
417
|
+
patched (and no patch failed); false means we couldn't fast-patch and
|
|
418
|
+
the caller should fall back to a full re-bootstrap.
|
|
419
|
+
Failures we explicitly fall back on:
|
|
420
|
+
- file's source isn't tracked in the component registry yet
|
|
421
|
+
- changed file has no Angular components (e.g. a service or routes file)
|
|
422
|
+
- any component's `applyUpdate` returned false (provider change, etc.)
|
|
423
|
+
- dynamic import failed */
|
|
424
|
+
const handleFastUpdate = async (message: HMRMessage) => {
|
|
399
425
|
const hmr = window.__ANGULAR_HMR__;
|
|
400
426
|
if (!hmr || !hmr.getRegistry) return false;
|
|
401
427
|
|
|
@@ -434,6 +460,45 @@ const _handleFastUpdate = async (message: HMRMessage) => {
|
|
|
434
460
|
// MAIN ENTRY POINT
|
|
435
461
|
// ============================================================
|
|
436
462
|
|
|
463
|
+
/* HMR updates are serialized through a single in-flight slot. While one
|
|
464
|
+
update is running (fast or full), additional incoming updates collapse
|
|
465
|
+
into one pending slot — only the latest matters because each rebuild
|
|
466
|
+
produces a chunk that supersedes prior ones for the same source file.
|
|
467
|
+
Without this, two rapid edits could:
|
|
468
|
+
- run two `startViewTransition`s and have the browser abort the first
|
|
469
|
+
mid-callback (the original "Transition was skipped" symptom), or
|
|
470
|
+
- run two `attemptFastPatch`s that both call `applyUpdate` on the same
|
|
471
|
+
registry entries, racing on prototype swaps. */
|
|
472
|
+
let activeMessage: Promise<void> | null = null;
|
|
473
|
+
let pendingMessage: HMRMessage | null = null;
|
|
474
|
+
|
|
475
|
+
const processMessage = async (message: HMRMessage) => {
|
|
476
|
+
const updateType = message.data.updateType || 'logic';
|
|
477
|
+
|
|
478
|
+
if (updateType === 'full') {
|
|
479
|
+
// Server signalled this requires a full reload — skip fast path.
|
|
480
|
+
await handleFullUpdate(message);
|
|
481
|
+
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Default 'logic' path: try fast-patch, fall back to full reload.
|
|
486
|
+
try {
|
|
487
|
+
const patched = await handleFastUpdate(message);
|
|
488
|
+
if (patched) return;
|
|
489
|
+
} catch (err) {
|
|
490
|
+
console.warn(
|
|
491
|
+
'[HMR] Angular fast update threw, falling back to full reload:',
|
|
492
|
+
err
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Fast path didn't apply — full re-bootstrap (loses in-memory app state
|
|
497
|
+
// like auth tokens; only happens when the fast path can't handle the
|
|
498
|
+
// change, e.g. routes/providers/services or a never-seen component).
|
|
499
|
+
await handleFullUpdate(message);
|
|
500
|
+
};
|
|
501
|
+
|
|
437
502
|
export const handleAngularUpdate = (message: HMRMessage) => {
|
|
438
503
|
if (detectCurrentFramework() !== 'angular') return;
|
|
439
504
|
|
|
@@ -443,6 +508,7 @@ export const handleAngularUpdate = (message: HMRMessage) => {
|
|
|
443
508
|
(updateType === 'style' || updateType === 'css-only') &&
|
|
444
509
|
message.data.cssUrl
|
|
445
510
|
) {
|
|
511
|
+
// CSS-only updates can run in parallel without breaking anything.
|
|
446
512
|
swapStylesheet(
|
|
447
513
|
message.data.cssUrl,
|
|
448
514
|
message.data.cssBaseName || '',
|
|
@@ -452,7 +518,22 @@ export const handleAngularUpdate = (message: HMRMessage) => {
|
|
|
452
518
|
return;
|
|
453
519
|
}
|
|
454
520
|
|
|
455
|
-
|
|
521
|
+
if (activeMessage) {
|
|
522
|
+
// Coalesce: an update is in flight, queue this one (replacing any
|
|
523
|
+
// earlier queued update, which is now stale).
|
|
524
|
+
pendingMessage = message;
|
|
525
|
+
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
activeMessage = processMessage(message).finally(() => {
|
|
530
|
+
activeMessage = null;
|
|
531
|
+
if (pendingMessage) {
|
|
532
|
+
const next = pendingMessage;
|
|
533
|
+
pendingMessage = null;
|
|
534
|
+
handleAngularUpdate(next);
|
|
535
|
+
}
|
|
536
|
+
});
|
|
456
537
|
};
|
|
457
538
|
|
|
458
539
|
// ============================================================
|
|
@@ -511,12 +592,21 @@ const tickAngularApp = () => {
|
|
|
511
592
|
}
|
|
512
593
|
};
|
|
513
594
|
|
|
514
|
-
|
|
595
|
+
/* `runWithViewTransition` wraps a callback in `document.startViewTransition`
|
|
596
|
+
for a smooth crossfade across full re-bootstraps. Queueing is NOT needed
|
|
597
|
+
here because `handleAngularUpdate` already serializes incoming messages
|
|
598
|
+
through the outer `activeMessage`/`pendingMessage` slots — only one
|
|
599
|
+
update runs at a time, so a new `startViewTransition` never aborts an
|
|
600
|
+
in-flight one mid-callback. */
|
|
601
|
+
const runWithViewTransition = async (updateFn: () => Promise<void>) => {
|
|
515
602
|
const doc: ViewTransitionDocument = document;
|
|
603
|
+
|
|
516
604
|
if (typeof doc.startViewTransition !== 'function') {
|
|
517
|
-
|
|
605
|
+
try {
|
|
606
|
+
await updateFn();
|
|
607
|
+
} catch (err) {
|
|
518
608
|
console.warn('[HMR] Angular update failed (non-fatal):', err);
|
|
519
|
-
}
|
|
609
|
+
}
|
|
520
610
|
|
|
521
611
|
return;
|
|
522
612
|
}
|
|
@@ -531,18 +621,41 @@ const runWithViewTransition = (updateFn: () => Promise<void>) => {
|
|
|
531
621
|
/* ignored */
|
|
532
622
|
}
|
|
533
623
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
624
|
+
let updatePromise: Promise<void> = Promise.resolve();
|
|
625
|
+
try {
|
|
626
|
+
const transition = doc.startViewTransition(() => {
|
|
627
|
+
updatePromise = updateFn();
|
|
537
628
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
.finished
|
|
542
|
-
|
|
629
|
+
return updatePromise;
|
|
630
|
+
});
|
|
631
|
+
// Wait for both the visual transition and the update callback.
|
|
632
|
+
// `transition.finished` rejects with AbortError when a new transition
|
|
633
|
+
// supersedes this one — swallow that since we serialize updates so
|
|
634
|
+
// it shouldn't happen, and even if it does we still want to wait
|
|
635
|
+
// for `updateFn` to complete before releasing the next update.
|
|
636
|
+
await Promise.all([
|
|
637
|
+
transition.finished.catch(() => {
|
|
638
|
+
/* skipped */
|
|
639
|
+
}),
|
|
640
|
+
updatePromise.catch((err) => {
|
|
641
|
+
console.warn('[HMR] Angular update failed (non-fatal):', err);
|
|
642
|
+
})
|
|
643
|
+
]);
|
|
644
|
+
} catch (err) {
|
|
645
|
+
console.warn('[HMR] Angular update failed (non-fatal):', err);
|
|
646
|
+
// If startViewTransition itself threw, run the update directly so
|
|
647
|
+
// HMR still applies (loses the crossfade but preserves correctness).
|
|
648
|
+
try {
|
|
649
|
+
await updateFn();
|
|
650
|
+
} catch (innerErr) {
|
|
651
|
+
console.warn('[HMR] Angular update failed (non-fatal):', innerErr);
|
|
652
|
+
}
|
|
653
|
+
} finally {
|
|
654
|
+
if (styleEl && styleEl.parentNode) styleEl.remove();
|
|
655
|
+
}
|
|
543
656
|
};
|
|
544
657
|
|
|
545
|
-
const handleFullUpdate = (message: HMRMessage) => {
|
|
658
|
+
const handleFullUpdate = async (message: HMRMessage) => {
|
|
546
659
|
const componentState = captureComponentState();
|
|
547
660
|
const scrollState = saveScrollState();
|
|
548
661
|
const formState = saveFormState();
|
|
@@ -574,5 +687,5 @@ const handleFullUpdate = (message: HMRMessage) => {
|
|
|
574
687
|
restoreScrollState(scrollState);
|
|
575
688
|
};
|
|
576
689
|
|
|
577
|
-
runWithViewTransition(doUpdate);
|
|
690
|
+
await runWithViewTransition(doUpdate);
|
|
578
691
|
};
|
|
@@ -48,8 +48,29 @@ type AngularHmrStats = {
|
|
|
48
48
|
readonly updateCount: number;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
/* The component registry MUST persist across chunk imports.
|
|
52
|
+
Each compiled page chunk inlines this `angularRuntime.ts` module — when
|
|
53
|
+
the HMR fast-patch dynamically `import()`s a new chunk, that chunk's
|
|
54
|
+
inlined runtime evaluates again. Without a window-level singleton, each
|
|
55
|
+
re-import would create a fresh `componentRegistry` Map, wipe out
|
|
56
|
+
prior registrations, and break subsequent fast-patches (the second
|
|
57
|
+
patch wouldn't find any registered components).
|
|
58
|
+
We anchor the registry on `globalThis.__ANGULAR_HMR_REGISTRY__` so every
|
|
59
|
+
chunk sees the same Map. */
|
|
60
|
+
type GlobalRegistryWindow = typeof globalThis & {
|
|
61
|
+
__ANGULAR_HMR_REGISTRY__?: Map<string, RegistryEntry>;
|
|
62
|
+
__ANGULAR_HMR_UPDATE_COUNT__?: { value: number };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const globalScope = globalThis as GlobalRegistryWindow;
|
|
66
|
+
|
|
67
|
+
const componentRegistry: Map<string, RegistryEntry> =
|
|
68
|
+
globalScope.__ANGULAR_HMR_REGISTRY__ ??
|
|
69
|
+
(globalScope.__ANGULAR_HMR_REGISTRY__ = new Map<string, RegistryEntry>());
|
|
70
|
+
|
|
71
|
+
const updateCounter: { value: number } =
|
|
72
|
+
globalScope.__ANGULAR_HMR_UPDATE_COUNT__ ??
|
|
73
|
+
(globalScope.__ANGULAR_HMR_UPDATE_COUNT__ = { value: 0 });
|
|
53
74
|
|
|
54
75
|
const hasInjectorProviderChanges = (
|
|
55
76
|
oldCtor: ComponentCtor,
|
|
@@ -149,7 +170,7 @@ const patchConstructor = (entry: RegistryEntry, newCtor: ComponentCtor) => {
|
|
|
149
170
|
throw new Error('Cannot patch non-configurable Angular metadata');
|
|
150
171
|
}
|
|
151
172
|
|
|
152
|
-
|
|
173
|
+
updateCounter.value++;
|
|
153
174
|
entry.updateCount++;
|
|
154
175
|
entry.registeredAt = Date.now();
|
|
155
176
|
};
|
|
@@ -211,7 +232,7 @@ const angularHmrStats: AngularHmrStats = {
|
|
|
211
232
|
return componentRegistry.size;
|
|
212
233
|
},
|
|
213
234
|
get updateCount() {
|
|
214
|
-
return
|
|
235
|
+
return updateCounter.value;
|
|
215
236
|
}
|
|
216
237
|
};
|
|
217
238
|
|
package/dist/index.js
CHANGED
|
@@ -44589,46 +44589,57 @@ var absoluteHttpTransferCacheOptions = {
|
|
|
44589
44589
|
}
|
|
44590
44590
|
};
|
|
44591
44591
|
|
|
44592
|
-
// Re-
|
|
44593
|
-
|
|
44594
|
-
|
|
44595
|
-
|
|
44596
|
-
|
|
44597
|
-
|
|
44598
|
-
//
|
|
44599
|
-
|
|
44600
|
-
|
|
44601
|
-
|
|
44602
|
-
|
|
44603
|
-
|
|
44604
|
-
|
|
44605
|
-
|
|
44606
|
-
|
|
44607
|
-
|
|
44608
|
-
|
|
44609
|
-
|
|
44610
|
-
|
|
44611
|
-
|
|
44612
|
-
|
|
44613
|
-
|
|
44614
|
-
|
|
44615
|
-
if (
|
|
44616
|
-
|
|
44617
|
-
|
|
44618
|
-
|
|
44619
|
-
|
|
44620
|
-
|
|
44621
|
-
|
|
44622
|
-
|
|
44623
|
-
|
|
44624
|
-
window.__ANGULAR_APP__ = appRef;
|
|
44592
|
+
// Re-export the page module so HMR fast-patch (in handlers/angular.ts) can
|
|
44593
|
+
// dynamically import this chunk and discover the freshly-built component
|
|
44594
|
+
// classes without needing a separate build artifact.
|
|
44595
|
+
export * from '${normalizedImportPath}';
|
|
44596
|
+
|
|
44597
|
+
// Re-Bootstrap HMR with View Transitions API.
|
|
44598
|
+
// Skipped during fast-patch: the HMR client sets
|
|
44599
|
+
// window.__ANGULAR_HMR_FAST_PATCH__ = true before \`import()\`-ing this
|
|
44600
|
+
// chunk so it can read the new component classes via \`export *\` above
|
|
44601
|
+
// without destroying the running app.
|
|
44602
|
+
if (!window.__ANGULAR_HMR_FAST_PATCH__) {
|
|
44603
|
+
if (window.__ANGULAR_APP__) {
|
|
44604
|
+
try { window.__ANGULAR_APP__.destroy(); } catch (_err) { /* ignore */ }
|
|
44605
|
+
window.__ANGULAR_APP__ = null;
|
|
44606
|
+
}
|
|
44607
|
+
|
|
44608
|
+
// Ensure root element exists after destroy (Angular removes it)
|
|
44609
|
+
var _sel = ${componentClassName}.\u0275cmp?.selectors?.[0]?.[0] || 'ng-app';
|
|
44610
|
+
if (!document.querySelector(_sel)) {
|
|
44611
|
+
(document.getElementById('root') || document.body).appendChild(document.createElement(_sel));
|
|
44612
|
+
}
|
|
44613
|
+
|
|
44614
|
+
var providers = [provideZonelessChangeDetection()];
|
|
44615
|
+
if (!window.__HMR_SKIP_HYDRATION__ && !pageHasIslands) {
|
|
44616
|
+
providers.push(provideClientHydration(withHttpTransferCacheOptions(absoluteHttpTransferCacheOptions)));
|
|
44617
|
+
}
|
|
44618
|
+
delete window.__HMR_SKIP_HYDRATION__;
|
|
44619
|
+
providers.push.apply(providers, pageProviders);
|
|
44620
|
+
providers.push.apply(providers, propProviders);
|
|
44621
|
+
window.__ABS_SLOT_HYDRATION_PENDING__ = pageHasRawStreamingSlots;
|
|
44622
|
+
|
|
44623
|
+
if (pageHasRawStreamingSlots) {
|
|
44625
44624
|
window.__ABS_SLOT_HYDRATION_PENDING__ = false;
|
|
44626
44625
|
if (typeof window.__ABS_SLOT_FLUSH__ === 'function') {
|
|
44627
44626
|
requestAnimationFrame(function() {
|
|
44628
44627
|
window.__ABS_SLOT_FLUSH__();
|
|
44629
44628
|
});
|
|
44630
44629
|
}
|
|
44631
|
-
}
|
|
44630
|
+
} else {
|
|
44631
|
+
bootstrapApplication(${componentClassName}, {
|
|
44632
|
+
providers: providers
|
|
44633
|
+
}).then(function (appRef) {
|
|
44634
|
+
window.__ANGULAR_APP__ = appRef;
|
|
44635
|
+
window.__ABS_SLOT_HYDRATION_PENDING__ = false;
|
|
44636
|
+
if (typeof window.__ABS_SLOT_FLUSH__ === 'function') {
|
|
44637
|
+
requestAnimationFrame(function() {
|
|
44638
|
+
window.__ABS_SLOT_FLUSH__();
|
|
44639
|
+
});
|
|
44640
|
+
}
|
|
44641
|
+
});
|
|
44642
|
+
}
|
|
44632
44643
|
}
|
|
44633
44644
|
`.trim() : `
|
|
44634
44645
|
import '@angular/compiler';
|
|
@@ -58538,5 +58549,5 @@ export {
|
|
|
58538
58549
|
ANGULAR_INIT_TIMEOUT_MS
|
|
58539
58550
|
};
|
|
58540
58551
|
|
|
58541
|
-
//# debugId=
|
|
58552
|
+
//# debugId=3B8AFA4EC6C4DEFD64756E2164756E21
|
|
58542
58553
|
//# sourceMappingURL=index.js.map
|