@nativescript/vite 8.0.0-alpha.2 → 8.0.0-alpha.20
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 +34 -1
- package/configuration/angular.js +380 -34
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +171 -7
- package/configuration/base.js.map +1 -1
- package/configuration/solid.js +27 -1
- package/configuration/solid.js.map +1 -1
- package/configuration/typescript.js +1 -1
- package/configuration/typescript.js.map +1 -1
- package/helpers/angular/angular-linker.js +3 -12
- package/helpers/angular/angular-linker.js.map +1 -1
- package/helpers/angular/inject-component-hmr-registration.d.ts +112 -0
- package/helpers/angular/inject-component-hmr-registration.js +359 -0
- package/helpers/angular/inject-component-hmr-registration.js.map +1 -0
- package/helpers/angular/inject-hmr-vite-ignore.d.ts +75 -0
- package/helpers/angular/inject-hmr-vite-ignore.js +288 -0
- package/helpers/angular/inject-hmr-vite-ignore.js.map +1 -0
- package/helpers/angular/util.d.ts +1 -0
- package/helpers/angular/util.js +88 -0
- package/helpers/angular/util.js.map +1 -1
- 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/config-as-json.js +10 -0
- package/helpers/config-as-json.js.map +1 -1
- package/helpers/dev-host.d.ts +274 -0
- package/helpers/dev-host.js +491 -0
- package/helpers/dev-host.js.map +1 -0
- package/helpers/global-defines.d.ts +51 -0
- package/helpers/global-defines.js +77 -0
- package/helpers/global-defines.js.map +1 -1
- package/helpers/logging.d.ts +1 -0
- package/helpers/logging.js +63 -3
- package/helpers/logging.js.map +1 -1
- package/helpers/main-entry.d.ts +3 -1
- package/helpers/main-entry.js +450 -125
- package/helpers/main-entry.js.map +1 -1
- package/helpers/nativeclass-transformer-plugin.d.ts +9 -2
- package/helpers/nativeclass-transformer-plugin.js +157 -14
- package/helpers/nativeclass-transformer-plugin.js.map +1 -1
- package/helpers/ns-core-url.d.ts +88 -0
- package/helpers/ns-core-url.js +191 -0
- package/helpers/ns-core-url.js.map +1 -0
- package/helpers/prelink-angular.js +1 -4
- package/helpers/prelink-angular.js.map +1 -1
- package/helpers/project.d.ts +35 -0
- package/helpers/project.js +120 -2
- package/helpers/project.js.map +1 -1
- package/helpers/resolver.js +9 -1
- package/helpers/resolver.js.map +1 -1
- package/helpers/solid-jsx-deps.d.ts +15 -0
- package/helpers/solid-jsx-deps.js +178 -0
- package/helpers/solid-jsx-deps.js.map +1 -0
- package/helpers/ts-config-paths.js +50 -2
- package/helpers/ts-config-paths.js.map +1 -1
- package/helpers/workers.d.ts +20 -19
- package/helpers/workers.js +620 -3
- package/helpers/workers.js.map +1 -1
- package/hmr/client/css-handler.d.ts +1 -0
- package/hmr/client/css-handler.js +34 -5
- package/hmr/client/css-handler.js.map +1 -1
- package/hmr/client/css-update-overlay.d.ts +18 -0
- package/hmr/client/css-update-overlay.js +27 -0
- package/hmr/client/css-update-overlay.js.map +1 -0
- package/hmr/client/hmr-pending-overlay.d.ts +27 -0
- package/hmr/client/hmr-pending-overlay.js +50 -0
- package/hmr/client/hmr-pending-overlay.js.map +1 -0
- package/hmr/client/index.js +483 -33
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +5 -0
- package/hmr/client/utils.js +283 -12
- package/hmr/client/utils.js.map +1 -1
- package/hmr/client/vue-sfc-update-overlay.d.ts +82 -0
- package/hmr/client/vue-sfc-update-overlay.js +133 -0
- package/hmr/client/vue-sfc-update-overlay.js.map +1 -0
- package/hmr/entry-runtime.d.ts +2 -1
- package/hmr/entry-runtime.js +253 -66
- package/hmr/entry-runtime.js.map +1 -1
- package/hmr/frameworks/angular/client/index.d.ts +3 -1
- package/hmr/frameworks/angular/client/index.js +802 -10
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/server/linker.js +1 -4
- package/hmr/frameworks/angular/server/linker.js.map +1 -1
- package/hmr/frameworks/angular/server/strategy.js +30 -6
- 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/frameworks/vue/client/index.js +18 -42
- package/hmr/frameworks/vue/client/index.js.map +1 -1
- package/hmr/helpers/ast-normalizer.js +52 -5
- package/hmr/helpers/ast-normalizer.js.map +1 -1
- package/hmr/helpers/cjs-named-exports.d.ts +23 -0
- package/hmr/helpers/cjs-named-exports.js +152 -0
- package/hmr/helpers/cjs-named-exports.js.map +1 -0
- package/hmr/helpers/package-exports.d.ts +16 -0
- package/hmr/helpers/package-exports.js +396 -0
- package/hmr/helpers/package-exports.js.map +1 -0
- package/hmr/server/constants.js +13 -4
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +93 -8
- package/hmr/server/core-sanitize.js +222 -49
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/import-map.js +80 -22
- package/hmr/server/import-map.js.map +1 -1
- package/hmr/server/index.d.ts +2 -1
- package/hmr/server/index.js.map +1 -1
- package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
- package/hmr/server/ns-core-cjs-shape.js +271 -0
- package/hmr/server/ns-core-cjs-shape.js.map +1 -0
- package/hmr/server/ns-rt-bridge.d.ts +51 -0
- package/hmr/server/ns-rt-bridge.js +131 -0
- package/hmr/server/ns-rt-bridge.js.map +1 -0
- package/hmr/server/perf-instrumentation.d.ts +114 -0
- package/hmr/server/perf-instrumentation.js +195 -0
- package/hmr/server/perf-instrumentation.js.map +1 -0
- 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 +144 -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 +497 -58
- 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 +78 -0
- package/hmr/server/websocket-angular-hot-update.js +413 -0
- package/hmr/server/websocket-angular-hot-update.js.map +1 -0
- package/hmr/server/websocket-core-bridge.d.ts +58 -0
- package/hmr/server/websocket-core-bridge.js +368 -0
- package/hmr/server/websocket-core-bridge.js.map +1 -0
- package/hmr/server/websocket-css-hot-update.d.ts +33 -0
- package/hmr/server/websocket-css-hot-update.js +65 -0
- package/hmr/server/websocket-css-hot-update.js.map +1 -0
- package/hmr/server/websocket-graph-upsert.d.ts +21 -0
- package/hmr/server/websocket-graph-upsert.js +33 -0
- package/hmr/server/websocket-graph-upsert.js.map +1 -0
- package/hmr/server/websocket-hmr-pending.d.ts +43 -0
- package/hmr/server/websocket-hmr-pending.js +55 -0
- package/hmr/server/websocket-hmr-pending.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 +101 -0
- package/hmr/server/websocket-module-specifiers.js +820 -0
- package/hmr/server/websocket-module-specifiers.js.map +1 -0
- package/hmr/server/websocket-ns-m-finalize.d.ts +22 -0
- package/hmr/server/websocket-ns-m-finalize.js +88 -0
- package/hmr/server/websocket-ns-m-finalize.js.map +1 -0
- package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
- package/hmr/server/websocket-ns-m-paths.js +92 -0
- package/hmr/server/websocket-ns-m-paths.js.map +1 -0
- package/hmr/server/websocket-ns-m-request.d.ts +45 -0
- package/hmr/server/websocket-ns-m-request.js +196 -0
- package/hmr/server/websocket-ns-m-request.js.map +1 -0
- package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
- package/hmr/server/websocket-served-module-helpers.js +644 -0
- package/hmr/server/websocket-served-module-helpers.js.map +1 -0
- package/hmr/server/websocket-txn.d.ts +6 -0
- package/hmr/server/websocket-txn.js +45 -0
- package/hmr/server/websocket-txn.js.map +1 -0
- package/hmr/server/websocket-vendor-unifier.d.ts +10 -0
- package/hmr/server/websocket-vendor-unifier.js +51 -0
- package/hmr/server/websocket-vendor-unifier.js.map +1 -0
- package/hmr/server/websocket-vue-sfc.d.ts +26 -0
- package/hmr/server/websocket-vue-sfc.js +1053 -0
- package/hmr/server/websocket-vue-sfc.js.map +1 -0
- package/hmr/server/websocket.d.ts +58 -75
- package/hmr/server/websocket.js +2230 -1802
- 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/boot-placeholder-ui.d.ts +69 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
- package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
- package/hmr/shared/runtime/boot-progress.d.ts +40 -0
- package/hmr/shared/runtime/boot-progress.js +128 -0
- package/hmr/shared/runtime/boot-progress.js.map +1 -0
- package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
- package/hmr/shared/runtime/boot-timeline.js +52 -0
- package/hmr/shared/runtime/boot-timeline.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 +78 -3
- package/hmr/shared/runtime/dev-overlay.js +1094 -26
- package/hmr/shared/runtime/dev-overlay.js.map +1 -1
- package/hmr/shared/runtime/module-provenance.js +1 -4
- package/hmr/shared/runtime/module-provenance.js.map +1 -1
- package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
- package/hmr/shared/runtime/root-placeholder.js +1019 -151
- 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 +309 -0
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
- package/hmr/shared/runtime/vendor-bootstrap.js +1 -9
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest.d.ts +32 -0
- package/hmr/shared/vendor/manifest.js +411 -46
- package/hmr/shared/vendor/manifest.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +5 -0
- package/index.js.map +1 -1
- package/package.json +9 -1
- package/runtime/core-aliases-early.js +94 -67
- package/runtime/core-aliases-early.js.map +1 -1
package/hmr/client/index.js
CHANGED
|
@@ -5,9 +5,42 @@
|
|
|
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 } from './utils.js';
|
|
8
|
+
import { setHMRWsUrl, getHMRWsUrl, pendingModuleFetches, deriveHttpOrigin, setHttpOriginForVite, moduleFetchCache, requestModuleFromServer, getHttpOriginForVite, normalizeSpec, hmrMetrics, graph, setGraphVersion, getGraphVersion, getCurrentApp, getRootFrame, setCurrentApp, setRootFrame, getCore, hasExplicitEviction, invalidateModulesByUrls, buildEvictionUrls, emitHmrModeBannerOnce } from './utils.js';
|
|
9
9
|
import { handleCssUpdates } from './css-handler.js';
|
|
10
|
+
import { buildCssApplyingDetail, buildCssAppliedDetail } from './css-update-overlay.js';
|
|
10
11
|
const VERBOSE = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;
|
|
12
|
+
function resolveTargetFlavor() {
|
|
13
|
+
try {
|
|
14
|
+
if (typeof __NS_TARGET_FLAVOR__ !== 'undefined' && __NS_TARGET_FLAVOR__) {
|
|
15
|
+
return __NS_TARGET_FLAVOR__;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
try {
|
|
20
|
+
const g = globalThis;
|
|
21
|
+
if (typeof g.__NS_TARGET_FLAVOR__ === 'string' && g.__NS_TARGET_FLAVOR__) {
|
|
22
|
+
return g.__NS_TARGET_FLAVOR__;
|
|
23
|
+
}
|
|
24
|
+
if (typeof g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__ === 'string' && g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__) {
|
|
25
|
+
return g.__NS_HMR_BROWSER_RUNTIME_TARGET_FLAVOR__;
|
|
26
|
+
}
|
|
27
|
+
if (typeof g.__reboot_ng_modules__ === 'function') {
|
|
28
|
+
return 'angular';
|
|
29
|
+
}
|
|
30
|
+
if (g.__VUE_HMR_RUNTIME__ || g.__NS_HMR_VUE_SFC_REGISTRY__) {
|
|
31
|
+
return 'vue';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch { }
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const TARGET_FLAVOR = resolveTargetFlavor();
|
|
38
|
+
try {
|
|
39
|
+
if (TARGET_FLAVOR && !globalThis.__NS_TARGET_FLAVOR__) {
|
|
40
|
+
globalThis.__NS_TARGET_FLAVOR__ = TARGET_FLAVOR;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
11
44
|
const APP_ROOT_VIRTUAL = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
|
|
12
45
|
const APP_VIRTUAL_WITH_SLASH = APP_ROOT_VIRTUAL.endsWith('/') ? APP_ROOT_VIRTUAL : `${APP_ROOT_VIRTUAL}/`;
|
|
13
46
|
const APP_MAIN_ENTRY_SPEC = `${APP_VIRTUAL_WITH_SLASH}app.ts`;
|
|
@@ -29,8 +62,9 @@ function ensureCoreAliasesOnGlobalThis() {
|
|
|
29
62
|
}
|
|
30
63
|
catch { }
|
|
31
64
|
try {
|
|
32
|
-
if (A && !g.Application)
|
|
65
|
+
if (A && (!g.Application || typeof g.Application.run !== 'function' || typeof g.Application.on !== 'function' || typeof g.Application.resetRootView !== 'function')) {
|
|
33
66
|
g.Application = A;
|
|
67
|
+
}
|
|
34
68
|
}
|
|
35
69
|
catch { }
|
|
36
70
|
try {
|
|
@@ -68,6 +102,80 @@ function hideConnectionOverlay() {
|
|
|
68
102
|
}
|
|
69
103
|
catch { }
|
|
70
104
|
}
|
|
105
|
+
function setUpdateOverlayStage(stage, info) {
|
|
106
|
+
try {
|
|
107
|
+
const api = getHmrOverlayApi();
|
|
108
|
+
if (api && typeof api.setUpdateStage === 'function') {
|
|
109
|
+
api.setUpdateStage(stage, info);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
}
|
|
114
|
+
// Store the listener registry on globalThis (rather than in a module-private
|
|
115
|
+
// closure) because in NativeScript the HMR client module and the user app
|
|
116
|
+
// modules can resolve to different module instances depending on how the
|
|
117
|
+
// dev runtime loads them (HTTP client URL vs. the bundled vendor realm).
|
|
118
|
+
// A module-local Set would not be shared across instances; the global one
|
|
119
|
+
// is.
|
|
120
|
+
function getNsSolidHmrListenerSet() {
|
|
121
|
+
const g = globalThis;
|
|
122
|
+
let set = g.__ns_solid_hmr_listener_set;
|
|
123
|
+
if (!set) {
|
|
124
|
+
set = new Set();
|
|
125
|
+
g.__ns_solid_hmr_listener_set = set;
|
|
126
|
+
}
|
|
127
|
+
return set;
|
|
128
|
+
}
|
|
129
|
+
function nsSolidHmrSubscribe(fn) {
|
|
130
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
131
|
+
listeners.add(fn);
|
|
132
|
+
if (VERBOSE)
|
|
133
|
+
console.log('[hmr][solid] subscribe — listeners=', listeners.size);
|
|
134
|
+
return () => listeners.delete(fn);
|
|
135
|
+
}
|
|
136
|
+
function nsSolidHmrEmit(ev) {
|
|
137
|
+
const listeners = getNsSolidHmrListenerSet();
|
|
138
|
+
if (VERBOSE)
|
|
139
|
+
console.log('[hmr][solid] emit listeners=', listeners.size, 'changedFiles=', ev.changedFiles);
|
|
140
|
+
for (const fn of Array.from(listeners)) {
|
|
141
|
+
try {
|
|
142
|
+
fn(ev);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
if (VERBOSE)
|
|
146
|
+
console.warn('[hmr][solid] listener threw', err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const g = globalThis;
|
|
152
|
+
g.__ns_solid_hmr_subscribe = nsSolidHmrSubscribe;
|
|
153
|
+
// Eagerly create the listener set so the global exists at module load time.
|
|
154
|
+
getNsSolidHmrListenerSet();
|
|
155
|
+
if (VERBOSE)
|
|
156
|
+
console.log('[hmr][solid] HMR client loaded. global set=', typeof g.__ns_solid_hmr_subscribe, 'listenerSet=', typeof g.__ns_solid_hmr_listener_set);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
console.warn('[hmr][solid] could not install global __ns_solid_hmr_subscribe', err);
|
|
160
|
+
}
|
|
161
|
+
// Eagerly drive the HMR-applying overlay's 'received' frame as soon
|
|
162
|
+
// as the server emits `ns:hmr-pending`, BEFORE the framework-specific
|
|
163
|
+
// (`ns:angular-update` / `ns:css-updates`) payload arrives. The
|
|
164
|
+
// flavor-specific handler later walks through 'evicting' →
|
|
165
|
+
// 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
|
|
166
|
+
// in the same cycle is safe: the overlay preserves
|
|
167
|
+
// `updateCycleStartedAt` when a 'received' frame replaces an existing
|
|
168
|
+
// 'received' frame so the minimum-visible window is still timed
|
|
169
|
+
// against the FIRST frame.
|
|
170
|
+
//
|
|
171
|
+
// Soft-fails when the overlay isn't installed (production builds,
|
|
172
|
+
// vitest, etc.) or when the user opted out via
|
|
173
|
+
// `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
|
|
174
|
+
import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
|
|
175
|
+
import { driveVueSfcUpdateOverlay } from './vue-sfc-update-overlay.js';
|
|
176
|
+
function setHmrPendingOverlay(filePath) {
|
|
177
|
+
applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
|
|
178
|
+
}
|
|
71
179
|
let connectionOverlayTimer = null;
|
|
72
180
|
let connectionOverlayVisible = false;
|
|
73
181
|
let hasOpenedHmrSocket = false;
|
|
@@ -114,7 +222,7 @@ function markHmrConnectionHealthy() {
|
|
|
114
222
|
*/
|
|
115
223
|
import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
|
|
116
224
|
import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
|
|
117
|
-
switch (
|
|
225
|
+
switch (TARGET_FLAVOR) {
|
|
118
226
|
case 'vue':
|
|
119
227
|
installNsVueDevShims();
|
|
120
228
|
break;
|
|
@@ -171,7 +279,7 @@ function applyFullGraph(payload) {
|
|
|
171
279
|
try {
|
|
172
280
|
const g = globalThis;
|
|
173
281
|
const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
|
|
174
|
-
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ &&
|
|
282
|
+
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
|
|
175
283
|
// simple snapshot helpers
|
|
176
284
|
const getTopmost = () => {
|
|
177
285
|
try {
|
|
@@ -244,7 +352,7 @@ function applyFullGraph(payload) {
|
|
|
244
352
|
if (VERBOSE)
|
|
245
353
|
console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
|
|
246
354
|
// Flavor-specific rescue handling
|
|
247
|
-
if (
|
|
355
|
+
if (TARGET_FLAVOR === 'typescript') {
|
|
248
356
|
// For TS apps, perform a one-time resetRootView to the conventional
|
|
249
357
|
// app root module. This mimics what Application.run would do and
|
|
250
358
|
// replaces the placeholder with the real UI without trying to
|
|
@@ -285,7 +393,7 @@ function applyFullGraph(payload) {
|
|
|
285
393
|
return;
|
|
286
394
|
}
|
|
287
395
|
let candidate = null;
|
|
288
|
-
switch (
|
|
396
|
+
switch (TARGET_FLAVOR) {
|
|
289
397
|
case 'vue': {
|
|
290
398
|
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
291
399
|
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
@@ -327,7 +435,7 @@ function applyFullGraph(payload) {
|
|
|
327
435
|
(async () => {
|
|
328
436
|
try {
|
|
329
437
|
let comp = null;
|
|
330
|
-
switch (
|
|
438
|
+
switch (TARGET_FLAVOR) {
|
|
331
439
|
case 'vue':
|
|
332
440
|
comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
|
|
333
441
|
break;
|
|
@@ -372,7 +480,7 @@ function applyFullGraph(payload) {
|
|
|
372
480
|
// to avoid double-mount races that can cause duplicate navigation logs.
|
|
373
481
|
if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
|
|
374
482
|
let candidate = null;
|
|
375
|
-
switch (
|
|
483
|
+
switch (TARGET_FLAVOR) {
|
|
376
484
|
case 'vue': {
|
|
377
485
|
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
378
486
|
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
@@ -416,7 +524,7 @@ function applyFullGraph(payload) {
|
|
|
416
524
|
(async () => {
|
|
417
525
|
try {
|
|
418
526
|
if (VERBOSE)
|
|
419
|
-
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=',
|
|
527
|
+
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
|
|
420
528
|
// Android-only: avoid racing entry-runtime reset and Activity bring-up
|
|
421
529
|
try {
|
|
422
530
|
const g = globalThis;
|
|
@@ -450,7 +558,7 @@ function applyFullGraph(payload) {
|
|
|
450
558
|
}
|
|
451
559
|
catch { }
|
|
452
560
|
let comp = null;
|
|
453
|
-
switch (
|
|
561
|
+
switch (TARGET_FLAVOR) {
|
|
454
562
|
case 'vue':
|
|
455
563
|
comp = await loadSfcComponent(candidate, 'initial_mount');
|
|
456
564
|
break;
|
|
@@ -499,7 +607,7 @@ function applyFullGraph(payload) {
|
|
|
499
607
|
})();
|
|
500
608
|
}
|
|
501
609
|
else if (VERBOSE) {
|
|
502
|
-
console.warn('[hmr][init] no component found in graph to mount initially for flavor',
|
|
610
|
+
console.warn('[hmr][init] no component found in graph to mount initially for flavor', TARGET_FLAVOR);
|
|
503
611
|
}
|
|
504
612
|
}
|
|
505
613
|
}
|
|
@@ -525,7 +633,7 @@ function applyDelta(payload) {
|
|
|
525
633
|
setGraphVersion(payload.newVersion);
|
|
526
634
|
}
|
|
527
635
|
const changed = payload.changed || [];
|
|
528
|
-
switch (
|
|
636
|
+
switch (TARGET_FLAVOR) {
|
|
529
637
|
case 'vue':
|
|
530
638
|
recordVuePayloadChanges(changed, getGraphVersion());
|
|
531
639
|
break;
|
|
@@ -615,7 +723,7 @@ function applyDelta(payload) {
|
|
|
615
723
|
// Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
|
|
616
724
|
function __nsNavigateUsingApp(comp, opts = {}) {
|
|
617
725
|
const g = globalThis;
|
|
618
|
-
switch (
|
|
726
|
+
switch (TARGET_FLAVOR) {
|
|
619
727
|
case 'vue':
|
|
620
728
|
ensureVueGlobals();
|
|
621
729
|
break;
|
|
@@ -642,8 +750,14 @@ function __nsNavigateUsingApp(comp, opts = {}) {
|
|
|
642
750
|
const buildTarget = () => {
|
|
643
751
|
const existingApp = getCurrentApp();
|
|
644
752
|
const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
|
|
645
|
-
|
|
646
|
-
|
|
753
|
+
// Forward `opts.props` as Vue's rootProps so `$navigateTo(Comp, { props: { … } })`
|
|
754
|
+
// reaches the destination component. nativescript-vue's stock `$navigateTo`
|
|
755
|
+
// does the same via `createNativeView(target, options?.props, …)` →
|
|
756
|
+
// `renderer.createApp(component, props)`. Dropping props here would surface
|
|
757
|
+
// at the destination as `[Vue warn]: Missing required prop` and any
|
|
758
|
+
// required-prop component would render with `undefined` bindings.
|
|
759
|
+
const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)), opts && opts.props);
|
|
760
|
+
switch (TARGET_FLAVOR) {
|
|
647
761
|
case 'vue':
|
|
648
762
|
ensurePiniaOnApp(app);
|
|
649
763
|
break;
|
|
@@ -777,7 +891,42 @@ async function processQueue() {
|
|
|
777
891
|
return;
|
|
778
892
|
if (VERBOSE)
|
|
779
893
|
console.log('[hmr][queue] processing changed ids', drained);
|
|
894
|
+
// Track wall-clock so the 'complete' frame can show a meaningful
|
|
895
|
+
// total. Only the Solid + TypeScript flavors drive the overlay
|
|
896
|
+
// from here; Angular has its own flow inside
|
|
897
|
+
// `frameworks/angular/client/index.ts`.
|
|
898
|
+
const tQueueStart = Date.now();
|
|
899
|
+
const driveSolidOverlay = TARGET_FLAVOR === 'solid';
|
|
900
|
+
// Explicit eviction step.
|
|
901
|
+
//
|
|
902
|
+
// On modern runtimes the URL canonicalizer collapses any
|
|
903
|
+
// `__ns_hmr__/<tag>/` segment back to a stable cache key, so
|
|
904
|
+
// without explicit eviction the upcoming `import(url)` would
|
|
905
|
+
// resolve via V8's `g_moduleRegistry` and return the cached
|
|
906
|
+
// stale module — making the queue drain a silent no-op for
|
|
907
|
+
// every save after the first.
|
|
908
|
+
//
|
|
909
|
+
// We hand the canonical eviction URLs to the runtime first;
|
|
910
|
+
// `invalidateModulesByUrls` is a no-op on older runtimes and
|
|
911
|
+
// `requestModuleFromServer` automatically falls back to the
|
|
912
|
+
// legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
|
|
913
|
+
// case. node_modules and virtual specs are filtered out by
|
|
914
|
+
// `buildEvictionUrls` so vendor modules stay hot.
|
|
915
|
+
if (driveSolidOverlay) {
|
|
916
|
+
setUpdateOverlayStage('evicting', {
|
|
917
|
+
detail: drained.length === 1 ? `Invalidating ${drained[0]}` : `Invalidating ${drained.length} modules`,
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
const evictUrls = buildEvictionUrls(drained);
|
|
921
|
+
const evicted = invalidateModulesByUrls(evictUrls);
|
|
922
|
+
if (VERBOSE)
|
|
923
|
+
console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
|
|
780
924
|
// Evaluate changed modules best-effort; failures shouldn't completely break HMR.
|
|
925
|
+
if (driveSolidOverlay) {
|
|
926
|
+
setUpdateOverlayStage('reimporting', {
|
|
927
|
+
detail: drained.length === 1 ? `Re-importing ${drained[0]}` : `Re-importing ${drained.length} modules`,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
781
930
|
for (const id of drained) {
|
|
782
931
|
try {
|
|
783
932
|
const spec = normalizeSpec(id);
|
|
@@ -794,11 +943,36 @@ async function processQueue() {
|
|
|
794
943
|
}
|
|
795
944
|
}
|
|
796
945
|
// After evaluating the batch, perform flavor-specific UI refresh.
|
|
797
|
-
switch (
|
|
946
|
+
switch (TARGET_FLAVOR) {
|
|
798
947
|
case 'vue':
|
|
799
|
-
// Vue SFCs are handled via the registry update path
|
|
948
|
+
// Vue SFCs are handled via the registry update path
|
|
949
|
+
// (which drives its own overlay completion through
|
|
950
|
+
// `driveVueSfcUpdateOverlay`); nothing else to do
|
|
951
|
+
// for the view-tree refresh here.
|
|
952
|
+
//
|
|
953
|
+
// However, when a non-SFC file (e.g. a `.ts`
|
|
954
|
+
// utility module imported by an SFC) is the only
|
|
955
|
+
// changed entry, no `ns:vue-sfc-registry-update`
|
|
956
|
+
// will follow — and without an explicit 'complete'
|
|
957
|
+
// the overlay would stick on "Preparing update
|
|
958
|
+
// (5%)". Drive the closing frame here so the
|
|
959
|
+
// auto-hide timer can dismiss the toast in the
|
|
960
|
+
// pure-TS-change case. The detail surfaces the
|
|
961
|
+
// changed count so a user can correlate the
|
|
962
|
+
// overlay with the server-side `[hmr-ws][update]`
|
|
963
|
+
// log.
|
|
964
|
+
setUpdateOverlayStage('complete', {
|
|
965
|
+
detail: drained.length === 1 ? `Updated ${drained[0]} in ${Math.max(0, Date.now() - tQueueStart)}ms` : `Updated ${drained.length} modules in ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
966
|
+
});
|
|
800
967
|
break;
|
|
801
968
|
case 'solid': {
|
|
969
|
+
// Boundaries discovered in this HMR cycle (tsx files reachable
|
|
970
|
+
// via the reverse import graph from any changed file, plus route
|
|
971
|
+
// files reachable from any tsx start point). Declared at the top
|
|
972
|
+
// of the case block so the emit step below can include the
|
|
973
|
+
// complete set in the listener event — framework integrations
|
|
974
|
+
// use it to map route boundaries → fresh component references.
|
|
975
|
+
const boundaries = new Set();
|
|
802
976
|
// Solid .tsx components are self-accepting via solid-refresh's inline
|
|
803
977
|
// patchRegistry — re-importing them is sufficient. For non-component
|
|
804
978
|
// .ts utility modules, we must propagate up the import graph to find
|
|
@@ -817,8 +991,10 @@ async function processQueue() {
|
|
|
817
991
|
arr.push(id);
|
|
818
992
|
}
|
|
819
993
|
}
|
|
820
|
-
// BFS from each non-tsx changed module up to tsx/jsx
|
|
821
|
-
|
|
994
|
+
// Pass 1: BFS from each non-tsx changed module up to tsx/jsx
|
|
995
|
+
// boundaries. These get re-imported below so solid-refresh's
|
|
996
|
+
// inline patchRegistry runs and (best-effort) swaps the proxy
|
|
997
|
+
// signals for any components defined in those tsx boundaries.
|
|
822
998
|
for (const id of drained) {
|
|
823
999
|
if (/\.(tsx|jsx)$/i.test(id))
|
|
824
1000
|
continue; // already self-accepting
|
|
@@ -842,6 +1018,51 @@ async function processQueue() {
|
|
|
842
1018
|
}
|
|
843
1019
|
}
|
|
844
1020
|
}
|
|
1021
|
+
// Pass 2: walk further from any tsx starting point (a tsx file
|
|
1022
|
+
// in `drained` OR a tsx boundary discovered in pass 1) to find
|
|
1023
|
+
// route files (`/src/routes/*.{tsx,jsx}`) that transitively
|
|
1024
|
+
// import them. Re-importing a route file refreshes its
|
|
1025
|
+
// `Route.options.component` to the freshly-imported reference
|
|
1026
|
+
// and the existing boundary loop below patches the live router
|
|
1027
|
+
// with that fresh reference.
|
|
1028
|
+
//
|
|
1029
|
+
// This is the key fix for "edit home.tsx → save → no visual
|
|
1030
|
+
// update": the old BFS skipped tsx files in `drained` (assuming
|
|
1031
|
+
// solid-refresh's in-place proxy patch was sufficient), but in
|
|
1032
|
+
// the universal-renderer + nested-context configuration that
|
|
1033
|
+
// patch does not always propagate to the visible page tree.
|
|
1034
|
+
// Adding the route file as a boundary lets us patch
|
|
1035
|
+
// `route.options.component` directly to a fresh module export,
|
|
1036
|
+
// which the framework subscriber then passes through to the
|
|
1037
|
+
// page remount — making the cycle robust to the proxy patch
|
|
1038
|
+
// silently failing.
|
|
1039
|
+
const tsxStarts = new Set();
|
|
1040
|
+
for (const id of drained) {
|
|
1041
|
+
if (/\.(tsx|jsx)$/i.test(id))
|
|
1042
|
+
tsxStarts.add(id);
|
|
1043
|
+
}
|
|
1044
|
+
for (const b of boundaries)
|
|
1045
|
+
tsxStarts.add(b);
|
|
1046
|
+
const ROUTE_FILE_RE = /\/src\/routes\/.+\.(tsx|jsx)$/i;
|
|
1047
|
+
for (const start of tsxStarts) {
|
|
1048
|
+
const visited = new Set();
|
|
1049
|
+
const queue = [start];
|
|
1050
|
+
while (queue.length) {
|
|
1051
|
+
const cur = queue.shift();
|
|
1052
|
+
if (visited.has(cur))
|
|
1053
|
+
continue;
|
|
1054
|
+
visited.add(cur);
|
|
1055
|
+
if (cur !== start && ROUTE_FILE_RE.test(cur)) {
|
|
1056
|
+
boundaries.add(cur);
|
|
1057
|
+
}
|
|
1058
|
+
const importers = reverseIndex.get(cur);
|
|
1059
|
+
if (!importers)
|
|
1060
|
+
continue;
|
|
1061
|
+
for (const imp of importers) {
|
|
1062
|
+
queue.push(imp);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
845
1066
|
// Re-import each boundary so solid-refresh patchRegistry fires.
|
|
846
1067
|
// For route files (TanStack Router), capture the new Route export
|
|
847
1068
|
// and patch the router's existing route with the fresh loader.
|
|
@@ -891,6 +1112,15 @@ async function processQueue() {
|
|
|
891
1112
|
}
|
|
892
1113
|
return null;
|
|
893
1114
|
};
|
|
1115
|
+
// Evict the boundary set so re-importing each .tsx
|
|
1116
|
+
// component actually picks up the new transitive
|
|
1117
|
+
// dependency code; without this V8 returns the
|
|
1118
|
+
// cached boundary module unchanged.
|
|
1119
|
+
const boundaryIds = Array.from(boundaries);
|
|
1120
|
+
const solidEvictUrls = buildEvictionUrls(boundaryIds);
|
|
1121
|
+
const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
|
|
1122
|
+
if (VERBOSE)
|
|
1123
|
+
console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
|
|
894
1124
|
for (const id of boundaries) {
|
|
895
1125
|
if (seen.has(id))
|
|
896
1126
|
continue;
|
|
@@ -902,22 +1132,28 @@ async function processQueue() {
|
|
|
902
1132
|
if (VERBOSE)
|
|
903
1133
|
console.log('[hmr][solid] propagated to boundary', { id, url });
|
|
904
1134
|
const mod = await import(/* @vite-ignore */ url);
|
|
905
|
-
// Patch TanStack Router route
|
|
1135
|
+
// Patch TanStack Router route options for any module
|
|
1136
|
+
// that exports a `Route`. We patch BOTH the component
|
|
1137
|
+
// and the loader (when present); components-only routes
|
|
1138
|
+
// were previously skipped because the gate required a
|
|
1139
|
+
// loader, which left their `options.component` pointing
|
|
1140
|
+
// at the stale module's exports after HMR.
|
|
906
1141
|
try {
|
|
907
1142
|
const newRoute = mod?.Route;
|
|
908
|
-
if (newRoute?.options
|
|
1143
|
+
if (newRoute?.options) {
|
|
909
1144
|
const router = findRouter();
|
|
910
1145
|
const fullPath = boundaryToFullPath(id);
|
|
911
1146
|
if (VERBOSE)
|
|
912
|
-
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router,
|
|
1147
|
+
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, hasLoader: !!newRoute.options.loader, hasComponent: !!newRoute.options.component });
|
|
913
1148
|
const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
|
|
914
1149
|
if (existingRoute?.options) {
|
|
915
|
-
|
|
1150
|
+
if (newRoute.options.loader)
|
|
1151
|
+
existingRoute.options.loader = newRoute.options.loader;
|
|
916
1152
|
if (newRoute.options.component)
|
|
917
1153
|
existingRoute.options.component = newRoute.options.component;
|
|
918
1154
|
routesPatchCount++;
|
|
919
1155
|
if (VERBOSE)
|
|
920
|
-
console.log('[hmr][solid] patched route
|
|
1156
|
+
console.log('[hmr][solid] patched route', existingRoute.id, 'fullPath=', fullPath);
|
|
921
1157
|
}
|
|
922
1158
|
else if (VERBOSE) {
|
|
923
1159
|
console.log('[hmr][solid] no matching route for fullPath', fullPath);
|
|
@@ -953,6 +1189,44 @@ async function processQueue() {
|
|
|
953
1189
|
if (VERBOSE)
|
|
954
1190
|
console.warn('[hmr][solid] propagation failed', e);
|
|
955
1191
|
}
|
|
1192
|
+
// Notify any framework integrations (e.g.
|
|
1193
|
+
// `@nativescript/tanstack-router`) that a Solid HMR
|
|
1194
|
+
// cycle has completed. They use this signal to perform
|
|
1195
|
+
// framework-specific UI refresh (e.g. remount the active
|
|
1196
|
+
// router page) when solid-refresh's own reactive
|
|
1197
|
+
// propagation does not reach the visible tree under
|
|
1198
|
+
// the current renderer/context configuration.
|
|
1199
|
+
//
|
|
1200
|
+
// Boundaries include both the directly-changed tsx files
|
|
1201
|
+
// AND every tsx ancestor reachable via the reverse import
|
|
1202
|
+
// graph (route files in particular). The framework
|
|
1203
|
+
// listener uses the route-file boundaries to look up the
|
|
1204
|
+
// freshly-patched `route.options.component` and pass it
|
|
1205
|
+
// through to the page remount.
|
|
1206
|
+
try {
|
|
1207
|
+
const tsxChangedInDrained = drained.filter((id) => /\.(tsx|jsx)$/i.test(id));
|
|
1208
|
+
const allBoundaries = Array.from(new Set([...tsxChangedInDrained, ...boundaries]));
|
|
1209
|
+
nsSolidHmrEmit({
|
|
1210
|
+
kind: 'solid',
|
|
1211
|
+
changedFiles: drained.slice(),
|
|
1212
|
+
boundaries: allBoundaries,
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
catch (err) {
|
|
1216
|
+
if (VERBOSE)
|
|
1217
|
+
console.warn('[hmr][solid] emit failed', err);
|
|
1218
|
+
}
|
|
1219
|
+
// Tell the overlay the cycle is done. solid-refresh's
|
|
1220
|
+
// inline patchRegistry has already flushed the new
|
|
1221
|
+
// component bodies into the live tree (the `case
|
|
1222
|
+
// 'solid'` block above re-imports each .tsx
|
|
1223
|
+
// boundary), so by the time we get here the user is
|
|
1224
|
+
// already looking at the new render. The 'complete'
|
|
1225
|
+
// frame surfaces the wall-clock total and triggers
|
|
1226
|
+
// the overlay's auto-hide.
|
|
1227
|
+
setUpdateOverlayStage('complete', {
|
|
1228
|
+
detail: `Total ${Math.max(0, Date.now() - tQueueStart)}ms`,
|
|
1229
|
+
});
|
|
956
1230
|
break;
|
|
957
1231
|
}
|
|
958
1232
|
case 'typescript': {
|
|
@@ -1132,6 +1406,10 @@ function connectHmr() {
|
|
|
1132
1406
|
console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
|
|
1133
1407
|
return;
|
|
1134
1408
|
}
|
|
1409
|
+
try {
|
|
1410
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1411
|
+
}
|
|
1412
|
+
catch { }
|
|
1135
1413
|
const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
|
|
1136
1414
|
const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
|
|
1137
1415
|
const buildCandidates = (url) => {
|
|
@@ -1176,7 +1454,7 @@ function connectHmr() {
|
|
|
1176
1454
|
if (seen.has(key))
|
|
1177
1455
|
continue;
|
|
1178
1456
|
seen.add(key);
|
|
1179
|
-
const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}`;
|
|
1457
|
+
const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}${u.search || ''}`;
|
|
1180
1458
|
candidates.push(cand);
|
|
1181
1459
|
}
|
|
1182
1460
|
}
|
|
@@ -1229,10 +1507,22 @@ function connectHmr() {
|
|
|
1229
1507
|
clearConnectionOverlayTimer();
|
|
1230
1508
|
hasOpenedHmrSocket = true;
|
|
1231
1509
|
awaitingHealthyHmrMessage = true;
|
|
1510
|
+
try {
|
|
1511
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = true;
|
|
1512
|
+
}
|
|
1513
|
+
catch { }
|
|
1232
1514
|
if (connectionOverlayVisible) {
|
|
1233
1515
|
showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
|
|
1234
1516
|
}
|
|
1235
1517
|
VERBOSE && console.log('[hmr-client] Connected to HMR WebSocket');
|
|
1518
|
+
// Print the active module reload mode once on first
|
|
1519
|
+
// successful connect so the user can correlate HMR latency
|
|
1520
|
+
// with runtime capability without grepping for protocol
|
|
1521
|
+
// details. The banner is verbose-gated.
|
|
1522
|
+
try {
|
|
1523
|
+
emitHmrModeBannerOnce();
|
|
1524
|
+
}
|
|
1525
|
+
catch { }
|
|
1236
1526
|
};
|
|
1237
1527
|
sock.onmessage = handleHmrMessage;
|
|
1238
1528
|
sock.onerror = (error) => {
|
|
@@ -1241,6 +1531,10 @@ function connectHmr() {
|
|
|
1241
1531
|
};
|
|
1242
1532
|
sock.onclose = (ev) => {
|
|
1243
1533
|
clearTimeout(timeout);
|
|
1534
|
+
try {
|
|
1535
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1536
|
+
}
|
|
1537
|
+
catch { }
|
|
1244
1538
|
if (!opened) {
|
|
1245
1539
|
// immediate failure during connect → try another candidate
|
|
1246
1540
|
if (VERBOSE)
|
|
@@ -1274,6 +1568,9 @@ async function handleHmrMessage(ev) {
|
|
|
1274
1568
|
if (awaitingHealthyHmrMessage && msg) {
|
|
1275
1569
|
markHmrConnectionHealthy();
|
|
1276
1570
|
}
|
|
1571
|
+
if (VERBOSE && msg?.type) {
|
|
1572
|
+
console.log('[hmr-client] received message', msg.type);
|
|
1573
|
+
}
|
|
1277
1574
|
// Notify optional app-level hook after an HMR batch is applied.
|
|
1278
1575
|
function notifyAppHmrUpdate(kind, changedIds) {
|
|
1279
1576
|
try {
|
|
@@ -1285,6 +1582,17 @@ async function handleHmrMessage(ev) {
|
|
|
1285
1582
|
catch { }
|
|
1286
1583
|
}
|
|
1287
1584
|
if (msg) {
|
|
1585
|
+
// `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
|
|
1586
|
+
// server at the START of handleHotUpdate. We drive the
|
|
1587
|
+
// HMR-applying overlay's 'received' frame here (synchronously),
|
|
1588
|
+
// well before the authoritative payload (`ns:angular-update` /
|
|
1589
|
+
// `ns:css-updates`) lands. Skip running any other handlers —
|
|
1590
|
+
// the pending message has no module payload and intentionally
|
|
1591
|
+
// does not bump the graph version.
|
|
1592
|
+
if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
|
|
1593
|
+
setHmrPendingOverlay(msg.path);
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1288
1596
|
if (msg.type === 'ns:hmr-full-graph') {
|
|
1289
1597
|
// Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
|
|
1290
1598
|
try {
|
|
@@ -1354,6 +1662,14 @@ async function handleHmrMessage(ev) {
|
|
|
1354
1662
|
});
|
|
1355
1663
|
if (toReimport.length && VERBOSE)
|
|
1356
1664
|
console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
|
|
1665
|
+
// Evict the inferred changed set before re-importing.
|
|
1666
|
+
// See `processQueue` for the architectural rationale; the
|
|
1667
|
+
// full-graph code path is the resync fallback (server chose
|
|
1668
|
+
// not to send a delta) and shares the same V8 cache pitfall.
|
|
1669
|
+
const fgEvictUrls = buildEvictionUrls(toReimport);
|
|
1670
|
+
const fgEvicted = invalidateModulesByUrls(fgEvictUrls);
|
|
1671
|
+
if (VERBOSE)
|
|
1672
|
+
console.log(`[hmr][full-graph] eviction count=${fgEvictUrls.length} ok=${fgEvicted}`);
|
|
1357
1673
|
for (const id of toReimport) {
|
|
1358
1674
|
try {
|
|
1359
1675
|
const spec = normalizeSpec(id);
|
|
@@ -1426,8 +1742,111 @@ async function handleHmrMessage(ev) {
|
|
|
1426
1742
|
notifyAppHmrUpdate('delta', deltaIds);
|
|
1427
1743
|
return;
|
|
1428
1744
|
}
|
|
1429
|
-
else
|
|
1430
|
-
|
|
1745
|
+
else {
|
|
1746
|
+
// Vite custom-event dispatch.
|
|
1747
|
+
//
|
|
1748
|
+
// `server.ws.send('event-name', payload)` from any Vite plugin lands
|
|
1749
|
+
// on the wire as `{ type: 'custom', event: 'event-name', data: payload }`.
|
|
1750
|
+
// On the web, Vite's stock client owns a `customListenersMap` that
|
|
1751
|
+
// fires every `import.meta.hot.on('event-name', cb)` callback. We
|
|
1752
|
+
// don't run Vite's stock client on device — the iOS runtime owns
|
|
1753
|
+
// the listener registry via `__NS_DISPATCH_HOT_EVENT__` (the
|
|
1754
|
+
// counterpart to `import.meta.hot.on` populated by user code +
|
|
1755
|
+
// compiled Angular components). Forwarding `type: 'custom'` here
|
|
1756
|
+
// is the only thing standing between server-emitted events and
|
|
1757
|
+
// the listeners they were meant for.
|
|
1758
|
+
//
|
|
1759
|
+
// `angular:component-update` is the canonical example. Analog's
|
|
1760
|
+
// plugin sends it on `.html` / component-style edits; the
|
|
1761
|
+
// compiled component `.mjs` registered a listener that
|
|
1762
|
+
// dynamic-imports `/@ng/component?c=<id>&t=<ts>` and calls
|
|
1763
|
+
// `ɵɵreplaceMetadata` on the live class — swapping the template
|
|
1764
|
+
// definition AND walking live `LView`s to recreate matching views
|
|
1765
|
+
// in-place. The page stays mounted and only the changed bits
|
|
1766
|
+
// re-render. We MUST `return` after dispatch so the reboot path
|
|
1767
|
+
// (`handleAngularHotUpdateMessage` → `__reboot_ng_modules__`)
|
|
1768
|
+
// never runs for these updates — that's the whole point of the
|
|
1769
|
+
// component-replacement pipeline.
|
|
1770
|
+
//
|
|
1771
|
+
// All other custom events are forwarded but NOT short-circuited
|
|
1772
|
+
// (Vite spec: custom events are additive — they don't replace
|
|
1773
|
+
// any framework-specific handling). The reboot path falls through
|
|
1774
|
+
// for `ns:angular-update` (the legacy/`.ts`-edit broadcast) and
|
|
1775
|
+
// for any framework not yet using the in-place replacement path.
|
|
1776
|
+
if (msg.type === 'custom' && typeof msg.event === 'string') {
|
|
1777
|
+
// Dispatch every Vite "custom" event through the runtime's
|
|
1778
|
+
// `__NS_DISPATCH_HOT_EVENT__` bridge so `import.meta.hot.on(event, cb)`
|
|
1779
|
+
// callbacks fire on the device. Critical contract: this is the
|
|
1780
|
+
// ONLY route by which Analog's `angular:component-update` reaches
|
|
1781
|
+
// the compiled component's `(d) => d.id === id && Component_HmrLoad(...)`
|
|
1782
|
+
// listener — without it, server-side broadcasts log green
|
|
1783
|
+
// (`(client) hmr update`) while the device sees nothing happen.
|
|
1784
|
+
//
|
|
1785
|
+
// Diagnostic policy: log "no dispatcher" loud (boot-time rt-bridge
|
|
1786
|
+
// failure), and listener exceptions loud (compiled HmrLoad
|
|
1787
|
+
// fetch/parse error). Successful dispatches are silent — the
|
|
1788
|
+
// runtime's `[import.meta.hot] dispatch summary` line carries
|
|
1789
|
+
// the per-event match-count diagnostic.
|
|
1790
|
+
try {
|
|
1791
|
+
const dispatch = globalThis.__NS_DISPATCH_HOT_EVENT__;
|
|
1792
|
+
if (typeof dispatch === 'function') {
|
|
1793
|
+
dispatch(msg.event, msg.data);
|
|
1794
|
+
}
|
|
1795
|
+
else {
|
|
1796
|
+
console.warn(`[hmr-client][custom] no __NS_DISPATCH_HOT_EVENT__ available for '${msg.event}'`);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
catch (err) {
|
|
1800
|
+
console.warn('[hmr-client][custom] dispatch threw for', msg.event, err);
|
|
1801
|
+
}
|
|
1802
|
+
if (msg.event === 'angular:component-update') {
|
|
1803
|
+
if (VERBOSE)
|
|
1804
|
+
console.log('[hmr-client][custom] dispatched angular:component-update — skipping reboot path');
|
|
1805
|
+
// Walk the apply-progress overlay through its
|
|
1806
|
+
// remaining stages for the in-place template-swap
|
|
1807
|
+
// path. The full reboot path
|
|
1808
|
+
// (`handleAngularHotUpdateMessage`) drives the
|
|
1809
|
+
// overlay itself ('received' → 'evicting' →
|
|
1810
|
+
// 'reimporting' → 'rebooting' → 'complete'); the
|
|
1811
|
+
// in-place path bypasses that handler entirely
|
|
1812
|
+
// because the work happens inside Angular's
|
|
1813
|
+
// `ɵɵreplaceMetadata` after the runtime forwards the
|
|
1814
|
+
// `angular:component-update` event to the compiled
|
|
1815
|
+
// component's listener. Without this update the
|
|
1816
|
+
// overlay would freeze at 5% ('received') even
|
|
1817
|
+
// though the visual swap completes a few frames
|
|
1818
|
+
// later — exactly the "Preparing update (5%)" stuck
|
|
1819
|
+
// frame we have been chasing.
|
|
1820
|
+
//
|
|
1821
|
+
// We transition straight to 'reimporting' to
|
|
1822
|
+
// communicate that metadata is being fetched (the
|
|
1823
|
+
// runtime listener fires `__ns_import('/@ng/component?c=...&t=...')`),
|
|
1824
|
+
// then schedule 'complete' on the next macrotask so
|
|
1825
|
+
// the auto-hide timer kicks in. The actual
|
|
1826
|
+
// template swap is fire-and-forget from this point;
|
|
1827
|
+
// the user sees the overlay close at the same time
|
|
1828
|
+
// as Angular re-renders the bound text/structure.
|
|
1829
|
+
try {
|
|
1830
|
+
const filePath = typeof msg.data?.id === 'string' ? decodeURIComponent(msg.data.id).split('@')[0] : undefined;
|
|
1831
|
+
const detail = filePath ? `Applying template update to ${filePath}` : 'Applying template update';
|
|
1832
|
+
setUpdateOverlayStage('reimporting', { detail });
|
|
1833
|
+
setTimeout(() => {
|
|
1834
|
+
try {
|
|
1835
|
+
setUpdateOverlayStage('complete', { detail: filePath ? `Updated ${filePath}` : 'Update applied' });
|
|
1836
|
+
}
|
|
1837
|
+
catch { }
|
|
1838
|
+
}, 16);
|
|
1839
|
+
}
|
|
1840
|
+
catch { }
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
|
|
1845
|
+
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
1846
|
+
}
|
|
1847
|
+
if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1431
1850
|
}
|
|
1432
1851
|
}
|
|
1433
1852
|
// On-demand module fetch response (Option A)
|
|
@@ -1459,13 +1878,32 @@ async function handleHmrMessage(ev) {
|
|
|
1459
1878
|
return;
|
|
1460
1879
|
}
|
|
1461
1880
|
if (msg.type === 'ns:css-updates' && Array.isArray(msg.updates)) {
|
|
1881
|
+
// Drive the HMR-applying overlay past the 'received' (5%) frame
|
|
1882
|
+
// that `ns:hmr-pending` set earlier in the cycle. Without this
|
|
1883
|
+
// the overlay sticks at "Preparing update" forever for CSS-only
|
|
1884
|
+
// edits because `handleCssUpdates` is a leaf — there's no
|
|
1885
|
+
// downstream module-evaluation path that would hit the queue's
|
|
1886
|
+
// 'complete' transition.
|
|
1887
|
+
const cssCount = msg.updates.length;
|
|
1888
|
+
try {
|
|
1889
|
+
setUpdateOverlayStage('reimporting', { detail: buildCssApplyingDetail(cssCount) });
|
|
1890
|
+
}
|
|
1891
|
+
catch { }
|
|
1462
1892
|
try {
|
|
1463
1893
|
const origin = msg.origin || getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
|
|
1464
1894
|
await handleCssUpdates(msg.updates, origin);
|
|
1895
|
+
try {
|
|
1896
|
+
setUpdateOverlayStage('complete', { detail: buildCssAppliedDetail(cssCount) });
|
|
1897
|
+
}
|
|
1898
|
+
catch { }
|
|
1465
1899
|
return;
|
|
1466
1900
|
}
|
|
1467
1901
|
catch (e) {
|
|
1468
1902
|
console.warn('[hmr-client] CSS updates handling failed:', e);
|
|
1903
|
+
try {
|
|
1904
|
+
setUpdateOverlayStage('complete', { detail: 'CSS update failed' });
|
|
1905
|
+
}
|
|
1906
|
+
catch { }
|
|
1469
1907
|
return;
|
|
1470
1908
|
}
|
|
1471
1909
|
}
|
|
@@ -1476,10 +1914,22 @@ async function handleHmrMessage(ev) {
|
|
|
1476
1914
|
if (msg.type === 'ns:vue-sfc-registry-update') {
|
|
1477
1915
|
if (typeof msg.version === 'number')
|
|
1478
1916
|
setGraphVersion(msg.version);
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1917
|
+
// `ns:hmr-pending` already set the overlay to 'received' (5%).
|
|
1918
|
+
// Without the explicit stage walk below the overlay would stick
|
|
1919
|
+
// at "Preparing update" forever after a successful SFC swap —
|
|
1920
|
+
// the Vue path was missing the framework-specific completion
|
|
1921
|
+
// hooks that Angular drives via `handleAngularHotUpdateMessage`
|
|
1922
|
+
// and CSS drives inline above. `driveVueSfcUpdateOverlay` is a
|
|
1923
|
+
// thin orchestrator that walks 'evicting' → 'reimporting' →
|
|
1924
|
+
// 'rebooting' → 'complete' around the load + reset steps and
|
|
1925
|
+
// always lands on 'complete' (or a failure detail) so the
|
|
1926
|
+
// auto-hide timer can dismiss the toast.
|
|
1927
|
+
const sfcFilePath = typeof msg.path === 'string' ? msg.path : undefined;
|
|
1928
|
+
await driveVueSfcUpdateOverlay({
|
|
1929
|
+
filePath: sfcFilePath,
|
|
1930
|
+
loadComponent: () => handleVueSfcRegistryUpdate(msg, getGraphVersion()),
|
|
1931
|
+
applyComponent: (component) => performResetRoot(component),
|
|
1932
|
+
}, { getOverlay: getHmrOverlayApi });
|
|
1483
1933
|
return;
|
|
1484
1934
|
}
|
|
1485
1935
|
}
|
|
@@ -1568,7 +2018,7 @@ async function performResetRoot(newComponent) {
|
|
|
1568
2018
|
if (cachedRoot)
|
|
1569
2019
|
return cachedRoot;
|
|
1570
2020
|
try {
|
|
1571
|
-
switch (
|
|
2021
|
+
switch (TARGET_FLAVOR) {
|
|
1572
2022
|
case 'vue':
|
|
1573
2023
|
cachedRoot = getRootForVue(newComponent, state);
|
|
1574
2024
|
break;
|
|
@@ -1905,7 +2355,7 @@ export function initHmrClient(opts) {
|
|
|
1905
2355
|
setTimeout(waitForBoot, 100);
|
|
1906
2356
|
}
|
|
1907
2357
|
// Best-effort: install back wrapper even before first remount; original root may be captured later
|
|
1908
|
-
switch (
|
|
2358
|
+
switch (TARGET_FLAVOR) {
|
|
1909
2359
|
case 'vue':
|
|
1910
2360
|
ensureBackWrapperInstalled(performResetRoot, getCore);
|
|
1911
2361
|
break;
|