@nativescript/vite 8.0.0-alpha.34 → 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.
- package/configuration/base.js +9 -1
- package/configuration/base.js.map +1 -1
- package/configuration/javascript.js +5 -3
- package/configuration/javascript.js.map +1 -1
- package/configuration/typescript.js +3 -2
- package/configuration/typescript.js.map +1 -1
- package/helpers/global-defines.d.ts +23 -0
- package/helpers/global-defines.js +36 -1
- package/helpers/global-defines.js.map +1 -1
- package/helpers/main-entry.d.ts +1 -0
- package/helpers/main-entry.js +18 -21
- package/helpers/main-entry.js.map +1 -1
- package/hmr/client/hmr-pending-overlay.js +12 -3
- package/hmr/client/hmr-pending-overlay.js.map +1 -1
- package/hmr/client/index.js +246 -17
- package/hmr/client/index.js.map +1 -1
- package/hmr/frameworks/angular/client/index.js +33 -10
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/vue/client/index.js +4 -1
- package/hmr/frameworks/vue/client/index.js.map +1 -1
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js +11 -3
- package/hmr/frameworks/vue/client/vue-sfc-update-overlay.js.map +1 -1
- package/package.json +1 -1
package/hmr/client/index.js
CHANGED
|
@@ -41,7 +41,11 @@ try {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
catch { }
|
|
44
|
-
|
|
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
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
-
|
|
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,11 +1462,21 @@ 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);
|
|
@@ -2241,6 +2456,20 @@ export function initHmrClient(opts) {
|
|
|
2241
2456
|
}
|
|
2242
2457
|
g.__NS_HMR_CLIENT_ACTIVE__ = true;
|
|
2243
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
|
+
}
|
|
2244
2473
|
// Defer WebSocket connection until boot completes to avoid native V8 crashes
|
|
2245
2474
|
// caused by concurrent WebSocket message handling + HTTP fetch during early startup.
|
|
2246
2475
|
// The WebSocket is only needed for HMR updates, not the initial boot sequence.
|