@nativescript/vite 8.0.0-alpha.1 → 8.0.0-alpha.3
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/angular.d.ts +1 -1
- package/configuration/angular.js +323 -119
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +41 -24
- package/configuration/base.js.map +1 -1
- package/configuration/javascript.js +3 -3
- package/configuration/javascript.js.map +1 -1
- package/configuration/solid.js +7 -0
- package/configuration/solid.js.map +1 -1
- package/configuration/typescript.js +3 -3
- package/configuration/typescript.js.map +1 -1
- package/helpers/angular/angular-linker.js +39 -34
- package/helpers/angular/angular-linker.js.map +1 -1
- package/helpers/angular/inline-decorator-component-templates.d.ts +3 -0
- package/helpers/angular/inline-decorator-component-templates.js +400 -0
- package/helpers/angular/inline-decorator-component-templates.js.map +1 -0
- package/helpers/angular/shared-linker.d.ts +7 -0
- package/helpers/angular/shared-linker.js +37 -1
- package/helpers/angular/shared-linker.js.map +1 -1
- package/helpers/angular/synthesize-decorator-ctor-parameters.d.ts +1 -0
- package/helpers/angular/synthesize-decorator-ctor-parameters.js +256 -0
- package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +1 -0
- package/helpers/angular/synthesize-injectable-factories.d.ts +3 -0
- package/helpers/angular/synthesize-injectable-factories.js +414 -0
- package/helpers/angular/synthesize-injectable-factories.js.map +1 -0
- package/helpers/commonjs-plugins.d.ts +5 -2
- package/helpers/commonjs-plugins.js +126 -0
- package/helpers/commonjs-plugins.js.map +1 -1
- package/helpers/esbuild-platform-resolver.js +5 -5
- package/helpers/esbuild-platform-resolver.js.map +1 -1
- package/helpers/external-configs.d.ts +9 -1
- package/helpers/external-configs.js +31 -6
- package/helpers/external-configs.js.map +1 -1
- package/helpers/import-meta-path.d.ts +4 -0
- package/helpers/import-meta-path.js +5 -0
- package/helpers/import-meta-path.js.map +1 -0
- package/helpers/import-specifier.d.ts +1 -0
- package/helpers/import-specifier.js +18 -0
- package/helpers/import-specifier.js.map +1 -0
- package/helpers/main-entry.d.ts +5 -2
- package/helpers/main-entry.js +112 -95
- package/helpers/main-entry.js.map +1 -1
- package/helpers/nativeclass-transform.js +8 -127
- package/helpers/nativeclass-transform.js.map +1 -1
- package/helpers/nativeclass-transformer-plugin.d.ts +12 -1
- package/helpers/nativeclass-transformer-plugin.js +175 -36
- package/helpers/nativeclass-transformer-plugin.js.map +1 -1
- package/hmr/client/css-handler.js +60 -20
- package/hmr/client/css-handler.js.map +1 -1
- package/hmr/client/index.js +524 -23
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.js +57 -6
- package/hmr/client/utils.js.map +1 -1
- package/hmr/entry-runtime.d.ts +10 -0
- package/hmr/entry-runtime.js +263 -21
- package/hmr/entry-runtime.js.map +1 -1
- package/hmr/frameworks/angular/client/index.d.ts +2 -1
- package/hmr/frameworks/angular/client/index.js +72 -19
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/server/linker.js +36 -2
- package/hmr/frameworks/angular/server/linker.js.map +1 -1
- package/hmr/frameworks/angular/server/strategy.js +20 -5
- package/hmr/frameworks/angular/server/strategy.js.map +1 -1
- package/hmr/frameworks/typescript/server/strategy.js +8 -2
- package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
- package/hmr/helpers/ast-normalizer.js +22 -10
- package/hmr/helpers/ast-normalizer.js.map +1 -1
- package/hmr/server/constants.d.ts +1 -0
- package/hmr/server/constants.js +2 -0
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +43 -0
- package/hmr/server/core-sanitize.js +219 -13
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/import-map.d.ts +65 -0
- package/hmr/server/import-map.js +219 -0
- package/hmr/server/import-map.js.map +1 -0
- package/hmr/server/index.d.ts +2 -1
- package/hmr/server/index.js.map +1 -1
- package/hmr/server/runtime-graph-filter.d.ts +5 -0
- package/hmr/server/runtime-graph-filter.js +21 -0
- package/hmr/server/runtime-graph-filter.js.map +1 -0
- package/hmr/server/shared-transform-request.d.ts +12 -0
- package/hmr/server/shared-transform-request.js +137 -0
- package/hmr/server/shared-transform-request.js.map +1 -0
- package/hmr/server/vite-plugin.d.ts +21 -1
- package/hmr/server/vite-plugin.js +443 -22
- package/hmr/server/vite-plugin.js.map +1 -1
- package/hmr/server/websocket-angular-entry.d.ts +2 -0
- package/hmr/server/websocket-angular-entry.js +68 -0
- package/hmr/server/websocket-angular-entry.js.map +1 -0
- package/hmr/server/websocket-angular-hot-update.d.ts +61 -0
- package/hmr/server/websocket-angular-hot-update.js +239 -0
- package/hmr/server/websocket-angular-hot-update.js.map +1 -0
- package/hmr/server/websocket-core-bridge.d.ts +23 -0
- package/hmr/server/websocket-core-bridge.js +360 -0
- package/hmr/server/websocket-core-bridge.js.map +1 -0
- package/hmr/server/websocket-graph-upsert.d.ts +6 -0
- package/hmr/server/websocket-graph-upsert.js +13 -0
- package/hmr/server/websocket-graph-upsert.js.map +1 -0
- package/hmr/server/websocket-module-bindings.d.ts +6 -0
- package/hmr/server/websocket-module-bindings.js +471 -0
- package/hmr/server/websocket-module-bindings.js.map +1 -0
- package/hmr/server/websocket-module-specifiers.d.ts +37 -0
- package/hmr/server/websocket-module-specifiers.js +637 -0
- package/hmr/server/websocket-module-specifiers.js.map +1 -0
- package/hmr/server/websocket.d.ts +26 -3
- package/hmr/server/websocket.js +1402 -678
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/package-classifier.d.ts +9 -0
- package/hmr/shared/package-classifier.js +58 -0
- package/hmr/shared/package-classifier.js.map +1 -0
- package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
- package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
- package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay.d.ts +38 -0
- package/hmr/shared/runtime/dev-overlay.js +675 -0
- package/hmr/shared/runtime/dev-overlay.js.map +1 -0
- package/hmr/shared/runtime/http-only-boot.d.ts +1 -0
- package/hmr/shared/runtime/http-only-boot.js +53 -6
- package/hmr/shared/runtime/http-only-boot.js.map +1 -1
- package/hmr/shared/runtime/module-provenance.d.ts +1 -0
- package/hmr/shared/runtime/module-provenance.js +66 -0
- package/hmr/shared/runtime/module-provenance.js.map +1 -0
- package/hmr/shared/runtime/platform-polyfills.d.ts +26 -0
- package/hmr/shared/runtime/platform-polyfills.js +122 -0
- package/hmr/shared/runtime/platform-polyfills.js.map +1 -0
- package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
- package/hmr/shared/runtime/root-placeholder.js +576 -76
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
- package/hmr/shared/runtime/session-bootstrap.js +146 -0
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
- package/hmr/shared/runtime/vendor-bootstrap.js +51 -6
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest.d.ts +7 -0
- package/hmr/shared/vendor/manifest.js +363 -23
- package/hmr/shared/vendor/manifest.js.map +1 -1
- package/hmr/shared/vendor/registry.js +104 -7
- package/hmr/shared/vendor/registry.js.map +1 -1
- package/package.json +12 -2
- package/runtime/core-aliases-early.js +83 -32
- package/runtime/core-aliases-early.js.map +1 -1
- package/shims/solid-jsx-runtime.d.ts +7 -0
- package/shims/solid-jsx-runtime.js +17 -0
- package/shims/solid-jsx-runtime.js.map +1 -0
package/hmr/client/index.js
CHANGED
|
@@ -8,6 +8,38 @@
|
|
|
8
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
|
+
function resolveTargetFlavor() {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof __NS_TARGET_FLAVOR__ !== 'undefined' && __NS_TARGET_FLAVOR__) {
|
|
14
|
+
return __NS_TARGET_FLAVOR__;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
try {
|
|
19
|
+
const g = globalThis;
|
|
20
|
+
if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
|
|
21
|
+
return g.__NS_TARGET_FLAVOR__;
|
|
22
|
+
}
|
|
23
|
+
if (typeof g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__ === 'string' && g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__) {
|
|
24
|
+
return g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__;
|
|
25
|
+
}
|
|
26
|
+
if (typeof g.__reboot_ng_modules__ === 'function') {
|
|
27
|
+
return 'angular';
|
|
28
|
+
}
|
|
29
|
+
if (g.__VUE_HMR_RUNTIME__ || g.__NS_HMR_VUE_SFC_REGISTRY__) {
|
|
30
|
+
return 'vue';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const TARGET_FLAVOR = resolveTargetFlavor();
|
|
37
|
+
try {
|
|
38
|
+
if (TARGET_FLAVOR && !globalThis.__NS_TARGET_FLAVOR__) {
|
|
39
|
+
globalThis.__NS_TARGET_FLAVOR__ = TARGET_FLAVOR;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
11
43
|
const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
|
|
12
44
|
const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
|
|
13
45
|
const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
|
|
@@ -29,8 +61,9 @@ function ensureCoreAliasesOnGlobalThis() {
|
|
|
29
61
|
}
|
|
30
62
|
catch { }
|
|
31
63
|
try {
|
|
32
|
-
if (A && !g.Application)
|
|
64
|
+
if (A && (!g.Application || typeof g.Application.run !== 'function' || typeof g.Application.on !== 'function' || typeof g.Application.resetRootView !== 'function')) {
|
|
33
65
|
g.Application = A;
|
|
66
|
+
}
|
|
34
67
|
}
|
|
35
68
|
catch { }
|
|
36
69
|
try {
|
|
@@ -43,12 +76,78 @@ function ensureCoreAliasesOnGlobalThis() {
|
|
|
43
76
|
}
|
|
44
77
|
// Apply once on module evaluation
|
|
45
78
|
ensureCoreAliasesOnGlobalThis();
|
|
79
|
+
function getHmrOverlayApi() {
|
|
80
|
+
try {
|
|
81
|
+
return globalThis.__NS_HMR_DEV_OVERLAY__ || null;
|
|
82
|
+
}
|
|
83
|
+
catch { }
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function setConnectionOverlayStage(stage, detail) {
|
|
87
|
+
try {
|
|
88
|
+
const api = getHmrOverlayApi();
|
|
89
|
+
if (api && typeof api.setConnectionStage === 'function') {
|
|
90
|
+
api.setConnectionStage(stage, { detail });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
}
|
|
95
|
+
function hideConnectionOverlay() {
|
|
96
|
+
try {
|
|
97
|
+
const api = getHmrOverlayApi();
|
|
98
|
+
if (api && typeof api.hide === 'function') {
|
|
99
|
+
api.hide('healthy');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
}
|
|
104
|
+
let connectionOverlayTimer = null;
|
|
105
|
+
let connectionOverlayVisible = false;
|
|
106
|
+
let hasOpenedHmrSocket = false;
|
|
107
|
+
let awaitingHealthyHmrMessage = false;
|
|
108
|
+
let pendingConnectionOverlayStage = 'connecting';
|
|
109
|
+
let pendingConnectionOverlayDetail = '';
|
|
110
|
+
function clearConnectionOverlayTimer() {
|
|
111
|
+
if (connectionOverlayTimer) {
|
|
112
|
+
clearTimeout(connectionOverlayTimer);
|
|
113
|
+
connectionOverlayTimer = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function showConnectionOverlayNow(stage, detail) {
|
|
117
|
+
pendingConnectionOverlayStage = stage;
|
|
118
|
+
pendingConnectionOverlayDetail = detail || '';
|
|
119
|
+
connectionOverlayVisible = true;
|
|
120
|
+
setConnectionOverlayStage(stage, detail);
|
|
121
|
+
}
|
|
122
|
+
function scheduleConnectionOverlay(stage, detail, delayMs = 1200) {
|
|
123
|
+
pendingConnectionOverlayStage = stage;
|
|
124
|
+
pendingConnectionOverlayDetail = detail || '';
|
|
125
|
+
clearConnectionOverlayTimer();
|
|
126
|
+
connectionOverlayTimer = setTimeout(() => {
|
|
127
|
+
showConnectionOverlayNow(pendingConnectionOverlayStage, pendingConnectionOverlayDetail);
|
|
128
|
+
}, delayMs);
|
|
129
|
+
}
|
|
130
|
+
function updateConnectionOverlay(stage, detail) {
|
|
131
|
+
pendingConnectionOverlayStage = stage;
|
|
132
|
+
pendingConnectionOverlayDetail = detail || '';
|
|
133
|
+
if (connectionOverlayVisible) {
|
|
134
|
+
showConnectionOverlayNow(stage, detail);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function markHmrConnectionHealthy() {
|
|
138
|
+
awaitingHealthyHmrMessage = false;
|
|
139
|
+
clearConnectionOverlayTimer();
|
|
140
|
+
if (connectionOverlayVisible) {
|
|
141
|
+
connectionOverlayVisible = false;
|
|
142
|
+
hideConnectionOverlay();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
46
145
|
/**
|
|
47
146
|
* Flavor hooks
|
|
48
147
|
*/
|
|
49
|
-
import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate } from '../frameworks/vue/client/index.js';
|
|
148
|
+
import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
|
|
50
149
|
import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
|
|
51
|
-
switch (
|
|
150
|
+
switch (TARGET_FLAVOR) {
|
|
52
151
|
case 'vue':
|
|
53
152
|
installNsVueDevShims();
|
|
54
153
|
break;
|
|
@@ -60,6 +159,10 @@ switch (__NS_TARGET_FLAVOR__) {
|
|
|
60
159
|
let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
|
|
61
160
|
// Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
|
|
62
161
|
let initialMounting = !!globalThis.__NS_HMR_INITIAL_MOUNT_IN_PROGRESS__;
|
|
162
|
+
// Track whether the first full-graph has been received. Before the full-graph,
|
|
163
|
+
// delta messages are just the server discovering modules during initial boot —
|
|
164
|
+
// NOT actual code changes. Re-imports must be gated behind this flag.
|
|
165
|
+
let hasReceivedFullGraph = false;
|
|
63
166
|
// TypeScript flavor: track registry modules and inferred main id
|
|
64
167
|
let tsModuleSet = null;
|
|
65
168
|
let tsMainId = null;
|
|
@@ -94,10 +197,14 @@ function applyFullGraph(payload) {
|
|
|
94
197
|
console.log('[hmr][graph] full graph applied version', getGraphVersion(), 'modules=', graph.size);
|
|
95
198
|
// Guarded initial mount rescue: if app hasn't replaced the placeholder shortly after graph arrives,
|
|
96
199
|
// perform a one-time mount. This waits briefly to let the app's main entry start() run first to avoid double mounts.
|
|
200
|
+
// TypeScript flavor: skip the rescue entirely. The HTTP boot will load main.ts which
|
|
201
|
+
// calls Application.run() (patched to resetRootView) — the rescue is redundant and
|
|
202
|
+
// causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
|
|
203
|
+
// causing a visual flash and leaving the app in an inconsistent state).
|
|
97
204
|
try {
|
|
98
205
|
const g = globalThis;
|
|
99
206
|
const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
|
|
100
|
-
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__) {
|
|
207
|
+
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
|
|
101
208
|
// simple snapshot helpers
|
|
102
209
|
const getTopmost = () => {
|
|
103
210
|
try {
|
|
@@ -170,7 +277,7 @@ function applyFullGraph(payload) {
|
|
|
170
277
|
if (VERBOSE)
|
|
171
278
|
console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
|
|
172
279
|
// Flavor-specific rescue handling
|
|
173
|
-
if (
|
|
280
|
+
if (TARGET_FLAVOR === 'typescript') {
|
|
174
281
|
// For TS apps, perform a one-time resetRootView to the conventional
|
|
175
282
|
// app root module. This mimics what Application.run would do and
|
|
176
283
|
// replaces the placeholder with the real UI without trying to
|
|
@@ -211,7 +318,7 @@ function applyFullGraph(payload) {
|
|
|
211
318
|
return;
|
|
212
319
|
}
|
|
213
320
|
let candidate = null;
|
|
214
|
-
switch (
|
|
321
|
+
switch (TARGET_FLAVOR) {
|
|
215
322
|
case 'vue': {
|
|
216
323
|
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
217
324
|
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
@@ -227,6 +334,19 @@ function applyFullGraph(payload) {
|
|
|
227
334
|
}
|
|
228
335
|
}
|
|
229
336
|
}
|
|
337
|
+
// Fallback: when the module graph is empty (Vite 7+ may not populate it
|
|
338
|
+
// before the first full-graph broadcast), check the SFC artifact registry
|
|
339
|
+
// which is populated from the ns:vue-sfc-registry message.
|
|
340
|
+
if (!candidate && sfcArtifactMap.size > 0) {
|
|
341
|
+
for (const id of sfcArtifactMap.keys()) {
|
|
342
|
+
if (/\.vue$/i.test(id)) {
|
|
343
|
+
candidate = id;
|
|
344
|
+
if (VERBOSE)
|
|
345
|
+
console.log('[hmr][init] rescue candidate from SFC registry:', id);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
230
350
|
break;
|
|
231
351
|
}
|
|
232
352
|
}
|
|
@@ -240,7 +360,7 @@ function applyFullGraph(payload) {
|
|
|
240
360
|
(async () => {
|
|
241
361
|
try {
|
|
242
362
|
let comp = null;
|
|
243
|
-
switch (
|
|
363
|
+
switch (TARGET_FLAVOR) {
|
|
244
364
|
case 'vue':
|
|
245
365
|
comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
|
|
246
366
|
break;
|
|
@@ -285,7 +405,7 @@ function applyFullGraph(payload) {
|
|
|
285
405
|
// to avoid double-mount races that can cause duplicate navigation logs.
|
|
286
406
|
if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
|
|
287
407
|
let candidate = null;
|
|
288
|
-
switch (
|
|
408
|
+
switch (TARGET_FLAVOR) {
|
|
289
409
|
case 'vue': {
|
|
290
410
|
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
291
411
|
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
@@ -301,6 +421,17 @@ function applyFullGraph(payload) {
|
|
|
301
421
|
}
|
|
302
422
|
}
|
|
303
423
|
}
|
|
424
|
+
// Fallback: SFC registry (same as rescue mount above)
|
|
425
|
+
if (!candidate && sfcArtifactMap.size > 0) {
|
|
426
|
+
for (const id of sfcArtifactMap.keys()) {
|
|
427
|
+
if (/\.vue$/i.test(id)) {
|
|
428
|
+
candidate = id;
|
|
429
|
+
if (VERBOSE)
|
|
430
|
+
console.log('[hmr][init] initial mount candidate from SFC registry:', id);
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
304
435
|
break;
|
|
305
436
|
}
|
|
306
437
|
case 'typescript': {
|
|
@@ -318,7 +449,7 @@ function applyFullGraph(payload) {
|
|
|
318
449
|
(async () => {
|
|
319
450
|
try {
|
|
320
451
|
if (VERBOSE)
|
|
321
|
-
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=',
|
|
452
|
+
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
|
|
322
453
|
// Android-only: avoid racing entry-runtime reset and Activity bring-up
|
|
323
454
|
try {
|
|
324
455
|
const g = globalThis;
|
|
@@ -352,7 +483,7 @@ function applyFullGraph(payload) {
|
|
|
352
483
|
}
|
|
353
484
|
catch { }
|
|
354
485
|
let comp = null;
|
|
355
|
-
switch (
|
|
486
|
+
switch (TARGET_FLAVOR) {
|
|
356
487
|
case 'vue':
|
|
357
488
|
comp = await loadSfcComponent(candidate, 'initial_mount');
|
|
358
489
|
break;
|
|
@@ -401,7 +532,7 @@ function applyFullGraph(payload) {
|
|
|
401
532
|
})();
|
|
402
533
|
}
|
|
403
534
|
else if (VERBOSE) {
|
|
404
|
-
console.warn('[hmr][init] no component found in graph to mount initially for flavor',
|
|
535
|
+
console.warn('[hmr][init] no component found in graph to mount initially for flavor', TARGET_FLAVOR);
|
|
405
536
|
}
|
|
406
537
|
}
|
|
407
538
|
}
|
|
@@ -427,7 +558,7 @@ function applyDelta(payload) {
|
|
|
427
558
|
setGraphVersion(payload.newVersion);
|
|
428
559
|
}
|
|
429
560
|
const changed = payload.changed || [];
|
|
430
|
-
switch (
|
|
561
|
+
switch (TARGET_FLAVOR) {
|
|
431
562
|
case 'vue':
|
|
432
563
|
recordVuePayloadChanges(changed, getGraphVersion());
|
|
433
564
|
break;
|
|
@@ -445,6 +576,16 @@ function applyDelta(payload) {
|
|
|
445
576
|
console.log('[hmr][graph] delta applied newVersion', getGraphVersion(), 'changed=', (payload.changed || []).length, 'removed=', (payload.removed || []).length, 'baseVersion=', payload.baseVersion);
|
|
446
577
|
// Queue evaluation of changed modules (placeholder pipeline)
|
|
447
578
|
if (payload.changed?.length) {
|
|
579
|
+
// Gate: Before the first full-graph is received, delta messages are the server
|
|
580
|
+
// discovering modules during initial boot — NOT actual code changes. The entry-runtime
|
|
581
|
+
// already loaded all modules; re-importing them would cause duplicate Application.run(),
|
|
582
|
+
// router initialization, and modal conflicts. Only queue re-imports after the first
|
|
583
|
+
// full-graph confirms the graph is synced and subsequent deltas are real changes.
|
|
584
|
+
if (!hasReceivedFullGraph) {
|
|
585
|
+
if (VERBOSE)
|
|
586
|
+
console.log('[hmr][delta] skipping re-import queue (initial graph build, no full-graph yet)');
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
448
589
|
// HARD SUPPRESS: the very first delta (baseVersion 0) commonly includes the app main entry which is already evaluated during bootstrap.
|
|
449
590
|
// Importing it again with a cache-bust often produces a spurious module-not-found (timestamp param treated as distinct file).
|
|
450
591
|
const isInitial = payload.baseVersion === 0;
|
|
@@ -507,7 +648,7 @@ function applyDelta(payload) {
|
|
|
507
648
|
// Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
|
|
508
649
|
function __nsNavigateUsingApp(comp, opts = {}) {
|
|
509
650
|
const g = globalThis;
|
|
510
|
-
switch (
|
|
651
|
+
switch (TARGET_FLAVOR) {
|
|
511
652
|
case 'vue':
|
|
512
653
|
ensureVueGlobals();
|
|
513
654
|
break;
|
|
@@ -535,7 +676,7 @@ function __nsNavigateUsingApp(comp, opts = {}) {
|
|
|
535
676
|
const existingApp = getCurrentApp();
|
|
536
677
|
const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
|
|
537
678
|
const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)));
|
|
538
|
-
switch (
|
|
679
|
+
switch (TARGET_FLAVOR) {
|
|
539
680
|
case 'vue':
|
|
540
681
|
ensurePiniaOnApp(app);
|
|
541
682
|
break;
|
|
@@ -686,10 +827,167 @@ async function processQueue() {
|
|
|
686
827
|
}
|
|
687
828
|
}
|
|
688
829
|
// After evaluating the batch, perform flavor-specific UI refresh.
|
|
689
|
-
switch (
|
|
830
|
+
switch (TARGET_FLAVOR) {
|
|
690
831
|
case 'vue':
|
|
691
832
|
// Vue SFCs are handled via the registry update path; nothing to do here.
|
|
692
833
|
break;
|
|
834
|
+
case 'solid': {
|
|
835
|
+
// Solid .tsx components are self-accepting via solid-refresh's inline
|
|
836
|
+
// patchRegistry — re-importing them is sufficient. For non-component
|
|
837
|
+
// .ts utility modules, we must propagate up the import graph to find
|
|
838
|
+
// the .tsx/.jsx component boundaries and re-import those so their
|
|
839
|
+
// solid-refresh proxies pick up the new dependency values.
|
|
840
|
+
try {
|
|
841
|
+
// Build reverse index: dep id → list of importer ids
|
|
842
|
+
const reverseIndex = new Map();
|
|
843
|
+
for (const [id, mod] of graph) {
|
|
844
|
+
for (const dep of mod.deps) {
|
|
845
|
+
let arr = reverseIndex.get(dep);
|
|
846
|
+
if (!arr) {
|
|
847
|
+
arr = [];
|
|
848
|
+
reverseIndex.set(dep, arr);
|
|
849
|
+
}
|
|
850
|
+
arr.push(id);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
// BFS from each non-tsx changed module up to tsx/jsx boundaries
|
|
854
|
+
const boundaries = new Set();
|
|
855
|
+
for (const id of drained) {
|
|
856
|
+
if (/\.(tsx|jsx)$/i.test(id))
|
|
857
|
+
continue; // already self-accepting
|
|
858
|
+
const visited = new Set();
|
|
859
|
+
const queue = [id];
|
|
860
|
+
while (queue.length) {
|
|
861
|
+
const cur = queue.shift();
|
|
862
|
+
if (visited.has(cur))
|
|
863
|
+
continue;
|
|
864
|
+
visited.add(cur);
|
|
865
|
+
const importers = reverseIndex.get(cur);
|
|
866
|
+
if (!importers)
|
|
867
|
+
continue;
|
|
868
|
+
for (const imp of importers) {
|
|
869
|
+
if (/\.(tsx|jsx)$/i.test(imp)) {
|
|
870
|
+
boundaries.add(imp);
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
queue.push(imp);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// Re-import each boundary so solid-refresh patchRegistry fires.
|
|
879
|
+
// For route files (TanStack Router), capture the new Route export
|
|
880
|
+
// and patch the router's existing route with the fresh loader.
|
|
881
|
+
let routesPatchCount = 0;
|
|
882
|
+
let discoveredRouter = null;
|
|
883
|
+
// Discover router: try __ns_router global (set by createNativeScriptRouter),
|
|
884
|
+
// then scan globalThis for any router-shaped object with routesById.
|
|
885
|
+
const findRouter = () => {
|
|
886
|
+
if (discoveredRouter)
|
|
887
|
+
return discoveredRouter;
|
|
888
|
+
const g = globalThis;
|
|
889
|
+
if (g.__ns_router?.routesById)
|
|
890
|
+
return (discoveredRouter = g.__ns_router);
|
|
891
|
+
// Fallback: scan common global keys for router
|
|
892
|
+
for (const key of ['__ns_router', 'router', '__router']) {
|
|
893
|
+
if (g[key]?.routesById && g[key]?.invalidate)
|
|
894
|
+
return (discoveredRouter = g[key]);
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
};
|
|
898
|
+
// Convert boundary file path to TanStack Router fullPath.
|
|
899
|
+
// e.g. /src/routes/posts.$postId.tsx → /posts/$postId
|
|
900
|
+
const boundaryToFullPath = (bid) => {
|
|
901
|
+
const m = bid.match(/\/src\/routes\/(.+)\.(tsx|jsx|ts|js)$/i);
|
|
902
|
+
if (!m)
|
|
903
|
+
return null;
|
|
904
|
+
let p = m[1];
|
|
905
|
+
// Replace dots between segments with slashes (posts.$postId → posts/$postId)
|
|
906
|
+
p = p.replace(/\./g, '/');
|
|
907
|
+
// Handle index files
|
|
908
|
+
if (p === 'index')
|
|
909
|
+
return '/';
|
|
910
|
+
if (p.endsWith('/index'))
|
|
911
|
+
p = p.slice(0, -6);
|
|
912
|
+
// Strip leading - (TanStack pathless layout convention)
|
|
913
|
+
p = p.replace(/(^|\/)-([\w])/g, '$1$2');
|
|
914
|
+
return '/' + p;
|
|
915
|
+
};
|
|
916
|
+
// Find existing route by fullPath (since new Route has no id yet)
|
|
917
|
+
const findRouteByFullPath = (router, fp) => {
|
|
918
|
+
if (!router?.routesById)
|
|
919
|
+
return null;
|
|
920
|
+
for (const rid of Object.keys(router.routesById)) {
|
|
921
|
+
const r = router.routesById[rid];
|
|
922
|
+
if (r?.fullPath === fp)
|
|
923
|
+
return r;
|
|
924
|
+
}
|
|
925
|
+
return null;
|
|
926
|
+
};
|
|
927
|
+
for (const id of boundaries) {
|
|
928
|
+
if (seen.has(id))
|
|
929
|
+
continue;
|
|
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][solid] propagated to boundary', { id, url });
|
|
937
|
+
const mod = await import(/* @vite-ignore */ url);
|
|
938
|
+
// Patch TanStack Router route loaders
|
|
939
|
+
try {
|
|
940
|
+
const newRoute = mod?.Route;
|
|
941
|
+
if (newRoute?.options?.loader) {
|
|
942
|
+
const router = findRouter();
|
|
943
|
+
const fullPath = boundaryToFullPath(id);
|
|
944
|
+
if (VERBOSE)
|
|
945
|
+
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, routesByIdKeys: router?.routesById ? Object.keys(router.routesById) : 'none' });
|
|
946
|
+
const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
|
|
947
|
+
if (existingRoute?.options) {
|
|
948
|
+
existingRoute.options.loader = newRoute.options.loader;
|
|
949
|
+
if (newRoute.options.component)
|
|
950
|
+
existingRoute.options.component = newRoute.options.component;
|
|
951
|
+
routesPatchCount++;
|
|
952
|
+
if (VERBOSE)
|
|
953
|
+
console.log('[hmr][solid] patched route loader', existingRoute.id, 'fullPath=', fullPath);
|
|
954
|
+
}
|
|
955
|
+
else if (VERBOSE) {
|
|
956
|
+
console.log('[hmr][solid] no matching route for fullPath', fullPath);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
catch (e) {
|
|
961
|
+
if (VERBOSE)
|
|
962
|
+
console.warn('[hmr][solid] route patch error', id, e);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
catch (e) {
|
|
966
|
+
if (VERBOSE)
|
|
967
|
+
console.warn('[hmr][solid] boundary re-import failed', id, e);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
// Route loaders were patched with fresh closures. The data is
|
|
971
|
+
// correct in router.state.matches[].loaderData (confirmed via
|
|
972
|
+
// diagnostics), but the currently visible page doesn't re-render
|
|
973
|
+
// because the NativeScriptRouterProvider's Solid store flush only
|
|
974
|
+
// fires through history.subscribe. TODO: find the right mechanism
|
|
975
|
+
// to trigger a Solid reactive update for the active match stores.
|
|
976
|
+
if (routesPatchCount > 0 && VERBOSE) {
|
|
977
|
+
console.log('[hmr][solid] patched', routesPatchCount, 'route loaders (data correct in match state, pending UI refresh mechanism)');
|
|
978
|
+
}
|
|
979
|
+
if (VERBOSE) {
|
|
980
|
+
if (boundaries.size)
|
|
981
|
+
console.log('[hmr][solid] propagated non-component change to', boundaries.size, 'boundaries', Array.from(boundaries));
|
|
982
|
+
console.log('[hmr][queue] Solid: modules re-imported, solid-refresh handles reactive update', drained);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
catch (e) {
|
|
986
|
+
if (VERBOSE)
|
|
987
|
+
console.warn('[hmr][solid] propagation failed', e);
|
|
988
|
+
}
|
|
989
|
+
break;
|
|
990
|
+
}
|
|
693
991
|
case 'typescript': {
|
|
694
992
|
// For TS apps, always reset back to the conventional app root.
|
|
695
993
|
// This preserves the shell (Frame, ActionBar, etc.) that the app's
|
|
@@ -702,9 +1000,124 @@ async function processQueue() {
|
|
|
702
1000
|
console.warn('[hmr][queue] TS flavor: Application.resetRootView unavailable; skipping UI refresh');
|
|
703
1001
|
break;
|
|
704
1002
|
}
|
|
1003
|
+
// Re-fetch changed XML/CSS files and update the bundled module registry
|
|
1004
|
+
// so Builder.createViewFromEntry picks up fresh content.
|
|
1005
|
+
const rawAssetIds = drained.filter((id) => /\.(xml|css|scss|sass|less)$/i.test(id));
|
|
1006
|
+
if (rawAssetIds.length && typeof g.registerModule === 'function') {
|
|
1007
|
+
const origin = getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
|
|
1008
|
+
if (origin) {
|
|
1009
|
+
for (const id of rawAssetIds) {
|
|
1010
|
+
try {
|
|
1011
|
+
const spec = normalizeSpec(id);
|
|
1012
|
+
// Fetch the raw file content directly from Vite's dev server.
|
|
1013
|
+
// Use the project-relative path which Vite serves as static files.
|
|
1014
|
+
const fetchUrl = origin + (spec.startsWith('/') ? spec : '/' + spec);
|
|
1015
|
+
if (VERBOSE)
|
|
1016
|
+
console.log('[hmr][queue] fetching raw asset', { id, fetchUrl });
|
|
1017
|
+
const resp = await fetch(fetchUrl);
|
|
1018
|
+
if (resp.ok) {
|
|
1019
|
+
const rawContent = await resp.text();
|
|
1020
|
+
// Register under all nickname variants the module registry uses.
|
|
1021
|
+
// The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
|
|
1022
|
+
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
1023
|
+
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
1024
|
+
if (relPath.startsWith(appVirtual))
|
|
1025
|
+
relPath = relPath.slice(appVirtual.length);
|
|
1026
|
+
const nicknames = ['./' + relPath, relPath];
|
|
1027
|
+
// Also add without extension for CSS
|
|
1028
|
+
const extIdx = relPath.lastIndexOf('.');
|
|
1029
|
+
if (extIdx > 0) {
|
|
1030
|
+
const baseName = relPath.slice(0, extIdx);
|
|
1031
|
+
if (!relPath.endsWith('.xml'))
|
|
1032
|
+
nicknames.push(baseName, './' + baseName);
|
|
1033
|
+
}
|
|
1034
|
+
for (const name of nicknames) {
|
|
1035
|
+
if (VERBOSE)
|
|
1036
|
+
console.log('[hmr][queue] re-registering module', name);
|
|
1037
|
+
g.registerModule(name, () => rawContent);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
else if (VERBOSE) {
|
|
1041
|
+
console.warn('[hmr][queue] raw asset fetch failed', id, resp.status);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
catch (e) {
|
|
1045
|
+
if (VERBOSE)
|
|
1046
|
+
console.warn('[hmr][queue] raw asset refresh failed for', id, e);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
// Determine if we can navigate in-place to a changed page
|
|
1052
|
+
// instead of resetting all the way back to app-root.
|
|
1053
|
+
// This keeps the user on the page they're editing for faster iteration.
|
|
1054
|
+
const changedXmlPages = drained
|
|
1055
|
+
.filter((id) => /\.xml$/i.test(id))
|
|
1056
|
+
.map((id) => {
|
|
1057
|
+
const spec = normalizeSpec(id);
|
|
1058
|
+
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
1059
|
+
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
1060
|
+
if (relPath.startsWith(appVirtual))
|
|
1061
|
+
relPath = relPath.slice(appVirtual.length);
|
|
1062
|
+
// Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
|
|
1063
|
+
return relPath.replace(/\.xml$/i, '');
|
|
1064
|
+
})
|
|
1065
|
+
.filter((m) => m && m !== 'app-root');
|
|
1066
|
+
// Resolve the topmost Frame from the bundled realm.
|
|
1067
|
+
// Frame.topmost() relies on an internal frameStack array, so we must
|
|
1068
|
+
// call it on the bundled-realm class. Multiple strategies to find it:
|
|
1069
|
+
const FrameClass = getCore('Frame') || g.Frame;
|
|
1070
|
+
let topFrame = null;
|
|
1071
|
+
// 1) Try the vendor-realm static topmost()
|
|
1072
|
+
try {
|
|
1073
|
+
topFrame = FrameClass?.topmost?.();
|
|
1074
|
+
}
|
|
1075
|
+
catch { }
|
|
1076
|
+
// 2) Try getting the root view from Application — if it's a Frame, use it
|
|
1077
|
+
if (!topFrame) {
|
|
1078
|
+
try {
|
|
1079
|
+
const rootView = App.getRootView?.() || App._rootView;
|
|
1080
|
+
if (rootView) {
|
|
1081
|
+
// rootView could be a Frame itself, or contain a Frame
|
|
1082
|
+
const isFrame = rootView.constructor?.name === 'Frame' || rootView.navigate;
|
|
1083
|
+
if (isFrame) {
|
|
1084
|
+
topFrame = rootView;
|
|
1085
|
+
}
|
|
1086
|
+
else if (rootView.getChildAt) {
|
|
1087
|
+
// Walk direct children looking for a Frame
|
|
1088
|
+
for (let i = 0; i < (rootView.getChildrenCount?.() || 0); i++) {
|
|
1089
|
+
const child = rootView.getChildAt(i);
|
|
1090
|
+
if (child?.constructor?.name === 'Frame' || child?.navigate) {
|
|
1091
|
+
topFrame = child;
|
|
1092
|
+
break;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
catch { }
|
|
1099
|
+
}
|
|
705
1100
|
if (VERBOSE)
|
|
706
|
-
console.log('[hmr][queue] TS
|
|
707
|
-
|
|
1101
|
+
console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
|
|
1102
|
+
if (changedXmlPages.length > 0 && topFrame) {
|
|
1103
|
+
// Navigate the current frame to the changed page directly.
|
|
1104
|
+
// Use the last changed XML page (most specific).
|
|
1105
|
+
const moduleName = changedXmlPages[changedXmlPages.length - 1];
|
|
1106
|
+
if (VERBOSE)
|
|
1107
|
+
console.log('[hmr][queue] TS: navigating in-place to', moduleName);
|
|
1108
|
+
try {
|
|
1109
|
+
topFrame.navigate({ moduleName, clearHistory: false, animated: false });
|
|
1110
|
+
}
|
|
1111
|
+
catch (navErr) {
|
|
1112
|
+
console.warn('[hmr][queue] TS flavor: in-place navigate failed, falling back to resetRootView', navErr);
|
|
1113
|
+
App.resetRootView({ moduleName: 'app-root' });
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
if (VERBOSE)
|
|
1118
|
+
console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
|
|
1119
|
+
App.resetRootView({ moduleName: 'app-root' });
|
|
1120
|
+
}
|
|
708
1121
|
}
|
|
709
1122
|
catch (e) {
|
|
710
1123
|
console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
|
|
@@ -752,6 +1165,11 @@ function connectHmr() {
|
|
|
752
1165
|
console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
|
|
753
1166
|
return;
|
|
754
1167
|
}
|
|
1168
|
+
try {
|
|
1169
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1170
|
+
}
|
|
1171
|
+
catch { }
|
|
1172
|
+
const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
|
|
755
1173
|
const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
|
|
756
1174
|
const buildCandidates = (url) => {
|
|
757
1175
|
let candidates = [];
|
|
@@ -795,7 +1213,7 @@ function connectHmr() {
|
|
|
795
1213
|
if (seen.has(key))
|
|
796
1214
|
continue;
|
|
797
1215
|
seen.add(key);
|
|
798
|
-
const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}`;
|
|
1216
|
+
const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}${u.search || ''}`;
|
|
799
1217
|
candidates.push(cand);
|
|
800
1218
|
}
|
|
801
1219
|
}
|
|
@@ -811,10 +1229,19 @@ function connectHmr() {
|
|
|
811
1229
|
let idx = 0;
|
|
812
1230
|
const tryNext = () => {
|
|
813
1231
|
if (idx >= candidates.length) {
|
|
1232
|
+
showConnectionOverlayNow('offline', 'Waiting for the Vite websocket to come back.');
|
|
814
1233
|
console.warn('[hmr-client] All WS candidates failed:', candidates.join(', '));
|
|
1234
|
+
setTimeout(connectHmr, 1500);
|
|
815
1235
|
return;
|
|
816
1236
|
}
|
|
817
1237
|
const url = candidates[idx++];
|
|
1238
|
+
const connectionDetail = `${overlayStage === 'reconnecting' ? 'Retrying' : 'Opening'} ${url}`;
|
|
1239
|
+
if (connectionOverlayVisible) {
|
|
1240
|
+
updateConnectionOverlay(overlayStage, connectionDetail);
|
|
1241
|
+
}
|
|
1242
|
+
else {
|
|
1243
|
+
scheduleConnectionOverlay(overlayStage, connectionDetail);
|
|
1244
|
+
}
|
|
818
1245
|
try {
|
|
819
1246
|
if (__NS_ENV_VERBOSE__)
|
|
820
1247
|
console.log('[hmr-client] Connecting to HMR WebSocket:', url);
|
|
@@ -836,6 +1263,16 @@ function connectHmr() {
|
|
|
836
1263
|
sock.onopen = () => {
|
|
837
1264
|
opened = true;
|
|
838
1265
|
clearTimeout(timeout);
|
|
1266
|
+
clearConnectionOverlayTimer();
|
|
1267
|
+
hasOpenedHmrSocket = true;
|
|
1268
|
+
awaitingHealthyHmrMessage = true;
|
|
1269
|
+
try {
|
|
1270
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = true;
|
|
1271
|
+
}
|
|
1272
|
+
catch { }
|
|
1273
|
+
if (connectionOverlayVisible) {
|
|
1274
|
+
showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
|
|
1275
|
+
}
|
|
839
1276
|
VERBOSE && console.log('[hmr-client] Connected to HMR WebSocket');
|
|
840
1277
|
};
|
|
841
1278
|
sock.onmessage = handleHmrMessage;
|
|
@@ -845,6 +1282,10 @@ function connectHmr() {
|
|
|
845
1282
|
};
|
|
846
1283
|
sock.onclose = (ev) => {
|
|
847
1284
|
clearTimeout(timeout);
|
|
1285
|
+
try {
|
|
1286
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1287
|
+
}
|
|
1288
|
+
catch { }
|
|
848
1289
|
if (!opened) {
|
|
849
1290
|
// immediate failure during connect → try another candidate
|
|
850
1291
|
if (VERBOSE)
|
|
@@ -854,6 +1295,7 @@ function connectHmr() {
|
|
|
854
1295
|
else {
|
|
855
1296
|
if (VERBOSE)
|
|
856
1297
|
console.log('[hmr-client] WebSocket closed (code', ev?.code, '), will reconnect…');
|
|
1298
|
+
scheduleConnectionOverlay('reconnecting', 'The websocket closed. Waiting to reconnect.', 700);
|
|
857
1299
|
// try to reconnect with full candidate list again
|
|
858
1300
|
setTimeout(connectHmr, 1000);
|
|
859
1301
|
}
|
|
@@ -874,6 +1316,12 @@ async function handleHmrMessage(ev) {
|
|
|
874
1316
|
catch {
|
|
875
1317
|
return;
|
|
876
1318
|
}
|
|
1319
|
+
if (awaitingHealthyHmrMessage && msg) {
|
|
1320
|
+
markHmrConnectionHealthy();
|
|
1321
|
+
}
|
|
1322
|
+
if (VERBOSE && msg?.type) {
|
|
1323
|
+
console.log('[hmr-client] received message', msg.type);
|
|
1324
|
+
}
|
|
877
1325
|
// Notify optional app-level hook after an HMR batch is applied.
|
|
878
1326
|
function notifyAppHmrUpdate(kind, changedIds) {
|
|
879
1327
|
try {
|
|
@@ -896,6 +1344,34 @@ async function handleHmrMessage(ev) {
|
|
|
896
1344
|
const prevGraph = new Map(graph);
|
|
897
1345
|
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
898
1346
|
applyFullGraph(msg);
|
|
1347
|
+
hasReceivedFullGraph = true;
|
|
1348
|
+
// Gate: On first boot, the entry-runtime handles all initial module loading
|
|
1349
|
+
// (with the import map already configured). Don't re-import here — the graph
|
|
1350
|
+
// is stored above for future HMR delta comparisons, but modules are already
|
|
1351
|
+
// loaded correctly via the entry-runtime boot sequence.
|
|
1352
|
+
//
|
|
1353
|
+
// Two cases to catch:
|
|
1354
|
+
// 1. Boot still in progress (__NS_HMR_BOOT_COMPLETE__ is false)
|
|
1355
|
+
// 2. Boot already finished but this is the FIRST full-graph (prevGraph was
|
|
1356
|
+
// empty). The WebSocket often connects after entry-runtime finishes, so
|
|
1357
|
+
// boot is "complete" but we still shouldn't re-import — all modules were
|
|
1358
|
+
// just loaded fresh. Only re-import on subsequent full-graphs (reconnect
|
|
1359
|
+
// scenarios) where prevGraph already has entries.
|
|
1360
|
+
if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
|
|
1361
|
+
if (VERBOSE)
|
|
1362
|
+
console.info('[hmr][full-graph] skipping initial re-import (boot in progress)');
|
|
1363
|
+
const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
|
|
1364
|
+
notifyAppHmrUpdate('full-graph', fullIds);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
if (prevGraph.size === 0) {
|
|
1368
|
+
if (VERBOSE)
|
|
1369
|
+
console.info('[hmr][full-graph] skipping re-import on first graph after boot (modules already fresh)');
|
|
1370
|
+
const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
|
|
1371
|
+
notifyAppHmrUpdate('full-graph', fullIds);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
// Reconnect / resync case — re-import changed modules as normal.
|
|
899
1375
|
// In some cases (e.g. server chooses full-graph resync / page reload), we won't
|
|
900
1376
|
// receive a delta queue to re-import changed TS modules. Without re-import,
|
|
901
1377
|
// HTTP ESM caching means module bodies (and side effects) won't re-run.
|
|
@@ -998,8 +1474,13 @@ async function handleHmrMessage(ev) {
|
|
|
998
1474
|
notifyAppHmrUpdate('delta', deltaIds);
|
|
999
1475
|
return;
|
|
1000
1476
|
}
|
|
1001
|
-
else
|
|
1002
|
-
|
|
1477
|
+
else {
|
|
1478
|
+
if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
|
|
1479
|
+
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
1480
|
+
}
|
|
1481
|
+
if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1003
1484
|
}
|
|
1004
1485
|
}
|
|
1005
1486
|
// On-demand module fetch response (Option A)
|
|
@@ -1140,7 +1621,7 @@ async function performResetRoot(newComponent) {
|
|
|
1140
1621
|
if (cachedRoot)
|
|
1141
1622
|
return cachedRoot;
|
|
1142
1623
|
try {
|
|
1143
|
-
switch (
|
|
1624
|
+
switch (TARGET_FLAVOR) {
|
|
1144
1625
|
case 'vue':
|
|
1145
1626
|
cachedRoot = getRootForVue(newComponent, state);
|
|
1146
1627
|
break;
|
|
@@ -1455,9 +1936,29 @@ export function initHmrClient(opts) {
|
|
|
1455
1936
|
}
|
|
1456
1937
|
g.__NS_HMR_CLIENT_ACTIVE__ = true;
|
|
1457
1938
|
ensureCoreAliasesOnGlobalThis();
|
|
1458
|
-
|
|
1939
|
+
// Defer WebSocket connection until boot completes to avoid native V8 crashes
|
|
1940
|
+
// caused by concurrent WebSocket message handling + HTTP fetch during early startup.
|
|
1941
|
+
// The WebSocket is only needed for HMR updates, not the initial boot sequence.
|
|
1942
|
+
if (g.__NS_HMR_BOOT_COMPLETE__) {
|
|
1943
|
+
connectHmr();
|
|
1944
|
+
}
|
|
1945
|
+
else {
|
|
1946
|
+
const waitForBoot = () => {
|
|
1947
|
+
if (globalThis.__NS_HMR_BOOT_COMPLETE__) {
|
|
1948
|
+
if (VERBOSE)
|
|
1949
|
+
console.log('[hmr-client] boot complete, connecting HMR WebSocket');
|
|
1950
|
+
connectHmr();
|
|
1951
|
+
}
|
|
1952
|
+
else {
|
|
1953
|
+
setTimeout(waitForBoot, 100);
|
|
1954
|
+
}
|
|
1955
|
+
};
|
|
1956
|
+
if (VERBOSE)
|
|
1957
|
+
console.log('[hmr-client] deferring WebSocket connection until boot completes');
|
|
1958
|
+
setTimeout(waitForBoot, 100);
|
|
1959
|
+
}
|
|
1459
1960
|
// Best-effort: install back wrapper even before first remount; original root may be captured later
|
|
1460
|
-
switch (
|
|
1961
|
+
switch (TARGET_FLAVOR) {
|
|
1461
1962
|
case 'vue':
|
|
1462
1963
|
ensureBackWrapperInstalled(performResetRoot, getCore);
|
|
1463
1964
|
break;
|