@absolutejs/absolute 0.19.0-beta.845 → 0.19.0-beta.847

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.
Files changed (37) hide show
  1. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  2. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  3. package/dist/angular/index.js +45 -23
  4. package/dist/angular/index.js.map +11 -10
  5. package/dist/angular/server.js +45 -23
  6. package/dist/angular/server.js.map +11 -10
  7. package/dist/build.js +955 -498
  8. package/dist/build.js.map +16 -13
  9. package/dist/cli/index.js +547 -286
  10. package/dist/client/index.js +16 -9
  11. package/dist/client/index.js.map +6 -5
  12. package/dist/dev/client/handlers/angular.ts +309 -19
  13. package/dist/dev/client/handlers/angularRuntime.ts +468 -0
  14. package/dist/dev/client/hmrToast.ts +150 -0
  15. package/dist/index.js +1002 -545
  16. package/dist/index.js.map +17 -14
  17. package/dist/islands/index.js +32 -11
  18. package/dist/islands/index.js.map +7 -6
  19. package/dist/react/index.js +32 -11
  20. package/dist/react/index.js.map +7 -6
  21. package/dist/src/build/rewriteImports.d.ts +6 -14
  22. package/dist/src/build/rewriteImportsPlugin.d.ts +48 -0
  23. package/dist/src/dev/angular/editTypeDetection.d.ts +8 -0
  24. package/dist/src/dev/pathUtils.d.ts +3 -0
  25. package/dist/src/utils/buildDirectoryLock.d.ts +26 -3
  26. package/dist/src/utils/loadConfig.d.ts +5 -0
  27. package/dist/src/utils/resolveDevPort.d.ts +21 -0
  28. package/dist/src/utils/runtimeMode.d.ts +3 -0
  29. package/dist/svelte/index.js +32 -11
  30. package/dist/svelte/index.js.map +7 -6
  31. package/dist/svelte/server.js +17 -3
  32. package/dist/svelte/server.js.map +3 -3
  33. package/dist/types/build.d.ts +15 -0
  34. package/dist/types/globals.d.ts +12 -0
  35. package/dist/vue/index.js +32 -11
  36. package/dist/vue/index.js.map +7 -6
  37. package/package.json +1 -1
@@ -32,22 +32,50 @@ import {
32
32
  restoreScrollState
33
33
  } from '../domState';
34
34
  import { detectCurrentFramework, findIndexPath } from '../frameworkDetect';
35
+ import { showHmrToast } from '../hmrToast';
36
+
37
+ type AngularUpdateType =
38
+ | 'template'
39
+ | 'style-component'
40
+ | 'class-component'
41
+ | 'service-method-only'
42
+ | 'service-with-side-effects'
43
+ | 'route'
44
+ | 'reboot'
45
+ // Legacy types kept for back-compat with global CSS edits and pre-2.1
46
+ // builds. Treated as fast-path triggers (style → swap stylesheet,
47
+ // logic → fast-patch + reboot fallback).
48
+ | 'style'
49
+ | 'css-only'
50
+ | 'logic'
51
+ | 'full';
35
52
 
36
53
  type HMRMessage = {
37
54
  data: {
38
55
  cssBaseName?: string;
39
56
  cssUrl?: string;
57
+ editSourceFile?: string;
40
58
  html?: string;
41
59
  manifest?: Record<string, string>;
42
60
  pageModuleUrl?: string;
61
+ reason?: string;
43
62
  serverDuration?: number;
44
63
  sourceFile?: string;
45
- updateType?: string;
64
+ updateType?: AngularUpdateType;
46
65
  };
47
66
  };
48
67
 
49
68
  type AngularHmrApi = {
50
69
  applyUpdate: (id: string, newCtor: unknown) => boolean;
70
+ applyStyleUpdate?: (id: string, newCtor: unknown) => boolean;
71
+ applyTemplateUpdate?: (id: string, newCtor: unknown) => boolean;
72
+ applyServiceUpdate?: (id: string, newCtor: unknown) => boolean;
73
+ beginStyleUpdateBatch?: () => void;
74
+ endStyleUpdateBatch?: () => Array<{ id: string; ok: boolean }>;
75
+ beginTemplateUpdateBatch?: () => void;
76
+ endTemplateUpdateBatch?: () => Array<{ id: string; ok: boolean }>;
77
+ beginServiceUpdateBatch?: () => void;
78
+ endServiceUpdateBatch?: () => Array<{ id: string; ok: boolean }>;
51
79
  getRegistry?: () => Map<string, unknown>;
52
80
  refresh: () => void;
53
81
  hasPageExportsChanged?: (sourceId: string) => boolean;
@@ -320,45 +348,307 @@ const handleFastUpdate = async (message: HMRMessage) => {
320
348
  let activeMessage: Promise<void> | null = null;
321
349
  let pendingMessage: HMRMessage | null = null;
322
350
 
323
- const processMessage = async (message: HMRMessage) => {
324
- const updateType = message.data.updateType || 'logic';
325
-
326
- if (updateType === 'full') {
327
- // Server signalled this requires a full reload — skip fast path.
328
- await handleFullUpdate(message);
351
+ /* Stub handlers for fast paths that aren't implemented yet. Each one
352
+ * returns false to signal "couldn't handle, fall through to reboot",
353
+ * letting Phase 2 ship piecewise — we land §2.1 (this dispatch table)
354
+ * first, then §2.4 / §2.3 / §2.2 fill in the stubs one at a time
355
+ * without further plumbing changes. */
356
+ /* Template HMR — re-imports the rebuilt page chunk under FAST_PATCH +
357
+ * TEMPLATE_UPDATE_MODE, then asks the runtime to swap the template
358
+ * subgraph of `ɵcmp` (template factory, slot counts, queries,
359
+ * dependencies, host bindings) on every re-registered component.
360
+ *
361
+ * Live instances keep their state, services, queries, and DI tokens —
362
+ * only the rendered output changes. After all components are patched,
363
+ * a single `refresh()` call walks the subtree, marks each patched
364
+ * component dirty, and ticks the app to repaint.
365
+ *
366
+ * Returns false on any partial failure so the orchestrator falls
367
+ * through to a coherent reboot. */
368
+ type TemplateUpdateWindow = FastPatchWindow & {
369
+ __ANGULAR_HMR_TEMPLATE_UPDATE_MODE__?: boolean;
370
+ };
329
371
 
330
- return;
372
+ const handleTemplateUpdate = async (message: HMRMessage): Promise<boolean> => {
373
+ const hmr = window.__ANGULAR_HMR__;
374
+ if (
375
+ !hmr ||
376
+ !hmr.applyTemplateUpdate ||
377
+ !hmr.beginTemplateUpdateBatch ||
378
+ !hmr.endTemplateUpdateBatch
379
+ ) {
380
+ return false;
331
381
  }
332
382
 
333
- // Default 'logic' path: try fast-patch, fall back to full reload.
383
+ const indexPath = findIndexPath(
384
+ message.data.manifest,
385
+ message.data.sourceFile,
386
+ 'angular'
387
+ );
388
+ if (!indexPath) return false;
389
+
390
+ const w = window as TemplateUpdateWindow;
391
+ w.__ANGULAR_HMR_FAST_PATCH__ = true;
392
+ w.__ANGULAR_HMR_TEMPLATE_UPDATE_MODE__ = true;
393
+ hmr.beginTemplateUpdateBatch();
394
+
395
+ const origWarn = suppressNg0912();
334
396
  try {
335
- const patched = await handleFastUpdate(message);
336
- if (patched) return;
397
+ await import(`${indexPath}?t=${Date.now()}`);
398
+
399
+ // Page-level routes/providers cannot ride a template update.
400
+ if (hmr.hasPageExportsChanged?.(message.data.sourceFile || '')) {
401
+ return false;
402
+ }
403
+
404
+ const results = hmr.endTemplateUpdateBatch();
405
+ if (results.length === 0) return false;
406
+ if (!results.every((r) => r.ok)) return false;
407
+
408
+ console.warn = origWarn;
409
+ hmr.refresh();
410
+
411
+ return true;
337
412
  } catch (err) {
413
+ console.warn = origWarn;
338
414
  console.warn(
339
- '[HMR] Angular fast update threw, falling back to full reload:',
415
+ '[HMR] Angular template update failed, falling back:',
340
416
  err
341
417
  );
418
+ return false;
419
+ } finally {
420
+ delete w.__ANGULAR_HMR_FAST_PATCH__;
421
+ delete w.__ANGULAR_HMR_TEMPLATE_UPDATE_MODE__;
422
+ console.warn = origWarn;
423
+ }
424
+ };
425
+
426
+ /* Component-style HMR — re-imports the rebuilt page chunk under the
427
+ * combined `FAST_PATCH` and `STYLE_UPDATE_MODE` flags so:
428
+ * - the chunk's bootstrap section is skipped (FAST_PATCH)
429
+ * - every per-file auto-registration block routes its new ctor
430
+ * through `applyStyleUpdate` instead of a no-op (STYLE_UPDATE_MODE)
431
+ *
432
+ * The registration-block path is the only way to reach CHILD
433
+ * components — the page chunk's `export *` only re-exports the page's
434
+ * own module, so a top-level export walk would miss imported
435
+ * components like `LayoutComponent`. Each compiled .ts file emits a
436
+ * registration block for its own component classes, so the chunk
437
+ * covers the whole tree on re-evaluation.
438
+ *
439
+ * Returns true iff every component the chunk re-registered swapped
440
+ * its styles cleanly. Any failure (Shadow DOM, length change, missing
441
+ * live <style> tag) → reboot. The transactional check inside
442
+ * `applyStyleUpdate` means we never apply a partial update — either
443
+ * the page restyles coherently or we reboot. */
444
+ type StyleUpdateWindow = FastPatchWindow & {
445
+ __ANGULAR_HMR_STYLE_UPDATE_MODE__?: boolean;
446
+ };
447
+
448
+ const handleComponentStyleUpdate = async (
449
+ message: HMRMessage
450
+ ): Promise<boolean> => {
451
+ const hmr = window.__ANGULAR_HMR__;
452
+ if (
453
+ !hmr ||
454
+ !hmr.applyStyleUpdate ||
455
+ !hmr.beginStyleUpdateBatch ||
456
+ !hmr.endStyleUpdateBatch
457
+ ) {
458
+ return false;
459
+ }
460
+
461
+ const indexPath = findIndexPath(
462
+ message.data.manifest,
463
+ message.data.sourceFile,
464
+ 'angular'
465
+ );
466
+ if (!indexPath) return false;
467
+
468
+ const w = window as StyleUpdateWindow;
469
+ w.__ANGULAR_HMR_FAST_PATCH__ = true;
470
+ w.__ANGULAR_HMR_STYLE_UPDATE_MODE__ = true;
471
+ hmr.beginStyleUpdateBatch();
472
+
473
+ try {
474
+ await import(`${indexPath}?t=${Date.now()}`);
475
+
476
+ // Page-level routes/providers cannot ride a style update — they
477
+ // are read once during bootstrap. If they changed in this
478
+ // rebuild, reboot rather than risk a stale router/injector.
479
+ if (hmr.hasPageExportsChanged?.(message.data.sourceFile || '')) {
480
+ return false;
481
+ }
482
+
483
+ const results = hmr.endStyleUpdateBatch();
484
+ if (results.length === 0) {
485
+ // Chunk re-evaluated but no component re-registered — likely
486
+ // the edited file isn't actually used by this page, or the
487
+ // chunk skipped its registration block. Fall back to reboot.
488
+ return false;
489
+ }
490
+ return results.every((r) => r.ok);
491
+ } catch (err) {
492
+ console.warn('[HMR] Angular style update failed, falling back:', err);
493
+ return false;
494
+ } finally {
495
+ delete w.__ANGULAR_HMR_FAST_PATCH__;
496
+ delete w.__ANGULAR_HMR_STYLE_UPDATE_MODE__;
497
+ }
498
+ };
499
+
500
+ /* Service HMR — re-imports the rebuilt page chunk under FAST_PATCH +
501
+ * SERVICE_UPDATE_MODE so the page's auto-registration block routes
502
+ * each new service ctor through `applyServiceUpdate`. The runtime
503
+ * does prototype method-swap (always) and best-effort field merge on
504
+ * the live singleton (when reachable via the root injector and
505
+ * donor-instantiable). Live components keep their references — they
506
+ * just call into the new method bodies on next invocation.
507
+ *
508
+ * This path only runs when the server-side classifier returned
509
+ * `service-method-only` — services with side-effecting constructors
510
+ * never get here, so the live singleton's existing subscriptions /
511
+ * timers / listeners stay intact and we don't double-register them. */
512
+ type ServiceUpdateWindow = FastPatchWindow & {
513
+ __ANGULAR_HMR_SERVICE_UPDATE_MODE__?: boolean;
514
+ };
515
+
516
+ const handleServiceMethodSwap = async (
517
+ message: HMRMessage
518
+ ): Promise<boolean> => {
519
+ const hmr = window.__ANGULAR_HMR__;
520
+ if (
521
+ !hmr ||
522
+ !hmr.applyServiceUpdate ||
523
+ !hmr.beginServiceUpdateBatch ||
524
+ !hmr.endServiceUpdateBatch
525
+ ) {
526
+ return false;
527
+ }
528
+
529
+ const indexPath = findIndexPath(
530
+ message.data.manifest,
531
+ message.data.sourceFile,
532
+ 'angular'
533
+ );
534
+ if (!indexPath) return false;
535
+
536
+ const w = window as ServiceUpdateWindow;
537
+ w.__ANGULAR_HMR_FAST_PATCH__ = true;
538
+ w.__ANGULAR_HMR_SERVICE_UPDATE_MODE__ = true;
539
+ hmr.beginServiceUpdateBatch();
540
+
541
+ try {
542
+ await import(`${indexPath}?t=${Date.now()}`);
543
+
544
+ // Page-level routes/providers cannot ride a service update.
545
+ if (hmr.hasPageExportsChanged?.(message.data.sourceFile || '')) {
546
+ return false;
547
+ }
548
+
549
+ const results = hmr.endServiceUpdateBatch();
550
+ if (results.length === 0) return false;
551
+ if (!results.every((r) => r.ok)) return false;
552
+
553
+ // New method bodies might compute new values for observable-fed
554
+ // fields, so the existing component subtree should re-render.
555
+ // `refresh()` ticks the app + marks any pending fast-patch
556
+ // components dirty.
557
+ hmr.refresh();
558
+
559
+ return true;
560
+ } catch (err) {
561
+ console.warn('[HMR] Angular service update failed, falling back:', err);
562
+ return false;
563
+ } finally {
564
+ delete w.__ANGULAR_HMR_FAST_PATCH__;
565
+ delete w.__ANGULAR_HMR_SERVICE_UPDATE_MODE__;
566
+ }
567
+ };
568
+
569
+ const logRebootReason = (message: HMRMessage) => {
570
+ const reason = message.data.reason;
571
+ const updateType = message.data.updateType;
572
+ if (!reason && !updateType) return;
573
+ console.info(
574
+ `[HMR] Angular reboot — ${updateType ?? 'unknown'}: ${reason ?? '(no reason given)'}`
575
+ );
576
+ // Surface the same info as a brief on-page toast so the developer
577
+ // sees it without opening devtools. Toasts auto-dismiss after a
578
+ // few seconds.
579
+ showHmrToast({
580
+ editSourceFile: message.data.editSourceFile,
581
+ reason,
582
+ updateType
583
+ });
584
+ };
585
+
586
+ const processMessage = async (message: HMRMessage) => {
587
+ const updateType = message.data.updateType ?? 'logic';
588
+
589
+ switch (updateType) {
590
+ case 'template': {
591
+ const ok = await handleTemplateUpdate(message);
592
+ if (ok) return;
593
+ break;
594
+ }
595
+ case 'style-component': {
596
+ const ok = await handleComponentStyleUpdate(message);
597
+ if (ok) return;
598
+ break;
599
+ }
600
+ case 'service-method-only': {
601
+ const ok = await handleServiceMethodSwap(message);
602
+ if (ok) return;
603
+ break;
604
+ }
605
+ case 'class-component':
606
+ case 'logic': {
607
+ // Existing prototype-swap fast-patch. Falls through to reboot
608
+ // on any failure (provider change, page-export change, etc.).
609
+ try {
610
+ const patched = await handleFastUpdate(message);
611
+ if (patched) return;
612
+ } catch (err) {
613
+ console.warn(
614
+ '[HMR] Angular fast update threw, falling back to reboot:',
615
+ err
616
+ );
617
+ }
618
+ break;
619
+ }
620
+ case 'service-with-side-effects':
621
+ case 'route':
622
+ case 'reboot':
623
+ case 'full':
624
+ // Explicit reboot signals — skip the fast path entirely and
625
+ // log why so the developer can see the classification.
626
+ break;
627
+ default:
628
+ // Unknown update type — be conservative and reboot.
629
+ break;
342
630
  }
343
631
 
344
- // Fast path didn't apply — full re-bootstrap. Components and services
345
- // that opted into `preserveAcrossHmr(this)` keep their state; anything
346
- // that didn't opt in is reset to its class-field defaults. The summary
347
- // log emitted by `endHmrReboot` after the reboot tells the developer
348
- // which classes were preserved.
632
+ // Falling through: full re-bootstrap. Components and services that
633
+ // opted into `preserveAcrossHmr(this)` keep their state; anything
634
+ // that didn't opt in is reset to its class-field defaults. The
635
+ // summary log emitted by `endHmrReboot` lists what was preserved.
636
+ logRebootReason(message);
349
637
  await handleFullUpdate(message);
350
638
  };
351
639
 
352
640
  export const handleAngularUpdate = (message: HMRMessage) => {
353
641
  if (detectCurrentFramework() !== 'angular') return;
354
642
 
355
- const updateType = message.data.updateType || 'logic';
643
+ const updateType = message.data.updateType ?? 'logic';
356
644
 
357
645
  if (
358
646
  (updateType === 'style' || updateType === 'css-only') &&
359
647
  message.data.cssUrl
360
648
  ) {
361
- // CSS-only updates can run in parallel without breaking anything.
649
+ // Global CSS-only updates: swap the stylesheet in place. These
650
+ // run outside the activeMessage queue because they can't conflict
651
+ // with an in-flight component update.
362
652
  swapStylesheet(
363
653
  message.data.cssUrl,
364
654
  message.data.cssBaseName || '',