@nativescript/vite 8.0.0-alpha.33 → 8.0.0-alpha.35

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 (57) hide show
  1. package/configuration/base.js +9 -1
  2. package/configuration/base.js.map +1 -1
  3. package/configuration/javascript.js +4 -15
  4. package/configuration/javascript.js.map +1 -1
  5. package/configuration/typescript.js +2 -12
  6. package/configuration/typescript.js.map +1 -1
  7. package/helpers/css-platform-plugin.d.ts +14 -0
  8. package/helpers/css-platform-plugin.js +43 -25
  9. package/helpers/css-platform-plugin.js.map +1 -1
  10. package/helpers/global-defines.d.ts +76 -0
  11. package/helpers/global-defines.js +94 -14
  12. package/helpers/global-defines.js.map +1 -1
  13. package/helpers/main-entry.d.ts +1 -0
  14. package/helpers/main-entry.js +40 -29
  15. package/helpers/main-entry.js.map +1 -1
  16. package/helpers/postcss-platform-config.d.ts +17 -1
  17. package/helpers/postcss-platform-config.js +17 -34
  18. package/helpers/postcss-platform-config.js.map +1 -1
  19. package/helpers/ui-registration.d.ts +21 -0
  20. package/helpers/ui-registration.js +156 -0
  21. package/helpers/ui-registration.js.map +1 -0
  22. package/hmr/client/hmr-pending-overlay.js +12 -3
  23. package/hmr/client/hmr-pending-overlay.js.map +1 -1
  24. package/hmr/client/index.js +253 -17
  25. package/hmr/client/index.js.map +1 -1
  26. package/hmr/frameworks/angular/client/index.js +33 -10
  27. package/hmr/frameworks/angular/client/index.js.map +1 -1
  28. package/hmr/frameworks/typescript/server/strategy.js +11 -1
  29. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  30. package/hmr/frameworks/vue/client/index.js +4 -1
  31. package/hmr/frameworks/vue/client/index.js.map +1 -1
  32. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +11 -3
  33. package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -1
  34. package/hmr/helpers/ast-normalizer.d.ts +3 -1
  35. package/hmr/helpers/ast-normalizer.js +23 -4
  36. package/hmr/helpers/ast-normalizer.js.map +1 -1
  37. package/hmr/server/constants.js +7 -1
  38. package/hmr/server/constants.js.map +1 -1
  39. package/hmr/server/device-transform-helpers.js +43 -1
  40. package/hmr/server/device-transform-helpers.js.map +1 -1
  41. package/hmr/server/perf-instrumentation.d.ts +1 -1
  42. package/hmr/server/perf-instrumentation.js +2 -0
  43. package/hmr/server/perf-instrumentation.js.map +1 -1
  44. package/hmr/server/process-code-for-device.js +69 -19
  45. package/hmr/server/process-code-for-device.js.map +1 -1
  46. package/hmr/server/rewrite-imports.js +11 -0
  47. package/hmr/server/rewrite-imports.js.map +1 -1
  48. package/hmr/server/vite-plugin.js +12 -0
  49. package/hmr/server/vite-plugin.js.map +1 -1
  50. package/hmr/server/websocket-ns-m.js +6 -2
  51. package/hmr/server/websocket-ns-m.js.map +1 -1
  52. package/hmr/server/websocket-served-module-helpers.d.ts +43 -0
  53. package/hmr/server/websocket-served-module-helpers.js +109 -2
  54. package/hmr/server/websocket-served-module-helpers.js.map +1 -1
  55. package/hmr/shared/runtime/root-placeholder.js +15 -0
  56. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  57. package/package.json +1 -1
@@ -41,7 +41,11 @@ try {
41
41
  }
42
42
  }
43
43
  catch { }
44
- const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
44
+ // Define substitution does NOT reach this file (served raw from node_modules),
45
+ // so prefer the globalThis seed planted by the entry's defines-seed module —
46
+ // the '/src' literal is a last-resort default and is WRONG for 'app/'-rooted
47
+ // projects.
48
+ const APP_ROOT_VIRTUAL = (typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__) || (typeof globalThis.__NS_APP_ROOT_VIRTUAL__ === 'string' && globalThis.__NS_APP_ROOT_VIRTUAL__) || '/src';
45
49
  const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
46
50
  const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
47
51
  // Policy: by default, let the app's own main entry mount initially; HMR client handles updates/remounts only.
@@ -777,6 +781,184 @@ try {
777
781
  globalThis.__nsNavigateUsingApp = __nsNavigateUsingApp;
778
782
  }
779
783
  catch { }
784
+ const openModalRecords = [];
785
+ let modalTrackingInstalled = false;
786
+ /**
787
+ * Map a served/graph module id (e.g. `/app/modal-page.xml`) to its app-root
788
+ * relative path (`modal-page.xml`). Single mapping point — the raw-asset
789
+ * re-registration, page-navigation targets, and modal matching all derive
790
+ * from this; keep them in sync by construction.
791
+ */
792
+ function toAppRelativePath(id) {
793
+ try {
794
+ const spec = normalizeSpec(id);
795
+ const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
796
+ let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
797
+ if (relPath.startsWith(appVirtual))
798
+ relPath = relPath.slice(appVirtual.length);
799
+ return relPath || null;
800
+ }
801
+ catch {
802
+ return null;
803
+ }
804
+ }
805
+ /** App-root relative module name (no extension) for page-shaped files, else null. */
806
+ function toAppModuleName(id) {
807
+ const relPath = toAppRelativePath(id);
808
+ if (!relPath || !/\.(xml|ts|js)$/i.test(relPath))
809
+ return null;
810
+ return relPath.replace(/\.(xml|ts|js)$/i, '');
811
+ }
812
+ function ensureModalTracking() {
813
+ if (modalTrackingInstalled)
814
+ return;
815
+ try {
816
+ const View = getCore('View') || globalThis.View;
817
+ const proto = View?.prototype;
818
+ if (!proto || typeof proto.showModal !== 'function')
819
+ return;
820
+ const orig = proto.showModal;
821
+ if (orig.__nsHmrModalTracked) {
822
+ modalTrackingInstalled = true;
823
+ return;
824
+ }
825
+ const wrapped = function (...args) {
826
+ const result = orig.apply(this, args);
827
+ try {
828
+ if (typeof args[0] === 'string' && result) {
829
+ // Mirror core's getModalOptions arg shapes: (moduleName, options)
830
+ // or the deprecated positional form.
831
+ const options = args.length === 2 && args[1] && typeof args[1] === 'object' ? args[1] : { context: args[1], closeCallback: args[2], fullscreen: args[3], animated: args[4], stretched: args[5] };
832
+ const moduleName = String(args[0])
833
+ .replace(/^\.\//, '')
834
+ .replace(/\.(xml|ts|js)$/i, '');
835
+ openModalRecords.push({ moduleName, options, parent: this, modal: result });
836
+ if (VERBOSE)
837
+ console.log('[hmr][modal] tracked open modal', moduleName);
838
+ }
839
+ }
840
+ catch { }
841
+ return result;
842
+ };
843
+ wrapped.__nsHmrModalTracked = true;
844
+ proto.showModal = wrapped;
845
+ modalTrackingInstalled = true;
846
+ }
847
+ catch (e) {
848
+ if (VERBOSE)
849
+ console.warn('[hmr][modal] tracking install failed', e);
850
+ }
851
+ }
852
+ /**
853
+ * Enumerate the modals that are currently presented AND were opened by module
854
+ * name, with everything needed to re-present them.
855
+ *
856
+ * Source of truth is core's live modal stack (`_getRootModalViews()`):
857
+ * - `modal._moduleName` — set by the Builder on every createViewFromEntry
858
+ * view (longstanding, used by livesync), so available on any core.
859
+ * - `modal._modalOptions` — the original ShowModalOptions, stored by core's
860
+ * `_showNativeModalView` (newer cores). For older cores the showModal
861
+ * wrap's records (see ensureModalTracking) fill the gap.
862
+ * - `modal._modalParent` — the presenting view.
863
+ * Stale wrap records are pruned against the live stack while we're here.
864
+ */
865
+ function getOpenStringModuleModals() {
866
+ const out = [];
867
+ try {
868
+ const App = getCore('Application');
869
+ const root = App?.getRootView?.() || App?._rootView;
870
+ const stack = root?._getRootModalViews?.() || [];
871
+ // Prune wrap records whose modal is gone (keeps the fallback list small).
872
+ for (let i = openModalRecords.length - 1; i >= 0; i--) {
873
+ if (!stack.includes(openModalRecords[i].modal)) {
874
+ openModalRecords.splice(i, 1);
875
+ }
876
+ }
877
+ for (const modal of stack) {
878
+ const record = openModalRecords.find((r) => r.modal === modal);
879
+ const rawModuleName = typeof modal?._moduleName === 'string' && modal._moduleName ? modal._moduleName : record?.moduleName;
880
+ const parent = modal?._modalParent || record?.parent;
881
+ const options = modal?._modalOptions || record?.options;
882
+ if (!rawModuleName || !parent)
883
+ continue;
884
+ const moduleName = String(rawModuleName)
885
+ .replace(/^\.\//, '')
886
+ .replace(/\.(xml|ts|js)$/i, '');
887
+ out.push({ moduleName, options, parent, modal });
888
+ }
889
+ }
890
+ catch (e) {
891
+ if (VERBOSE)
892
+ console.warn('[hmr][modal] open-modal enumeration failed', e);
893
+ }
894
+ return out;
895
+ }
896
+ /**
897
+ * Close and re-present an open modal so it rebuilds from the freshly
898
+ * re-registered XML/code-behind. Core clears the modal stack synchronously on
899
+ * close but the NATIVE dismissal completes asynchronously; iOS refuses a
900
+ * present while a dismissal is in flight. Newer cores fire `closedModally` on
901
+ * the modal at exactly that completion point — preferred signal. Older cores
902
+ * fall back to polling `isLoaded` (flipped by `_tearDownUI` in the same
903
+ * completion callback).
904
+ */
905
+ async function reshowOpenModal(record) {
906
+ const { parent, modal, moduleName, options } = record;
907
+ await new Promise((resolve) => {
908
+ let settled = false;
909
+ const finish = () => {
910
+ if (!settled) {
911
+ settled = true;
912
+ resolve();
913
+ }
914
+ };
915
+ let eventArmed = false;
916
+ try {
917
+ if (typeof modal.once === 'function') {
918
+ modal.once('closedModally', finish);
919
+ eventArmed = true;
920
+ }
921
+ }
922
+ catch { }
923
+ // Poll fallback (also the safety net if the event never fires —
924
+ // e.g. an interactive-dismiss cancellation).
925
+ const deadline = Date.now() + 2000;
926
+ const poll = () => {
927
+ if (settled)
928
+ return;
929
+ let stillLoaded = false;
930
+ try {
931
+ stillLoaded = !!modal.isLoaded;
932
+ }
933
+ catch { }
934
+ if ((!eventArmed && !stillLoaded) || Date.now() > deadline) {
935
+ finish();
936
+ return;
937
+ }
938
+ setTimeout(poll, 50);
939
+ };
940
+ setTimeout(poll, 50);
941
+ try {
942
+ modal.closeModal();
943
+ }
944
+ catch (e) {
945
+ if (VERBOSE)
946
+ console.warn('[hmr][modal] close failed for', moduleName, e);
947
+ finish();
948
+ }
949
+ });
950
+ // One settle beat so the platform finishes releasing the presentation
951
+ // before the new present begins.
952
+ await new Promise((resolve) => setTimeout(resolve, 100));
953
+ try {
954
+ parent.showModal(moduleName, { ...(options || {}), animated: false });
955
+ if (VERBOSE)
956
+ console.log('[hmr][modal] re-presented', moduleName);
957
+ }
958
+ catch (e) {
959
+ console.warn('[hmr][modal] re-present failed for', moduleName, e);
960
+ }
961
+ }
780
962
  async function processQueue() {
781
963
  if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
782
964
  if (VERBOSE)
@@ -857,6 +1039,27 @@ async function processQueue() {
857
1039
  if (VERBOSE)
858
1040
  console.log('[hmr][queue] re-import', { id, spec, url });
859
1041
  const mod = await import(/* @vite-ignore */ url);
1042
+ // TS/XML flavor: refresh the bundler module registry with the fresh
1043
+ // exports so Builder.createViewFromEntry / loadModule('<page>')
1044
+ // resolves the NEW code-behind (tap handlers, page events) instead
1045
+ // of the stale module captured in the boot bundle. Without this,
1046
+ // XML re-renders pick up new markup but keep old behavior.
1047
+ if (TARGET_FLAVOR === 'typescript' && mod && /\.(ts|js)$/i.test(id)) {
1048
+ try {
1049
+ const g = globalThis;
1050
+ const moduleName = toAppModuleName(id);
1051
+ if (moduleName && typeof g.registerModule === 'function') {
1052
+ g.registerModule(moduleName, () => mod);
1053
+ g.registerModule('./' + moduleName, () => mod);
1054
+ if (VERBOSE)
1055
+ console.log('[hmr][queue] re-registered code-behind', moduleName);
1056
+ }
1057
+ }
1058
+ catch (e) {
1059
+ if (VERBOSE)
1060
+ console.warn('[hmr][queue] code-behind re-register failed for', id, e);
1061
+ }
1062
+ }
860
1063
  }
861
1064
  catch (e) {
862
1065
  if (VERBOSE)
@@ -1163,10 +1366,9 @@ async function processQueue() {
1163
1366
  const rawContent = await resp.text();
1164
1367
  // Register under all nickname variants the module registry uses.
1165
1368
  // The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
1166
- const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1167
- let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1168
- if (relPath.startsWith(appVirtual))
1169
- relPath = relPath.slice(appVirtual.length);
1369
+ const relPath = toAppRelativePath(id);
1370
+ if (!relPath)
1371
+ continue;
1170
1372
  const nicknames = ['./' + relPath, relPath];
1171
1373
  // Also add without extension for CSS
1172
1374
  const extIdx = relPath.lastIndexOf('.');
@@ -1192,21 +1394,24 @@ async function processQueue() {
1192
1394
  }
1193
1395
  }
1194
1396
  }
1397
+ // Modal-aware refresh: pages currently PRESENTED AS MODALS must be
1398
+ // closed + re-presented in place — navigating the top frame to a
1399
+ // modal's page would push it as a frame page, and resetRootView
1400
+ // would dismiss the modal entirely. State comes from core's live
1401
+ // modal stack (_moduleName/_modalOptions/_modalParent); the
1402
+ // showModal wrap only backfills options on older cores.
1403
+ ensureModalTracking();
1404
+ const openModals = getOpenStringModuleModals();
1405
+ const changedModuleNames = new Set(drained.map(toAppModuleName).filter(Boolean));
1406
+ const modalsToReshow = openModals.filter((record) => changedModuleNames.has(record.moduleName));
1407
+ const reshowModuleNames = new Set(modalsToReshow.map((record) => record.moduleName));
1195
1408
  // Determine if we can navigate in-place to a changed page
1196
1409
  // instead of resetting all the way back to app-root.
1197
1410
  // This keeps the user on the page they're editing for faster iteration.
1198
1411
  const changedXmlPages = drained
1199
1412
  .filter((id) => /\.xml$/i.test(id))
1200
- .map((id) => {
1201
- const spec = normalizeSpec(id);
1202
- const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
1203
- let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
1204
- if (relPath.startsWith(appVirtual))
1205
- relPath = relPath.slice(appVirtual.length);
1206
- // Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
1207
- return relPath.replace(/\.xml$/i, '');
1208
- })
1209
- .filter((m) => m && m !== 'app-root');
1413
+ .map((id) => toAppModuleName(id))
1414
+ .filter((m) => m && m !== 'app-root' && !reshowModuleNames.has(m));
1210
1415
  // Resolve the topmost Frame from the bundled realm.
1211
1416
  // Frame.topmost() relies on an internal frameStack array, so we must
1212
1417
  // call it on the bundled-realm class. Multiple strategies to find it:
@@ -1242,7 +1447,7 @@ async function processQueue() {
1242
1447
  catch { }
1243
1448
  }
1244
1449
  if (VERBOSE)
1245
- console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
1450
+ console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame, 'modalsToReshow=', modalsToReshow.length);
1246
1451
  if (changedXmlPages.length > 0 && topFrame) {
1247
1452
  // Navigate the current frame to the changed page directly.
1248
1453
  // Use the last changed XML page (most specific).
@@ -1257,15 +1462,32 @@ async function processQueue() {
1257
1462
  App.resetRootView({ moduleName: 'app-root' });
1258
1463
  }
1259
1464
  }
1260
- else {
1465
+ else if (modalsToReshow.length === 0) {
1466
+ // No frame page to refresh and no open modal owns the change —
1467
+ // fall back to a full root reset. (Skipped when an open modal is
1468
+ // being re-presented below: resetRootView would dismiss it.)
1261
1469
  if (VERBOSE)
1262
1470
  console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
1263
1471
  App.resetRootView({ moduleName: 'app-root' });
1264
1472
  }
1473
+ // Re-present any open modals whose XML/code-behind changed. The
1474
+ // modules were already re-registered above (raw XML assets + fresh
1475
+ // code-behind exports), so the re-presented modal rebuilds from
1476
+ // the new content while the page beneath it stays put.
1477
+ for (const record of modalsToReshow) {
1478
+ await reshowOpenModal(record);
1479
+ }
1265
1480
  }
1266
1481
  catch (e) {
1267
1482
  console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
1268
1483
  }
1484
+ // Tell the overlay the cycle is done — same as the solid path
1485
+ // above. Without this the applying overlay sticks at
1486
+ // 'received' (5%) forever even though the in-place navigate /
1487
+ // resetRootView already applied the update.
1488
+ setUpdateOverlayStage('complete', {
1489
+ detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
1490
+ });
1269
1491
  break;
1270
1492
  }
1271
1493
  }
@@ -2234,6 +2456,20 @@ export function initHmrClient(opts) {
2234
2456
  }
2235
2457
  g.__NS_HMR_CLIENT_ACTIVE__ = true;
2236
2458
  ensureCoreAliasesOnGlobalThis();
2459
+ // XML flavor: record string-module modals from the moment the client is up
2460
+ // so an already-open modal can be re-presented when its files change.
2461
+ // Installed at init (not first-update time) because the wrap can only
2462
+ // observe showModal calls made AFTER it lands. Retried briefly because
2463
+ // getCore('View') may not resolve until the vendor realm finishes booting.
2464
+ if (TARGET_FLAVOR === 'typescript') {
2465
+ const tryInstallModalTracking = (attempts) => {
2466
+ ensureModalTracking();
2467
+ if (!modalTrackingInstalled && attempts > 0) {
2468
+ setTimeout(() => tryInstallModalTracking(attempts - 1), 250);
2469
+ }
2470
+ };
2471
+ tryInstallModalTracking(40);
2472
+ }
2237
2473
  // Defer WebSocket connection until boot completes to avoid native V8 crashes
2238
2474
  // caused by concurrent WebSocket message handling + HTTP fetch during early startup.
2239
2475
  // The WebSocket is only needed for HMR updates, not the initial boot sequence.