@nativescript/vite 1.0.6-rc.0 → 1.0.6-rc.2

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 (49) hide show
  1. package/bin/cli.cjs +0 -0
  2. package/configuration/angular.js +10 -1
  3. package/configuration/angular.js.map +1 -1
  4. package/configuration/base.js +2 -2
  5. package/configuration/base.js.map +1 -1
  6. package/configuration/react.js +1 -1
  7. package/configuration/react.js.map +1 -1
  8. package/configuration/solid.js +1 -1
  9. package/configuration/solid.js.map +1 -1
  10. package/configuration/typescript.js +1 -1
  11. package/configuration/typescript.js.map +1 -1
  12. package/configuration/vue.js +1 -1
  13. package/configuration/vue.js.map +1 -1
  14. package/helpers/app-components.d.ts +3 -3
  15. package/helpers/app-components.js +36 -5
  16. package/helpers/app-components.js.map +1 -1
  17. package/helpers/css-tree.js +0 -1
  18. package/helpers/css-tree.js.map +1 -1
  19. package/helpers/init.js +4 -4
  20. package/helpers/init.js.map +1 -1
  21. package/helpers/main-entry.js +53 -8
  22. package/helpers/main-entry.js.map +1 -1
  23. package/helpers/nativeclass-transform.js +1 -1
  24. package/helpers/nativeclass-transform.js.map +1 -1
  25. package/hmr/client/index.js +146 -398
  26. package/hmr/client/index.js.map +1 -1
  27. package/hmr/client/utils.d.ts +0 -2
  28. package/hmr/client/utils.js +25 -179
  29. package/hmr/client/utils.js.map +1 -1
  30. package/hmr/entry-runtime.js +18 -1
  31. package/hmr/entry-runtime.js.map +1 -1
  32. package/hmr/frameworks/vue/client/index.js +1 -5
  33. package/hmr/frameworks/vue/client/index.js.map +1 -1
  34. package/hmr/helpers/ast-normalizer.js +11 -0
  35. package/hmr/helpers/ast-normalizer.js.map +1 -1
  36. package/hmr/server/constants.js +4 -1
  37. package/hmr/server/constants.js.map +1 -1
  38. package/hmr/server/vite-plugin.js +66 -6
  39. package/hmr/server/vite-plugin.js.map +1 -1
  40. package/hmr/server/websocket.js +52 -173
  41. package/hmr/server/websocket.js.map +1 -1
  42. package/hmr/shared/runtime/hooks.d.ts +17 -0
  43. package/hmr/shared/runtime/hooks.js +68 -0
  44. package/hmr/shared/runtime/hooks.js.map +1 -0
  45. package/hmr/shared/runtime/http-only-boot.js +20 -3
  46. package/hmr/shared/runtime/http-only-boot.js.map +1 -1
  47. package/hmr/shared/vendor/manifest.js +8 -5
  48. package/hmr/shared/vendor/manifest.js.map +1 -1
  49. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  * Always resolve core classes and Application from the vendor realm or globalThis at runtime.
6
6
  * The HMR client is evaluated via HTTP ESM on device; static imports would create secondary instances.
7
7
  */
8
- import { setHMRWsUrl, getHMRWsUrl, pendingModuleFetches, deriveHttpOrigin, setHttpOriginForVite, moduleFetchCache, requestModuleFromServer, getHttpOriginForVite, normalizeSpec, hmrMetrics, graph, setGraphVersion, getGraphVersion, getCurrentApp, getRootFrame, setCurrentApp, setRootFrame, getCore, attachDiagnosticsToFrame, logUiSnapshot } from './utils.js';
8
+ import { setHMRWsUrl, getHMRWsUrl, pendingModuleFetches, deriveHttpOrigin, setHttpOriginForVite, moduleFetchCache, requestModuleFromServer, getHttpOriginForVite, normalizeSpec, hmrMetrics, graph, setGraphVersion, getGraphVersion, getCurrentApp, getRootFrame, setCurrentApp, setRootFrame, getCore } from './utils.js';
9
9
  import { handleCssUpdates } from './css-handler.js';
10
10
  const VERBOSE = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;
11
11
  const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
@@ -14,14 +14,6 @@ const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
14
14
  // Policy: by default, let the app's own main entry mount initially; HMR client handles updates/remounts only.
15
15
  // Flip this to true via global __NS_HMR_ALLOW_INITIAL_MOUNT__ if you need the client to perform the first mount.
16
16
  const ALLOW_INITIAL_MOUNT = !!globalThis.__NS_HMR_ALLOW_INITIAL_MOUNT__;
17
- // When verbose mode is enabled, also enable runtime nav diagnostics so /ns/rt logs are visible
18
- try {
19
- if (VERBOSE) {
20
- globalThis.__NS_DEV_LOGS__ = true;
21
- globalThis.__NS_VERBOSE_RT_NAV__ = true;
22
- }
23
- }
24
- catch { }
25
17
  // Ensure core aliases are present on globalThis early so that /ns/rt exports resolve to functions
26
18
  // before any SFCs are evaluated during HTTP-only dev boot.
27
19
  function ensureCoreAliasesOnGlobalThis() {
@@ -46,46 +38,11 @@ function ensureCoreAliasesOnGlobalThis() {
46
38
  g.Page = P;
47
39
  }
48
40
  catch { }
49
- // Optional diagnostics: compare with vendor realm if available
50
- if (VERBOSE) {
51
- let vF, vA, vP;
52
- try {
53
- const reg = g.__nsVendorRegistry;
54
- const vmod = reg?.get ? reg.get('@nativescript/core') : undefined;
55
- const vns = (vmod && (vmod.default || vmod)) || vmod;
56
- vF = vns?.Frame;
57
- vA = vns?.Application;
58
- vP = vns?.Page;
59
- }
60
- catch { }
61
- try {
62
- console.log('[hmr-client] core alias status', {
63
- globalHas: { Frame: !!F, Application: !!A, Page: !!P },
64
- globalMethods: {
65
- FrameTopmost: typeof F?.topmost === 'function',
66
- AppResetRoot: typeof A?.resetRootView === 'function',
67
- },
68
- sameRef: {
69
- Frame: F && vF ? F === vF : undefined,
70
- Application: A && vA ? A === vA : undefined,
71
- Page: P && vP ? P === vP : undefined,
72
- },
73
- ctorNames: {
74
- Frame: F?.name || F?.constructor?.name,
75
- Application: A?.name || A?.constructor?.name,
76
- Page: P?.name || P?.constructor?.name,
77
- },
78
- });
79
- }
80
- catch { }
81
- }
82
41
  }
83
42
  catch { }
84
43
  }
85
44
  // Apply once on module evaluation
86
45
  ensureCoreAliasesOnGlobalThis();
87
- // Install low-level diagnostics for navigation and root replacement to trace duplicates and state
88
- installDeepDiagnostics();
89
46
  /**
90
47
  * Flavor hooks
91
48
  */
@@ -93,207 +50,12 @@ import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSf
93
50
  import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
94
51
  switch (__NS_TARGET_FLAVOR__) {
95
52
  case 'vue':
96
- if (VERBOSE) {
97
- console.log('[hmr-client] installing nativescript-vue dev shims');
98
- }
99
53
  installNsVueDevShims();
100
54
  break;
101
55
  case 'angular':
102
- if (VERBOSE) {
103
- try {
104
- console.log('[hmr-client] Initializing Angular HMR shims');
105
- }
106
- catch { }
107
- }
108
56
  installAngularHmrClientHooks();
109
57
  break;
110
58
  }
111
- // Global frame diagnostics: instrument Frame.navigate and Frame.topmost to detect
112
- // navigation against non-authoritative frames across the app (helps gray-screen cases)
113
- try {
114
- const g = globalThis;
115
- const F = getCore('Frame') || g.Frame;
116
- if (F && F.prototype && !g.__NS_DEV_GLOBAL_FRAME_PATCHED__) {
117
- const tag = (fr) => {
118
- try {
119
- if (!fr)
120
- return;
121
- if (!fr.__ns_tag)
122
- fr.__ns_tag = Math.random().toString(36).slice(2);
123
- }
124
- catch { }
125
- };
126
- const proto = F.prototype;
127
- const origNav = proto.navigate;
128
- if (typeof origNav === 'function') {
129
- proto.navigate = function __ns_diag_nav(entry) {
130
- try {
131
- tag(this);
132
- console.log('[diag][global][frame.navigate]', {
133
- tag: this.__ns_tag,
134
- type: this?.constructor?.name,
135
- hasCreate: !!entry?.create,
136
- clearHistory: !!entry?.clearHistory,
137
- animated: !!entry?.animated,
138
- });
139
- }
140
- catch { }
141
- return origNav.apply(this, arguments);
142
- };
143
- }
144
- const origTop = typeof F.topmost === 'function' ? F.topmost.bind(F) : null;
145
- if (origTop) {
146
- F.topmost = function __ns_diag_topmost() {
147
- const fr = origTop();
148
- try {
149
- tag(fr);
150
- console.log('[diag][global][Frame.topmost]', {
151
- tag: fr?.__ns_tag,
152
- type: fr?.constructor?.name,
153
- });
154
- }
155
- catch { }
156
- return fr;
157
- };
158
- }
159
- try {
160
- g.__NS_DEV_GLOBAL_FRAME_PATCHED__ = true;
161
- }
162
- catch { }
163
- }
164
- }
165
- catch { }
166
- // --- Diagnostics helpers ----------------------------------------------------
167
- function summarizeNavEntry(entry) {
168
- try {
169
- if (!entry)
170
- return { kind: 'empty' };
171
- if (typeof entry === 'string')
172
- return { kind: 'string', moduleName: entry };
173
- const hasCreate = typeof entry.create === 'function';
174
- const moduleName = entry.moduleName;
175
- const clearHistory = !!entry.clearHistory;
176
- const animated = entry.animated;
177
- const backstackVisible = entry.backstackVisible;
178
- const contextKeys = Object.keys(entry.context || {});
179
- return {
180
- kind: 'entry',
181
- hasCreate,
182
- moduleName,
183
- clearHistory,
184
- animated,
185
- backstackVisible,
186
- contextKeys,
187
- };
188
- }
189
- catch {
190
- return { kind: 'unknown' };
191
- }
192
- }
193
- function classifyResetArg(arg) {
194
- try {
195
- const ctorName = String(arg?.constructor?.name || '').replace(/^_+/, '');
196
- const keys = Object.keys(arg || {});
197
- const hasCreate = typeof arg?.create === 'function';
198
- const hasModuleName = typeof arg?.moduleName === 'string';
199
- const isFrameLike = !!arg && (ctorName === 'Frame' || /^Frame(\$\d+)?$/.test(ctorName) || (typeof arg?.navigate === 'function' && typeof arg?.addChild === 'function'));
200
- const isPageLike = !!arg && (ctorName === 'Page' || /^Page(\$\d+)?$/.test(ctorName) || (typeof arg?.content !== 'undefined' && typeof arg?.addChild === 'function'));
201
- return {
202
- ctorName,
203
- keys,
204
- hasCreate,
205
- hasModuleName,
206
- isFrameLike,
207
- isPageLike,
208
- };
209
- }
210
- catch {
211
- return { ctorName: 'unknown' };
212
- }
213
- }
214
- function installDeepDiagnostics() {
215
- if (!VERBOSE)
216
- return;
217
- const g = globalThis;
218
- try {
219
- // Patch Frame.navigate to log calls and a short stack
220
- const F = getCore('Frame') || g.Frame;
221
- if (F?.prototype && !F.prototype.__ns_diag_nav__) {
222
- const orig = F.prototype.navigate;
223
- if (typeof orig === 'function') {
224
- F.prototype.__ns_diag_nav__ = true;
225
- // Simple duplicate navigation suppression in dev: if the same target is navigated twice within a short window, ignore the 2nd.
226
- F.prototype.navigate = function (...args) {
227
- try {
228
- const entry = args[0];
229
- const summary = summarizeNavEntry(entry);
230
- const stack = (new Error().stack || '').split('\n').slice(2, 8).join('\n');
231
- console.log('[diag][Frame.navigate]', {
232
- frameCtor: this?.constructor?.name,
233
- summary,
234
- stack,
235
- });
236
- try {
237
- const gAny = globalThis;
238
- const key = JSON.stringify({
239
- k: 'nav',
240
- m: summary.moduleName || '',
241
- c: !!summary.hasCreate,
242
- ch: !!summary.clearHistory,
243
- a: !!summary.animated,
244
- });
245
- const now = Date.now();
246
- const last = gAny.__NS_DIAG_LAST_NAV__;
247
- if (last && last.key === key && now - last.t < 300) {
248
- console.warn('[diag][Frame.navigate] duplicate nav suppressed (dev)', { withinMs: now - last.t, key });
249
- return; // suppress duplicate
250
- }
251
- gAny.__NS_DIAG_LAST_NAV__ = { key, t: now };
252
- }
253
- catch { }
254
- }
255
- catch { }
256
- return orig.apply(this, args);
257
- };
258
- }
259
- }
260
- }
261
- catch { }
262
- try {
263
- // Wrap Application.resetRootView to log argument classification and stack
264
- const App = getCore('Application') || g.Application;
265
- const proto = App && Object.getPrototypeOf(App);
266
- const orig = (App && App.resetRootView) || (proto && proto.resetRootView);
267
- if (typeof orig === 'function' && !g.__NS_DIAG_RESET_WRAPPED__) {
268
- const wrapped = function __ns_diag_resetRootView(entry) {
269
- try {
270
- const classification = classifyResetArg(entry);
271
- const stack = (new Error().stack || '').split('\n').slice(2, 8).join('\n');
272
- console.log('[diag][Application.resetRootView]', {
273
- classification,
274
- stack,
275
- });
276
- }
277
- catch { }
278
- return orig.call(this, entry);
279
- };
280
- try {
281
- App.resetRootView = wrapped;
282
- }
283
- catch { }
284
- try {
285
- if (proto && typeof proto === 'object')
286
- proto.resetRootView = wrapped;
287
- }
288
- catch { }
289
- try {
290
- g.__NS_DIAG_RESET_WRAPPED__ = true;
291
- }
292
- catch { }
293
- }
294
- }
295
- catch { }
296
- }
297
59
  // Track whether we've mounted an initial app root yet in HTTP-only boot
298
60
  let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
299
61
  // Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
@@ -303,6 +65,7 @@ let tsModuleSet = null;
303
65
  let tsMainId = null;
304
66
  const changedQueue = [];
305
67
  let processingQueue = false;
68
+ let processingPromise = null;
306
69
  // Detect whether the early placeholder root is still active on screen
307
70
  function isPlaceholderActive() {
308
71
  try {
@@ -844,7 +607,6 @@ function __nsNavigateUsingApp(comp, opts = {}) {
844
607
  if (typeof GApp?.resetRootView === 'function' && typeof F === 'function') {
845
608
  GApp.resetRootView({
846
609
  create: () => {
847
- var _a;
848
610
  const fr = new F();
849
611
  const navEntry = {
850
612
  create: () => buildTarget(),
@@ -855,19 +617,7 @@ function __nsNavigateUsingApp(comp, opts = {}) {
855
617
  fr.navigate(navEntry);
856
618
  }
857
619
  catch { }
858
- try {
859
- attachDiagnosticsToFrame(fr);
860
- }
861
- catch { }
862
620
  setRootFrame(fr);
863
- try {
864
- (_a = getRootFrame()).__ns_tag || (_a.__ns_tag = Math.random().toString(36).slice(2));
865
- console.log('[diag][root] ROOT_FRAME set (app-nav)', {
866
- tag: getRootFrame()?.__ns_tag,
867
- type: getRootFrame()?.constructor?.name,
868
- });
869
- }
870
- catch { }
871
621
  return fr;
872
622
  },
873
623
  });
@@ -875,28 +625,8 @@ function __nsNavigateUsingApp(comp, opts = {}) {
875
625
  }
876
626
  throw new Error('Application.resetRootView unavailable');
877
627
  }
878
- try {
879
- attachDiagnosticsToFrame(frame);
880
- }
881
- catch { }
882
628
  const navEntry = { create: () => buildTarget(), ...(opts || {}) };
883
- try {
884
- const summary = summarizeNavEntry(navEntry);
885
- if (VERBOSE)
886
- console.log('[app-nav] navigate entry', summary);
887
- }
888
- catch { }
889
629
  frame.navigate(navEntry);
890
- try {
891
- const top2 = (g.Frame && g.Frame.topmost && g.Frame.topmost()) || null;
892
- const ctor2 = top2 && top2.constructor && top2.constructor.name;
893
- if (VERBOSE)
894
- console.log('[app-nav] after navigate', {
895
- topCtor: ctor2,
896
- hasTop: !!top2,
897
- });
898
- }
899
- catch { }
900
630
  return undefined;
901
631
  }
902
632
  // Expose deterministic app navigation globally so /ns/rt can guarantee single-path navigation
@@ -914,77 +644,81 @@ async function processQueue() {
914
644
  }
915
645
  catch { }
916
646
  }, 150);
917
- return;
647
+ return Promise.resolve();
918
648
  }
919
649
  if (processingQueue)
920
- return;
650
+ return processingPromise || Promise.resolve();
921
651
  processingQueue = true;
922
- try {
923
- // Simple deterministic drain of the queue. We currently focus on TS flavor
924
- // by re-importing changed modules (to refresh their HTTP ESM copies) and
925
- // then performing a root reset so the UI reflects the new code.
926
- const seen = new Set();
927
- const drained = [];
928
- while (changedQueue.length) {
929
- const id = changedQueue.shift();
930
- if (!id || typeof id !== 'string')
931
- continue;
932
- if (seen.has(id))
933
- continue;
934
- seen.add(id);
935
- drained.push(id);
936
- }
937
- if (!drained.length)
938
- return;
939
- if (VERBOSE)
940
- console.log('[hmr][queue] processing changed ids', drained);
941
- // Evaluate changed modules best-effort; failures shouldn't completely break HMR.
942
- for (const id of drained) {
943
- try {
944
- const spec = normalizeSpec(id);
945
- const url = await requestModuleFromServer(spec);
946
- if (!url)
652
+ processingPromise = (async () => {
653
+ try {
654
+ // Simple deterministic drain of the queue. We currently focus on TS flavor
655
+ // by re-importing changed modules (to refresh their HTTP ESM copies) and
656
+ // then performing a root reset so the UI reflects the new code.
657
+ const seen = new Set();
658
+ const drained = [];
659
+ while (changedQueue.length) {
660
+ const id = changedQueue.shift();
661
+ if (!id || typeof id !== 'string')
947
662
  continue;
948
- if (VERBOSE)
949
- console.log('[hmr][queue] re-import', { id, spec, url });
950
- await import(/* @vite-ignore */ url);
951
- }
952
- catch (e) {
953
- if (VERBOSE)
954
- console.warn('[hmr][queue] re-import failed for', id, e);
663
+ if (seen.has(id))
664
+ continue;
665
+ seen.add(id);
666
+ drained.push(id);
955
667
  }
956
- }
957
- // After evaluating the batch, perform flavor-specific UI refresh.
958
- switch (__NS_TARGET_FLAVOR__) {
959
- case 'vue':
960
- // Vue SFCs are handled via the registry update path; nothing to do here.
961
- break;
962
- case 'typescript': {
963
- // For TS apps, always reset back to the conventional app root.
964
- // This preserves the shell (Frame, ActionBar, etc.) that the app's
965
- // own bootstrapping wires up via `Application.run`.
668
+ if (!drained.length)
669
+ return;
670
+ if (VERBOSE)
671
+ console.log('[hmr][queue] processing changed ids', drained);
672
+ // Evaluate changed modules best-effort; failures shouldn't completely break HMR.
673
+ for (const id of drained) {
966
674
  try {
967
- const g = globalThis;
968
- const App = getCore('Application') || g.Application;
969
- if (!App || typeof App.resetRootView !== 'function') {
970
- if (VERBOSE)
971
- console.warn('[hmr][queue] TS flavor: Application.resetRootView unavailable; skipping UI refresh');
972
- break;
973
- }
675
+ const spec = normalizeSpec(id);
676
+ const url = await requestModuleFromServer(spec);
677
+ if (!url)
678
+ continue;
974
679
  if (VERBOSE)
975
- console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
976
- App.resetRootView({ moduleName: 'app-root' });
680
+ console.log('[hmr][queue] re-import', { id, spec, url });
681
+ const mod = await import(/* @vite-ignore */ url);
977
682
  }
978
683
  catch (e) {
979
- console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
684
+ if (VERBOSE)
685
+ console.warn('[hmr][queue] re-import failed for', id, e);
686
+ }
687
+ }
688
+ // After evaluating the batch, perform flavor-specific UI refresh.
689
+ switch (__NS_TARGET_FLAVOR__) {
690
+ case 'vue':
691
+ // Vue SFCs are handled via the registry update path; nothing to do here.
692
+ break;
693
+ case 'typescript': {
694
+ // For TS apps, always reset back to the conventional app root.
695
+ // This preserves the shell (Frame, ActionBar, etc.) that the app's
696
+ // own bootstrapping wires up via `Application.run`.
697
+ try {
698
+ const g = globalThis;
699
+ const App = getCore('Application') || g.Application;
700
+ if (!App || typeof App.resetRootView !== 'function') {
701
+ if (VERBOSE)
702
+ console.warn('[hmr][queue] TS flavor: Application.resetRootView unavailable; skipping UI refresh');
703
+ break;
704
+ }
705
+ if (VERBOSE)
706
+ console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
707
+ App.resetRootView({ moduleName: 'app-root' });
708
+ }
709
+ catch (e) {
710
+ console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
711
+ }
712
+ break;
980
713
  }
981
- break;
982
714
  }
983
715
  }
984
- }
985
- finally {
986
- processingQueue = false;
987
- }
716
+ finally {
717
+ processingQueue = false;
718
+ processingPromise = null;
719
+ }
720
+ })();
721
+ return processingPromise;
988
722
  }
989
723
  let hmrSocket = null;
990
724
  // Track server-announced batches for each version so we can import in-order client-side
@@ -1140,12 +874,77 @@ async function handleHmrMessage(ev) {
1140
874
  catch {
1141
875
  return;
1142
876
  }
1143
- if (VERBOSE)
1144
- console.log('[hmr-client] msg', msg);
877
+ // Notify optional app-level hook after an HMR batch is applied.
878
+ function notifyAppHmrUpdate(kind, changedIds) {
879
+ try {
880
+ const hook = globalThis.__NS_HMR_ON_UPDATE__;
881
+ if (typeof hook === 'function') {
882
+ hook({ type: kind, version: getGraphVersion(), changedIds: changedIds || [], raw: msg });
883
+ }
884
+ }
885
+ catch { }
886
+ }
1145
887
  if (msg) {
1146
888
  if (msg.type === 'ns:hmr-full-graph') {
889
+ // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
890
+ try {
891
+ const g = globalThis;
892
+ g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
893
+ }
894
+ catch { }
895
+ // Capture previous graph snapshot so we can infer which modules changed.
896
+ const prevGraph = new Map(graph);
1147
897
  setGraphVersion(Number(msg.version || getGraphVersion() || 0));
1148
898
  applyFullGraph(msg);
899
+ // In some cases (e.g. server chooses full-graph resync / page reload), we won't
900
+ // receive a delta queue to re-import changed TS modules. Without re-import,
901
+ // HTTP ESM caching means module bodies (and side effects) won't re-run.
902
+ try {
903
+ const inferredChanged = [];
904
+ try {
905
+ for (const [id, next] of graph.entries()) {
906
+ const prev = prevGraph.get(id);
907
+ if (!prev || prev.hash !== next.hash)
908
+ inferredChanged.push(id);
909
+ }
910
+ // Removed modules are also "changed" but we don't import them.
911
+ }
912
+ catch { }
913
+ // Best-effort: only re-import real source modules (avoid .vue registry pipeline and virtual ids)
914
+ const toReimport = inferredChanged.filter((id) => {
915
+ if (!id || typeof id !== 'string')
916
+ return false;
917
+ if (/^\0|^\/\0/.test(id))
918
+ return false;
919
+ if (/plugin-vue:export-helper/.test(id))
920
+ return false;
921
+ if (/\.vue$/i.test(id))
922
+ return false;
923
+ if (id.endsWith(APP_MAIN_ENTRY_SPEC))
924
+ return false;
925
+ return true;
926
+ });
927
+ if (toReimport.length && VERBOSE)
928
+ console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
929
+ for (const id of toReimport) {
930
+ try {
931
+ const spec = normalizeSpec(id);
932
+ const url = await requestModuleFromServer(spec);
933
+ if (!url)
934
+ continue;
935
+ if (VERBOSE)
936
+ console.log('[hmr][full-graph] re-import', { id, spec, url });
937
+ await import(/* @vite-ignore */ url);
938
+ }
939
+ catch (e) {
940
+ if (VERBOSE)
941
+ console.warn('[hmr][full-graph] re-import failed for', id, e);
942
+ }
943
+ }
944
+ }
945
+ catch { }
946
+ const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
947
+ notifyAppHmrUpdate('full-graph', fullIds);
1149
948
  return;
1150
949
  }
1151
950
  if (msg.type === 'ns:ts-module-registry') {
@@ -1176,7 +975,12 @@ async function handleHmrMessage(ev) {
1176
975
  return;
1177
976
  }
1178
977
  if (msg.type === 'ns:hmr-delta') {
1179
- setGraphVersion(Number(msg.newVersion || getGraphVersion() || 0));
978
+ // Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
979
+ try {
980
+ const g = globalThis;
981
+ g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
982
+ }
983
+ catch { }
1180
984
  try {
1181
985
  const ids = Array.isArray(msg.changed) ? msg.changed.map((c) => c?.id).filter(Boolean) : [];
1182
986
  if (ids.length)
@@ -1184,6 +988,14 @@ async function handleHmrMessage(ev) {
1184
988
  }
1185
989
  catch { }
1186
990
  applyDelta(msg);
991
+ // Ensure queued module re-imports complete before notifying app hooks.
992
+ // Otherwise app-level handlers can run against stale module bodies due to HTTP ESM caching.
993
+ try {
994
+ await processQueue();
995
+ }
996
+ catch { }
997
+ const deltaIds = Array.isArray(msg.changed) ? msg.changed.map((c) => c?.id).filter(Boolean) : [];
998
+ notifyAppHmrUpdate('delta', deltaIds);
1187
999
  return;
1188
1000
  }
1189
1001
  else if (handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
@@ -1297,25 +1109,6 @@ async function performResetRoot(newComponent) {
1297
1109
  ensureCoreAliasesOnGlobalThis();
1298
1110
  }
1299
1111
  catch { }
1300
- try {
1301
- if (VERBOSE)
1302
- logUiSnapshot('pre-performResetRoot');
1303
- }
1304
- catch { }
1305
- if (VERBOSE) {
1306
- try {
1307
- const g = globalThis;
1308
- const vF = getCore('Frame');
1309
- console.log('[hmr-client] alias check before remount', {
1310
- globalFrameHasTopmost: typeof g?.Frame?.topmost === 'function',
1311
- vendorFrameHasTopmost: typeof vF?.topmost === 'function',
1312
- sameFrameRef: vF === g?.Frame,
1313
- appHasReset: typeof g?.Application?.resetRootView === 'function',
1314
- pageIsCtor: typeof g?.Page === 'function',
1315
- });
1316
- }
1317
- catch { }
1318
- }
1319
1112
  if (VERBOSE) {
1320
1113
  console.log('[hmr-client] Single-path: replace current root Page');
1321
1114
  console.log('[hmr-client] Component details:', {
@@ -1522,12 +1315,6 @@ async function performResetRoot(newComponent) {
1522
1315
  const isAuthoritativeFrame = !!existingAppFrame && existingAppFrame !== placeholderFrame;
1523
1316
  if (!hadPlaceholder && !isFrameRoot && isAuthoritativeFrame && typeof existingAppFrame.navigate === 'function') {
1524
1317
  try {
1525
- if (VERBOSE)
1526
- console.log('[hmr-client] navigating authoritative app Frame to new Page (no placeholder, smooth swap)');
1527
- try {
1528
- attachDiagnosticsToFrame(existingAppFrame);
1529
- }
1530
- catch { }
1531
1318
  const navEntry = {
1532
1319
  create: () => preparedRoot,
1533
1320
  clearHistory: true,
@@ -1597,7 +1384,6 @@ async function performResetRoot(newComponent) {
1597
1384
  // Use the previously created preparedRoot
1598
1385
  const entry = {
1599
1386
  create: () => {
1600
- var _a, _b;
1601
1387
  if (!isFrameRoot) {
1602
1388
  const F = getCore('Frame');
1603
1389
  const fr = new F();
@@ -1612,68 +1398,30 @@ async function performResetRoot(newComponent) {
1612
1398
  };
1613
1399
  try {
1614
1400
  fr.navigate(navEntry);
1615
- if (VERBOSE)
1616
- console.log('[hmr-client] resetRootView:create navigated Frame');
1617
1401
  }
1618
1402
  catch (e) {
1619
1403
  console.warn('[hmr-client] resetRootView:create navigate failed', e);
1620
1404
  }
1621
- try {
1622
- attachDiagnosticsToFrame(fr);
1623
- }
1624
- catch { }
1625
1405
  setRootFrame(fr);
1626
- try {
1627
- (_a = getRootFrame()).__ns_tag || (_a.__ns_tag = Math.random().toString(36).slice(2));
1628
- console.log('[diag][root] ROOT_FRAME set (new)', {
1629
- tag: getRootFrame()?.__ns_tag,
1630
- type: getRootFrame()?.constructor?.name,
1631
- });
1632
- }
1633
- catch { }
1634
1406
  return fr;
1635
1407
  }
1636
1408
  else {
1637
1409
  const fr = preparedRoot;
1638
- if (VERBOSE)
1639
- console.log('[hmr-client] resetRootView:create using provided Frame', { type: fr?.constructor?.name });
1640
- try {
1641
- attachDiagnosticsToFrame(fr);
1642
- }
1643
- catch { }
1644
1410
  setRootFrame(fr);
1645
- try {
1646
- (_b = getRootFrame()).__ns_tag || (_b.__ns_tag = Math.random().toString(36).slice(2));
1647
- console.log('[diag][root] ROOT_FRAME set (provided)', {
1648
- tag: getRootFrame()?.__ns_tag,
1649
- type: getRootFrame()?.constructor?.name,
1650
- });
1651
- }
1652
- catch { }
1653
1411
  return fr;
1654
1412
  }
1655
1413
  },
1656
1414
  };
1657
- if (VERBOSE)
1658
- console.log('[hmr-client] invoking Application.resetRootView with entry (always)', { isFrameRoot, hadPlaceholder, isIOS });
1659
1415
  // Always use an entry with a create() function to avoid cross‑realm instanceof checks on Android.
1660
1416
  App2.resetRootView(entry);
1661
1417
  // After authoritative reset, it's safe to detach the early placeholder launch handler
1662
1418
  try {
1663
1419
  const restore = globalThis.__NS_DEV_RESTORE_PLACEHOLDER__;
1664
1420
  if (typeof restore === 'function') {
1665
- if (VERBOSE)
1666
- console.log('[hmr-client] restoring: detach early placeholder launch handler');
1667
1421
  restore();
1668
1422
  }
1669
1423
  }
1670
1424
  catch { }
1671
- if (VERBOSE) {
1672
- logUiSnapshot('post-resetRootView');
1673
- console.log('[hmr-client] performResetRoot completed', {
1674
- elapsedMs: Date.now() - tStart,
1675
- });
1676
- }
1677
1425
  return true;
1678
1426
  }
1679
1427
  catch (e) {