@nativescript/vite 8.0.0-alpha.30 → 8.0.0-alpha.31

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 (162) hide show
  1. package/configuration/angular.js +8 -8
  2. package/configuration/angular.js.map +1 -1
  3. package/configuration/solid.js +2 -2
  4. package/configuration/solid.js.map +1 -1
  5. package/hmr/client/framework-client-strategy.d.ts +73 -0
  6. package/hmr/client/framework-client-strategy.js +19 -0
  7. package/hmr/client/framework-client-strategy.js.map +1 -0
  8. package/hmr/client/index.js +77 -186
  9. package/hmr/client/index.js.map +1 -1
  10. package/hmr/client/utils.js.map +1 -1
  11. package/hmr/entry-runtime.js.map +1 -1
  12. package/hmr/frameworks/angular/build/angular-linker.js.map +1 -0
  13. package/hmr/frameworks/angular/build/inject-component-hmr-registration.js.map +1 -0
  14. package/hmr/frameworks/angular/build/inject-hmr-vite-ignore.js.map +1 -0
  15. package/hmr/frameworks/angular/build/inline-decorator-component-templates.js.map +1 -0
  16. package/hmr/frameworks/angular/build/js-lexer.js.map +1 -0
  17. package/hmr/frameworks/angular/build/shared-linker.js.map +1 -0
  18. package/hmr/frameworks/angular/build/synthesize-decorator-ctor-parameters.js.map +1 -0
  19. package/hmr/frameworks/angular/build/synthesize-injectable-factories.js.map +1 -0
  20. package/hmr/frameworks/angular/build/util.js.map +1 -0
  21. package/hmr/frameworks/angular/client/index.js.map +1 -1
  22. package/hmr/frameworks/angular/client/strategy.d.ts +9 -0
  23. package/hmr/frameworks/angular/client/strategy.js +19 -0
  24. package/hmr/frameworks/angular/client/strategy.js.map +1 -0
  25. package/hmr/frameworks/angular/server/angular-root-component.js.map +1 -0
  26. package/hmr/frameworks/angular/server/strategy.js +440 -2
  27. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  28. package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.js +2 -2
  29. package/hmr/frameworks/angular/server/websocket-angular-entry.js.map +1 -0
  30. package/hmr/{server → frameworks/angular/server}/websocket-angular-hot-update.d.ts +0 -11
  31. package/hmr/{server → frameworks/angular/server}/websocket-angular-hot-update.js +3 -80
  32. package/hmr/frameworks/angular/server/websocket-angular-hot-update.js.map +1 -0
  33. package/hmr/frameworks/solid/build/solid-jsx-deps.js.map +1 -0
  34. package/hmr/frameworks/solid/server/strategy.js +360 -1
  35. package/hmr/frameworks/solid/server/strategy.js.map +1 -1
  36. package/hmr/frameworks/typescript/server/strategy.js +27 -0
  37. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  38. package/hmr/frameworks/vue/client/index.js.map +1 -1
  39. package/hmr/frameworks/vue/client/strategy.d.ts +7 -0
  40. package/hmr/frameworks/vue/client/strategy.js +83 -0
  41. package/hmr/frameworks/vue/client/strategy.js.map +1 -0
  42. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -0
  43. package/hmr/frameworks/vue/server/sfc-route-assemble.d.ts +7 -0
  44. package/hmr/{server/websocket-sfc.js → frameworks/vue/server/sfc-route-assemble.js} +19 -536
  45. package/hmr/frameworks/vue/server/sfc-route-assemble.js.map +1 -0
  46. package/hmr/frameworks/vue/server/sfc-route-meta.d.ts +7 -0
  47. package/hmr/frameworks/vue/server/sfc-route-meta.js +80 -0
  48. package/hmr/frameworks/vue/server/sfc-route-meta.js.map +1 -0
  49. package/hmr/frameworks/vue/server/sfc-route-serve.d.ts +8 -0
  50. package/hmr/frameworks/vue/server/sfc-route-serve.js +457 -0
  51. package/hmr/frameworks/vue/server/sfc-route-serve.js.map +1 -0
  52. package/hmr/frameworks/vue/server/sfc-route-shared.d.ts +19 -0
  53. package/hmr/frameworks/vue/server/sfc-route-shared.js +14 -0
  54. package/hmr/frameworks/vue/server/sfc-route-shared.js.map +1 -0
  55. package/hmr/frameworks/vue/server/strategy.js +244 -0
  56. package/hmr/frameworks/vue/server/strategy.js.map +1 -1
  57. package/hmr/frameworks/vue/server/websocket-sfc.d.ts +15 -0
  58. package/hmr/frameworks/vue/server/websocket-sfc.js +20 -0
  59. package/hmr/frameworks/vue/server/websocket-sfc.js.map +1 -0
  60. package/hmr/server/device-transform-helpers.d.ts +24 -0
  61. package/hmr/server/device-transform-helpers.js +327 -0
  62. package/hmr/server/device-transform-helpers.js.map +1 -0
  63. package/hmr/server/framework-strategy.d.ts +95 -1
  64. package/hmr/server/hmr-module-graph.js.map +1 -1
  65. package/hmr/server/import-map.d.ts +11 -2
  66. package/hmr/server/import-map.js +21 -54
  67. package/hmr/server/import-map.js.map +1 -1
  68. package/hmr/server/index.js +7 -17
  69. package/hmr/server/index.js.map +1 -1
  70. package/hmr/server/process-code-for-device.d.ts +15 -0
  71. package/hmr/server/process-code-for-device.js +654 -0
  72. package/hmr/server/process-code-for-device.js.map +1 -0
  73. package/hmr/server/rewrite-imports.d.ts +2 -0
  74. package/hmr/server/rewrite-imports.js +604 -0
  75. package/hmr/server/rewrite-imports.js.map +1 -0
  76. package/hmr/server/transform-cache-invalidation.d.ts +11 -0
  77. package/hmr/server/transform-cache-invalidation.js +84 -0
  78. package/hmr/server/transform-cache-invalidation.js.map +1 -0
  79. package/hmr/server/websocket-device-transform.d.ts +3 -21
  80. package/hmr/server/websocket-device-transform.js +6 -1569
  81. package/hmr/server/websocket-device-transform.js.map +1 -1
  82. package/hmr/server/websocket-hmr-pending.d.ts +2 -8
  83. package/hmr/server/websocket-hmr-pending.js.map +1 -1
  84. package/hmr/server/websocket-hot-update.d.ts +40 -14
  85. package/hmr/server/websocket-hot-update.js +24 -854
  86. package/hmr/server/websocket-hot-update.js.map +1 -1
  87. package/hmr/server/websocket-import-map-route.js +3 -1
  88. package/hmr/server/websocket-import-map-route.js.map +1 -1
  89. package/hmr/server/websocket-ns-core.d.ts +4 -4
  90. package/hmr/server/websocket-ns-core.js +3 -2
  91. package/hmr/server/websocket-ns-core.js.map +1 -1
  92. package/hmr/server/websocket-ns-entry.d.ts +0 -1
  93. package/hmr/server/websocket-ns-entry.js +1 -1
  94. package/hmr/server/websocket-ns-entry.js.map +1 -1
  95. package/hmr/server/websocket-ns-m.d.ts +0 -1
  96. package/hmr/server/websocket-ns-m.js +24 -129
  97. package/hmr/server/websocket-ns-m.js.map +1 -1
  98. package/hmr/server/websocket-vendor-unifier.d.ts +0 -1
  99. package/hmr/server/websocket-vendor-unifier.js +2 -1
  100. package/hmr/server/websocket-vendor-unifier.js.map +1 -1
  101. package/hmr/server/websocket.d.ts +11 -12
  102. package/hmr/server/websocket.js +25 -33
  103. package/hmr/server/websocket.js.map +1 -1
  104. package/hmr/shared/ns-globals.d.ts +118 -0
  105. package/hmr/shared/ns-globals.js +27 -0
  106. package/hmr/shared/ns-globals.js.map +1 -0
  107. package/hmr/shared/protocol.d.ts +136 -0
  108. package/hmr/shared/protocol.js +28 -0
  109. package/hmr/shared/protocol.js.map +1 -0
  110. package/hmr/shared/vendor/manifest-collect.d.ts +0 -28
  111. package/hmr/shared/vendor/manifest-collect.js +2 -2
  112. package/hmr/shared/vendor/manifest-collect.js.map +1 -1
  113. package/hmr/shared/vendor/manifest.d.ts +1 -3
  114. package/hmr/shared/vendor/manifest.js +1 -3
  115. package/hmr/shared/vendor/manifest.js.map +1 -1
  116. package/hmr/shared/vendor/vendor-esbuild-plugins.js +1 -1
  117. package/hmr/shared/vendor/vendor-esbuild-plugins.js.map +1 -1
  118. package/package.json +1 -1
  119. package/helpers/angular/angular-linker.js.map +0 -1
  120. package/helpers/angular/inject-component-hmr-registration.js.map +0 -1
  121. package/helpers/angular/inject-hmr-vite-ignore.js.map +0 -1
  122. package/helpers/angular/inline-decorator-component-templates.js.map +0 -1
  123. package/helpers/angular/js-lexer.js.map +0 -1
  124. package/helpers/angular/shared-linker.js.map +0 -1
  125. package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +0 -1
  126. package/helpers/angular/synthesize-injectable-factories.js.map +0 -1
  127. package/helpers/angular/util.js.map +0 -1
  128. package/helpers/prelink-angular.d.ts +0 -2
  129. package/helpers/prelink-angular.js +0 -96
  130. package/helpers/prelink-angular.js.map +0 -1
  131. package/helpers/solid-jsx-deps.js.map +0 -1
  132. package/hmr/client/vue-sfc-update-overlay.js.map +0 -1
  133. package/hmr/server/angular-root-component.js.map +0 -1
  134. package/hmr/server/websocket-angular-entry.js.map +0 -1
  135. package/hmr/server/websocket-angular-hot-update.js.map +0 -1
  136. package/hmr/server/websocket-sfc.d.ts +0 -24
  137. package/hmr/server/websocket-sfc.js.map +0 -1
  138. /package/{helpers/angular → hmr/frameworks/angular/build}/angular-linker.d.ts +0 -0
  139. /package/{helpers/angular → hmr/frameworks/angular/build}/angular-linker.js +0 -0
  140. /package/{helpers/angular → hmr/frameworks/angular/build}/inject-component-hmr-registration.d.ts +0 -0
  141. /package/{helpers/angular → hmr/frameworks/angular/build}/inject-component-hmr-registration.js +0 -0
  142. /package/{helpers/angular → hmr/frameworks/angular/build}/inject-hmr-vite-ignore.d.ts +0 -0
  143. /package/{helpers/angular → hmr/frameworks/angular/build}/inject-hmr-vite-ignore.js +0 -0
  144. /package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.d.ts +0 -0
  145. /package/{helpers/angular → hmr/frameworks/angular/build}/inline-decorator-component-templates.js +0 -0
  146. /package/{helpers/angular → hmr/frameworks/angular/build}/js-lexer.d.ts +0 -0
  147. /package/{helpers/angular → hmr/frameworks/angular/build}/js-lexer.js +0 -0
  148. /package/{helpers/angular → hmr/frameworks/angular/build}/shared-linker.d.ts +0 -0
  149. /package/{helpers/angular → hmr/frameworks/angular/build}/shared-linker.js +0 -0
  150. /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-decorator-ctor-parameters.d.ts +0 -0
  151. /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-decorator-ctor-parameters.js +0 -0
  152. /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.d.ts +0 -0
  153. /package/{helpers/angular → hmr/frameworks/angular/build}/synthesize-injectable-factories.js +0 -0
  154. /package/{helpers/angular → hmr/frameworks/angular/build}/util.d.ts +0 -0
  155. /package/{helpers/angular → hmr/frameworks/angular/build}/util.js +0 -0
  156. /package/hmr/{server → frameworks/angular/server}/angular-root-component.d.ts +0 -0
  157. /package/hmr/{server → frameworks/angular/server}/angular-root-component.js +0 -0
  158. /package/hmr/{server → frameworks/angular/server}/websocket-angular-entry.d.ts +0 -0
  159. /package/{helpers → hmr/frameworks/solid/build}/solid-jsx-deps.d.ts +0 -0
  160. /package/{helpers → hmr/frameworks/solid/build}/solid-jsx-deps.js +0 -0
  161. /package/hmr/{client → frameworks/vue/client}/vue-sfc-update-overlay.d.ts +0 -0
  162. /package/hmr/{client → frameworks/vue/client}/vue-sfc-update-overlay.js +0 -0
@@ -172,7 +172,6 @@ catch (err) {
172
172
  // vitest, etc.) or when the user opted out via
173
173
  // `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
174
174
  import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
175
- import { driveVueSfcUpdateOverlay } from './vue-sfc-update-overlay.js';
176
175
  function setHmrPendingOverlay(filePath) {
177
176
  applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
178
177
  }
@@ -217,19 +216,19 @@ function markHmrConnectionHealthy() {
217
216
  hideConnectionOverlay();
218
217
  }
219
218
  }
220
- /**
221
- * Flavor hooks
222
- */
223
- import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
224
- import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
225
- switch (TARGET_FLAVOR) {
226
- case 'vue':
227
- installNsVueDevShims();
228
- break;
229
- case 'angular':
230
- installAngularHmrClientHooks();
231
- break;
232
- }
219
+ let CLIENT_STRATEGY;
220
+ const CLIENT_STRATEGY_READY = TARGET_FLAVOR === 'vue' || TARGET_FLAVOR === 'angular'
221
+ ? import(`../frameworks/${TARGET_FLAVOR}/client/strategy.js`)
222
+ .then((mod) => {
223
+ CLIENT_STRATEGY = mod && (mod[`${TARGET_FLAVOR}ClientStrategy`] ?? mod.default);
224
+ if (VERBOSE)
225
+ console.log('[hmr-client] client strategy loaded for flavor:', TARGET_FLAVOR);
226
+ CLIENT_STRATEGY?.install();
227
+ })
228
+ .catch((err) => {
229
+ console.warn('[hmr-client] failed to load client strategy for', TARGET_FLAVOR, err);
230
+ })
231
+ : Promise.resolve();
233
232
  // Track whether we've mounted an initial app root yet in HTTP-only boot
234
233
  let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
235
234
  // Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
@@ -392,39 +391,7 @@ function applyFullGraph(payload) {
392
391
  }
393
392
  return;
394
393
  }
395
- let candidate = null;
396
- switch (TARGET_FLAVOR) {
397
- case 'vue': {
398
- const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
399
- if (appEntry && Array.isArray(appEntry.deps)) {
400
- const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
401
- if (vueDep)
402
- candidate = vueDep;
403
- }
404
- if (!candidate) {
405
- for (const id of graph.keys()) {
406
- if (/\.vue$/i.test(id)) {
407
- candidate = id;
408
- break;
409
- }
410
- }
411
- }
412
- // Fallback: when the module graph is empty (Vite 7+ may not populate it
413
- // before the first full-graph broadcast), check the SFC artifact registry
414
- // which is populated from the ns:vue-sfc-registry message.
415
- if (!candidate && sfcArtifactMap.size > 0) {
416
- for (const id of sfcArtifactMap.keys()) {
417
- if (/\.vue$/i.test(id)) {
418
- candidate = id;
419
- if (VERBOSE)
420
- console.log('[hmr][init] rescue candidate from SFC registry:', id);
421
- break;
422
- }
423
- }
424
- }
425
- break;
426
- }
427
- }
394
+ const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
428
395
  if (!candidate)
429
396
  return;
430
397
  initialMounting = true;
@@ -435,10 +402,8 @@ function applyFullGraph(payload) {
435
402
  (async () => {
436
403
  try {
437
404
  let comp = null;
438
- switch (TARGET_FLAVOR) {
439
- case 'vue':
440
- comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
441
- break;
405
+ if (CLIENT_STRATEGY?.loadComponentForMount) {
406
+ comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount_rescue');
442
407
  }
443
408
  if (!comp)
444
409
  return;
@@ -479,41 +444,11 @@ function applyFullGraph(payload) {
479
444
  // Only allow initial mount when explicitly enabled. Rely on the app's own main entry start() for the first mount
480
445
  // to avoid double-mount races that can cause duplicate navigation logs.
481
446
  if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
482
- let candidate = null;
483
- switch (TARGET_FLAVOR) {
484
- case 'vue': {
485
- const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
486
- if (appEntry && Array.isArray(appEntry.deps)) {
487
- const vueDep = appEntry.deps.find((d) => typeof d === 'string' && /\.vue$/i.test(d));
488
- if (vueDep)
489
- candidate = vueDep;
490
- }
491
- if (!candidate) {
492
- for (const id of graph.keys()) {
493
- if (/\.vue$/i.test(id)) {
494
- candidate = id;
495
- break;
496
- }
497
- }
498
- }
499
- // Fallback: SFC registry (same as rescue mount above)
500
- if (!candidate && sfcArtifactMap.size > 0) {
501
- for (const id of sfcArtifactMap.keys()) {
502
- if (/\.vue$/i.test(id)) {
503
- candidate = id;
504
- if (VERBOSE)
505
- console.log('[hmr][init] initial mount candidate from SFC registry:', id);
506
- break;
507
- }
508
- }
509
- }
510
- break;
511
- }
512
- case 'typescript': {
513
- // For TS flavor, do not perform client-driven initial mount; rely on Application.run.
514
- return;
515
- }
447
+ if (TARGET_FLAVOR === 'typescript') {
448
+ // For TS flavor, do not perform client-driven initial mount; rely on Application.run.
449
+ return;
516
450
  }
451
+ const candidate = CLIENT_STRATEGY?.selectMountCandidate?.({ graph, appMainEntrySpec: APP_MAIN_ENTRY_SPEC }) ?? null;
517
452
  if (candidate) {
518
453
  // Mark initial-mount in progress (both module-local and global) BEFORE scheduling async work
519
454
  initialMounting = true;
@@ -558,21 +493,19 @@ function applyFullGraph(payload) {
558
493
  }
559
494
  catch { }
560
495
  let comp = null;
561
- switch (TARGET_FLAVOR) {
562
- case 'vue':
563
- comp = await loadSfcComponent(candidate, 'initial_mount');
564
- break;
565
- case 'typescript':
566
- try {
567
- const url = await requestModuleFromServer(candidate);
568
- const mod = await import(/* @vite-ignore */ url);
569
- comp = mod && (mod.default || mod);
570
- }
571
- catch (e) {
572
- if (VERBOSE)
573
- console.warn('[hmr][init] TS initial mount failed to import', candidate, e);
574
- }
575
- break;
496
+ if (TARGET_FLAVOR === 'typescript') {
497
+ try {
498
+ const url = await requestModuleFromServer(candidate);
499
+ const mod = await import(/* @vite-ignore */ url);
500
+ comp = mod && (mod.default || mod);
501
+ }
502
+ catch (e) {
503
+ if (VERBOSE)
504
+ console.warn('[hmr][init] TS initial mount failed to import', candidate, e);
505
+ }
506
+ }
507
+ else if (CLIENT_STRATEGY?.loadComponentForMount) {
508
+ comp = await CLIENT_STRATEGY.loadComponentForMount(candidate, 'initial_mount');
576
509
  }
577
510
  if (comp) {
578
511
  const ok = await performResetRoot(comp);
@@ -633,11 +566,7 @@ function applyDelta(payload) {
633
566
  setGraphVersion(payload.newVersion);
634
567
  }
635
568
  const changed = payload.changed || [];
636
- switch (TARGET_FLAVOR) {
637
- case 'vue':
638
- recordVuePayloadChanges(changed, getGraphVersion());
639
- break;
640
- }
569
+ CLIENT_STRATEGY?.recordPayloadChanges?.(changed, getGraphVersion());
641
570
  (payload.changed || []).forEach((m) => {
642
571
  if (!m || !m.id)
643
572
  return;
@@ -723,11 +652,7 @@ function applyDelta(payload) {
723
652
  // Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
724
653
  function __nsNavigateUsingApp(comp, opts = {}) {
725
654
  const g = globalThis;
726
- switch (TARGET_FLAVOR) {
727
- case 'vue':
728
- ensureVueGlobals();
729
- break;
730
- }
655
+ CLIENT_STRATEGY?.beforeNavigateBuild?.();
731
656
  const AppFactory = g.createApp;
732
657
  const RootCtor = g.NSVRoot;
733
658
  if (typeof AppFactory !== 'function' || typeof RootCtor !== 'function') {
@@ -757,11 +682,7 @@ function __nsNavigateUsingApp(comp, opts = {}) {
757
682
  // at the destination as `[Vue warn]: Missing required prop` and any
758
683
  // required-prop component would render with `undefined` bindings.
759
684
  const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)), opts && opts.props);
760
- switch (TARGET_FLAVOR) {
761
- case 'vue':
762
- ensurePiniaOnApp(app);
763
- break;
764
- }
685
+ CLIENT_STRATEGY?.onNavAppCreated?.(app);
765
686
  try {
766
687
  const registry = g.__nsVendorRegistry;
767
688
  const req = registry?.get ? g.__nsVendorRequire || g.__nsRequire || g.require : g.__nsRequire || g.require;
@@ -945,25 +866,7 @@ async function processQueue() {
945
866
  // After evaluating the batch, perform flavor-specific UI refresh.
946
867
  switch (TARGET_FLAVOR) {
947
868
  case 'vue':
948
- // Vue SFCs are handled via the registry update path
949
- // (which drives its own overlay completion through
950
- // `driveVueSfcUpdateOverlay`); nothing else to do
951
- // for the view-tree refresh here.
952
- //
953
- // However, when a non-SFC file (e.g. a `.ts`
954
- // utility module imported by an SFC) is the only
955
- // changed entry, no `ns:vue-sfc-registry-update`
956
- // will follow — and without an explicit 'complete'
957
- // the overlay would stick on "Preparing update
958
- // (5%)". Drive the closing frame here so the
959
- // auto-hide timer can dismiss the toast in the
960
- // pure-TS-change case. The detail surfaces the
961
- // changed count so a user can correlate the
962
- // overlay with the server-side `[hmr-ws][update]`
963
- // log.
964
- setUpdateOverlayStage('complete', {
965
- detail: drained.length === 1 ? `Updated ${drained[0]} in ${Math.max(0, Date.now() - tQueueStart)}ms` : `Updated ${drained.length} modules in ${Math.max(0, Date.now() - tQueueStart)}ms`,
966
- });
869
+ await CLIENT_STRATEGY?.refreshAfterBatch?.(drained, { setUpdateOverlayStage, startedAt: tQueueStart });
967
870
  break;
968
871
  case 'solid': {
969
872
  // Boundaries discovered in this HMR cycle (tsx files reachable
@@ -1593,6 +1496,11 @@ async function handleHmrMessage(ev) {
1593
1496
  setHmrPendingOverlay(msg.path);
1594
1497
  return;
1595
1498
  }
1499
+ // The per-flavor client strategy is loaded by a dynamic import(); make
1500
+ // sure it has resolved (and `install()` has run) before any handler that
1501
+ // delegates through it. After the first message this is an already-settled
1502
+ // promise (microtask only); for Solid/TypeScript it is `Promise.resolve()`.
1503
+ await CLIENT_STRATEGY_READY;
1596
1504
  if (msg.type === 'ns:hmr-full-graph') {
1597
1505
  // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
1598
1506
  try {
@@ -1844,7 +1752,7 @@ async function handleHmrMessage(ev) {
1844
1752
  if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
1845
1753
  setGraphVersion(Number(msg.version || getGraphVersion() || 0));
1846
1754
  }
1847
- if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
1755
+ if (CLIENT_STRATEGY?.handleHotUpdateMessage && (await CLIENT_STRATEGY.handleHotUpdateMessage(msg, { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi }))) {
1848
1756
  return;
1849
1757
  }
1850
1758
  }
@@ -1908,28 +1816,17 @@ async function handleHmrMessage(ev) {
1908
1816
  }
1909
1817
  }
1910
1818
  if (msg.type === 'ns:vue-sfc-registry') {
1911
- handleVueSfcRegistry(msg);
1819
+ CLIENT_STRATEGY?.handleSfcRegistry?.(msg);
1912
1820
  return;
1913
1821
  }
1914
1822
  if (msg.type === 'ns:vue-sfc-registry-update') {
1915
1823
  if (typeof msg.version === 'number')
1916
1824
  setGraphVersion(msg.version);
1917
- // `ns:hmr-pending` already set the overlay to 'received' (5%).
1918
- // Without the explicit stage walk below the overlay would stick
1919
- // at "Preparing update" forever after a successful SFC swap
1920
- // the Vue path was missing the framework-specific completion
1921
- // hooks that Angular drives via `handleAngularHotUpdateMessage`
1922
- // and CSS drives inline above. `driveVueSfcUpdateOverlay` is a
1923
- // thin orchestrator that walks 'evicting' → 'reimporting' →
1924
- // 'rebooting' → 'complete' around the load + reset steps and
1925
- // always lands on 'complete' (or a failure detail) so the
1926
- // auto-hide timer can dismiss the toast.
1927
- const sfcFilePath = typeof msg.path === 'string' ? msg.path : undefined;
1928
- await driveVueSfcUpdateOverlay({
1929
- filePath: sfcFilePath,
1930
- loadComponent: () => handleVueSfcRegistryUpdate(msg, getGraphVersion()),
1931
- applyComponent: (component) => performResetRoot(component),
1932
- }, { getOverlay: getHmrOverlayApi });
1825
+ // `ns:hmr-pending` already set the overlay to 'received' (5%). The Vue
1826
+ // strategy walks 'evicting' 'reimporting' 'rebooting' 'complete'
1827
+ // around the SFC load + reset so the toast always lands on 'complete'
1828
+ // (or a failure detail) and the auto-hide timer can dismiss it.
1829
+ await CLIENT_STRATEGY?.handleSfcRegistryUpdate?.(msg, getGraphVersion(), { getCore, verbose: VERBOSE, performResetRoot, getOverlay: getHmrOverlayApi });
1933
1830
  return;
1934
1831
  }
1935
1832
  }
@@ -1949,7 +1846,7 @@ function normalizeComponent(input, nameHint) {
1949
1846
  }
1950
1847
  // If provided a render function, wrap with defineComponent
1951
1848
  if (typeof input === 'function') {
1952
- ensureVueGlobals();
1849
+ CLIENT_STRATEGY?.beforeNavigateBuild?.();
1953
1850
  const comp = globalThis.defineComponent
1954
1851
  ? globalThis.defineComponent({
1955
1852
  name: nameHint || input.name || 'AnonymousSFC',
@@ -1960,7 +1857,7 @@ function normalizeComponent(input, nameHint) {
1960
1857
  }
1961
1858
  // If object has a render function property
1962
1859
  if (input?.render && typeof input.render === 'function') {
1963
- ensureVueGlobals();
1860
+ CLIENT_STRATEGY?.beforeNavigateBuild?.();
1964
1861
  const comp = globalThis.defineComponent
1965
1862
  ? globalThis.defineComponent({
1966
1863
  name: nameHint || input.name || 'AnonymousSFC',
@@ -2018,35 +1915,32 @@ async function performResetRoot(newComponent) {
2018
1915
  if (cachedRoot)
2019
1916
  return cachedRoot;
2020
1917
  try {
2021
- switch (TARGET_FLAVOR) {
2022
- case 'vue':
2023
- cachedRoot = getRootForVue(newComponent, state);
2024
- break;
2025
- case 'typescript': {
2026
- // For TS flavor, treat the component as a factory or direct NS view.
2027
- let root = null;
2028
- try {
2029
- if (typeof newComponent === 'function') {
2030
- root = newComponent();
2031
- }
2032
- else {
2033
- root = newComponent;
2034
- }
1918
+ if (CLIENT_STRATEGY?.createRoot) {
1919
+ cachedRoot = CLIENT_STRATEGY.createRoot(newComponent, state);
1920
+ }
1921
+ else if (TARGET_FLAVOR === 'typescript') {
1922
+ // For TS flavor, treat the component as a factory or direct NS view.
1923
+ let root = null;
1924
+ try {
1925
+ if (typeof newComponent === 'function') {
1926
+ root = newComponent();
2035
1927
  }
2036
- catch (e) {
2037
- console.warn('[hmr-client][ts] root factory invocation failed', e);
1928
+ else {
1929
+ root = newComponent;
2038
1930
  }
2039
- cachedRoot = root || {};
2040
- // Heuristic: if the root "looks" like a Frame, prefer frame semantics
2041
- try {
2042
- const name = String(cachedRoot?.constructor?.name || '').replace(/^_+/, '');
2043
- if (/^Frame(\$\d+)?$/.test(name)) {
2044
- rootKind = 'frame';
2045
- }
1931
+ }
1932
+ catch (e) {
1933
+ console.warn('[hmr-client][ts] root factory invocation failed', e);
1934
+ }
1935
+ cachedRoot = root || {};
1936
+ // Heuristic: if the root "looks" like a Frame, prefer frame semantics
1937
+ try {
1938
+ const name = String(cachedRoot?.constructor?.name || '').replace(/^_+/, '');
1939
+ if (/^Frame(\$\d+)?$/.test(name)) {
1940
+ rootKind = 'frame';
2046
1941
  }
2047
- catch { }
2048
- break;
2049
1942
  }
1943
+ catch { }
2050
1944
  }
2051
1945
  return cachedRoot;
2052
1946
  }
@@ -2197,7 +2091,7 @@ async function performResetRoot(newComponent) {
2197
2091
  // page to the Frame — the screen keeps showing the previous render. resetRootView with a
2198
2092
  // fresh Frame correctly reparents the Page and is the proven path that produces visible
2199
2093
  // in-place updates for SFC HMR cycles. Non-Vue flavors keep the legacy navigate fast path.
2200
- const allowNavigateFastPath = TARGET_FLAVOR !== 'vue';
2094
+ const allowNavigateFastPath = CLIENT_STRATEGY?.allowNavigateFastPath ?? true;
2201
2095
  if (allowNavigateFastPath && !hadPlaceholder && !isFrameRoot && isAuthoritativeFrame && typeof existingAppFrame.navigate === 'function') {
2202
2096
  try {
2203
2097
  const navEntry = {
@@ -2361,12 +2255,9 @@ export function initHmrClient(opts) {
2361
2255
  console.log('[hmr-client] deferring WebSocket connection until boot completes');
2362
2256
  setTimeout(waitForBoot, 100);
2363
2257
  }
2364
- // Best-effort: install back wrapper even before first remount; original root may be captured later
2365
- switch (TARGET_FLAVOR) {
2366
- case 'vue':
2367
- ensureBackWrapperInstalled(performResetRoot, getCore);
2368
- break;
2369
- }
2258
+ // Best-effort: install back wrapper even before first remount; original root may be captured later.
2259
+ // Deferred until the dynamically-imported strategy resolves.
2260
+ void CLIENT_STRATEGY_READY.then(() => CLIENT_STRATEGY?.installBackWrapper?.(performResetRoot, getCore));
2370
2261
  }
2371
2262
  export default function startViteHMR(opts) {
2372
2263
  if (VERBOSE)