@nativescript/vite 8.0.0-alpha.0 → 8.0.0-alpha.10
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 +486 -140
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +159 -41
- 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 +4 -4
- package/configuration/typescript.js.map +1 -1
- package/helpers/angular/angular-linker.js +38 -42
- 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/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/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/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/global-defines.d.ts +51 -0
- package/helpers/global-defines.js +77 -0
- package/helpers/global-defines.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/logging.d.ts +1 -0
- package/helpers/logging.js +63 -3
- package/helpers/logging.js.map +1 -1
- package/helpers/main-entry.d.ts +5 -2
- package/helpers/main-entry.js +365 -116
- 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 +19 -1
- package/helpers/nativeclass-transformer-plugin.js +318 -31
- package/helpers/nativeclass-transformer-plugin.js.map +1 -1
- package/helpers/ns-core-url.d.ts +83 -0
- package/helpers/ns-core-url.js +167 -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/preserve-imports.js +2 -17
- package/helpers/preserve-imports.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/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.js +60 -19
- package/hmr/client/css-handler.js.map +1 -1
- 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 +597 -24
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +5 -0
- package/hmr/client/utils.js +212 -21
- package/hmr/client/utils.js.map +1 -1
- package/hmr/entry-runtime.d.ts +10 -0
- package/hmr/entry-runtime.js +330 -42
- 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 +821 -25
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/server/linker.js +37 -6
- 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 +22 -10
- 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/server/constants.d.ts +1 -0
- package/hmr/server/constants.js +14 -3
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +49 -2
- package/hmr/server/core-sanitize.js +267 -24
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/import-map.d.ts +65 -0
- package/hmr/server/import-map.js +222 -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/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/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 +461 -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 +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 +21 -0
- package/hmr/server/websocket-core-bridge.js +357 -0
- package/hmr/server/websocket-core-bridge.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-runtime-compat.d.ts +19 -0
- package/hmr/server/websocket-runtime-compat.js +287 -0
- package/hmr/server/websocket-runtime-compat.js.map +1 -0
- package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
- package/hmr/server/websocket-served-module-helpers.js +631 -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 +27 -0
- package/hmr/server/websocket-vue-sfc.js +1069 -0
- package/hmr/server/websocket-vue-sfc.js.map +1 -0
- package/hmr/server/websocket.d.ts +26 -3
- package/hmr/server/websocket.js +2233 -796
- 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-timeline.d.ts +17 -0
- package/hmr/shared/runtime/boot-timeline.js +51 -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 +85 -0
- package/hmr/shared/runtime/dev-overlay.js +1236 -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 +63 -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 +552 -82
- 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 +195 -0
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
- package/hmr/shared/runtime/vendor-bootstrap.js +52 -15
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest.d.ts +37 -0
- package/hmr/shared/vendor/manifest.js +677 -57
- 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/index.d.ts +1 -0
- package/index.js +5 -0
- package/index.js.map +1 -1
- package/package.json +14 -2
- package/runtime/core-aliases-early.js +94 -67
- 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
|
@@ -5,9 +5,41 @@
|
|
|
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
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,95 @@ 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
|
+
// Eagerly drive the HMR-applying overlay's 'received' frame as soon
|
|
105
|
+
// as the server emits `ns:hmr-pending`, BEFORE the framework-specific
|
|
106
|
+
// (`ns:angular-update` / `ns:css-updates`) payload arrives. The
|
|
107
|
+
// flavor-specific handler later walks through 'evicting' →
|
|
108
|
+
// 'reimporting' → 'rebooting' → 'complete'. Calling 'received' twice
|
|
109
|
+
// in the same cycle is safe: the overlay preserves
|
|
110
|
+
// `updateCycleStartedAt` when a 'received' frame replaces an existing
|
|
111
|
+
// 'received' frame so the minimum-visible window is still timed
|
|
112
|
+
// against the FIRST frame.
|
|
113
|
+
//
|
|
114
|
+
// Soft-fails when the overlay isn't installed (production builds,
|
|
115
|
+
// vitest, etc.) or when the user opted out via
|
|
116
|
+
// `__NS_HMR_PROGRESS_OVERLAY_ENABLED__ === false`.
|
|
117
|
+
import { applyHmrPendingFrame } from './hmr-pending-overlay.js';
|
|
118
|
+
function setHmrPendingOverlay(filePath) {
|
|
119
|
+
applyHmrPendingFrame(filePath, { getOverlay: getHmrOverlayApi });
|
|
120
|
+
}
|
|
121
|
+
let connectionOverlayTimer = null;
|
|
122
|
+
let connectionOverlayVisible = false;
|
|
123
|
+
let hasOpenedHmrSocket = false;
|
|
124
|
+
let awaitingHealthyHmrMessage = false;
|
|
125
|
+
let pendingConnectionOverlayStage = 'connecting';
|
|
126
|
+
let pendingConnectionOverlayDetail = '';
|
|
127
|
+
function clearConnectionOverlayTimer() {
|
|
128
|
+
if (connectionOverlayTimer) {
|
|
129
|
+
clearTimeout(connectionOverlayTimer);
|
|
130
|
+
connectionOverlayTimer = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function showConnectionOverlayNow(stage, detail) {
|
|
134
|
+
pendingConnectionOverlayStage = stage;
|
|
135
|
+
pendingConnectionOverlayDetail = detail || '';
|
|
136
|
+
connectionOverlayVisible = true;
|
|
137
|
+
setConnectionOverlayStage(stage, detail);
|
|
138
|
+
}
|
|
139
|
+
function scheduleConnectionOverlay(stage, detail, delayMs = 1200) {
|
|
140
|
+
pendingConnectionOverlayStage = stage;
|
|
141
|
+
pendingConnectionOverlayDetail = detail || '';
|
|
142
|
+
clearConnectionOverlayTimer();
|
|
143
|
+
connectionOverlayTimer = setTimeout(() => {
|
|
144
|
+
showConnectionOverlayNow(pendingConnectionOverlayStage, pendingConnectionOverlayDetail);
|
|
145
|
+
}, delayMs);
|
|
146
|
+
}
|
|
147
|
+
function updateConnectionOverlay(stage, detail) {
|
|
148
|
+
pendingConnectionOverlayStage = stage;
|
|
149
|
+
pendingConnectionOverlayDetail = detail || '';
|
|
150
|
+
if (connectionOverlayVisible) {
|
|
151
|
+
showConnectionOverlayNow(stage, detail);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function markHmrConnectionHealthy() {
|
|
155
|
+
awaitingHealthyHmrMessage = false;
|
|
156
|
+
clearConnectionOverlayTimer();
|
|
157
|
+
if (connectionOverlayVisible) {
|
|
158
|
+
connectionOverlayVisible = false;
|
|
159
|
+
hideConnectionOverlay();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
46
162
|
/**
|
|
47
163
|
* Flavor hooks
|
|
48
164
|
*/
|
|
49
|
-
import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate } from '../frameworks/vue/client/index.js';
|
|
165
|
+
import { installNsVueDevShims, ensureBackWrapperInstalled, getRootForVue, loadSfcComponent, ensureVueGlobals, ensurePiniaOnApp, addSfcMapping, recordVuePayloadChanges, handleVueSfcRegistry, handleVueSfcRegistryUpdate, sfcArtifactMap } from '../frameworks/vue/client/index.js';
|
|
50
166
|
import { handleAngularHotUpdateMessage, installAngularHmrClientHooks } from '../frameworks/angular/client/index.js';
|
|
51
|
-
switch (
|
|
167
|
+
switch (TARGET_FLAVOR) {
|
|
52
168
|
case 'vue':
|
|
53
169
|
installNsVueDevShims();
|
|
54
170
|
break;
|
|
@@ -60,6 +176,10 @@ switch (__NS_TARGET_FLAVOR__) {
|
|
|
60
176
|
let initialMounted = !!globalThis.__NS_HMR_BOOT_COMPLETE__;
|
|
61
177
|
// Prevent duplicate initial-mount scheduling across rapid full-graph broadcasts and re-evaluations
|
|
62
178
|
let initialMounting = !!globalThis.__NS_HMR_INITIAL_MOUNT_IN_PROGRESS__;
|
|
179
|
+
// Track whether the first full-graph has been received. Before the full-graph,
|
|
180
|
+
// delta messages are just the server discovering modules during initial boot —
|
|
181
|
+
// NOT actual code changes. Re-imports must be gated behind this flag.
|
|
182
|
+
let hasReceivedFullGraph = false;
|
|
63
183
|
// TypeScript flavor: track registry modules and inferred main id
|
|
64
184
|
let tsModuleSet = null;
|
|
65
185
|
let tsMainId = null;
|
|
@@ -94,10 +214,14 @@ function applyFullGraph(payload) {
|
|
|
94
214
|
console.log('[hmr][graph] full graph applied version', getGraphVersion(), 'modules=', graph.size);
|
|
95
215
|
// Guarded initial mount rescue: if app hasn't replaced the placeholder shortly after graph arrives,
|
|
96
216
|
// perform a one-time mount. This waits briefly to let the app's main entry start() run first to avoid double mounts.
|
|
217
|
+
// TypeScript flavor: skip the rescue entirely. The HTTP boot will load main.ts which
|
|
218
|
+
// calls Application.run() (patched to resetRootView) — the rescue is redundant and
|
|
219
|
+
// causes a double-mount race (rescue fires at 450ms, then main.ts fires ~1s later,
|
|
220
|
+
// causing a visual flash and leaving the app in an inconsistent state).
|
|
97
221
|
try {
|
|
98
222
|
const g = globalThis;
|
|
99
223
|
const bootDone = !!g.__NS_HMR_BOOT_COMPLETE__;
|
|
100
|
-
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__) {
|
|
224
|
+
if (!bootDone && !initialMounted && !initialMounting && !g.__NS_HMR_RESCUE_SCHEDULED__ && TARGET_FLAVOR !== 'typescript') {
|
|
101
225
|
// simple snapshot helpers
|
|
102
226
|
const getTopmost = () => {
|
|
103
227
|
try {
|
|
@@ -170,7 +294,7 @@ function applyFullGraph(payload) {
|
|
|
170
294
|
if (VERBOSE)
|
|
171
295
|
console.log('[hmr][init] placeholder persists after delay; evaluating rescue policy');
|
|
172
296
|
// Flavor-specific rescue handling
|
|
173
|
-
if (
|
|
297
|
+
if (TARGET_FLAVOR === 'typescript') {
|
|
174
298
|
// For TS apps, perform a one-time resetRootView to the conventional
|
|
175
299
|
// app root module. This mimics what Application.run would do and
|
|
176
300
|
// replaces the placeholder with the real UI without trying to
|
|
@@ -211,7 +335,7 @@ function applyFullGraph(payload) {
|
|
|
211
335
|
return;
|
|
212
336
|
}
|
|
213
337
|
let candidate = null;
|
|
214
|
-
switch (
|
|
338
|
+
switch (TARGET_FLAVOR) {
|
|
215
339
|
case 'vue': {
|
|
216
340
|
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
217
341
|
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
@@ -227,6 +351,19 @@ function applyFullGraph(payload) {
|
|
|
227
351
|
}
|
|
228
352
|
}
|
|
229
353
|
}
|
|
354
|
+
// Fallback: when the module graph is empty (Vite 7+ may not populate it
|
|
355
|
+
// before the first full-graph broadcast), check the SFC artifact registry
|
|
356
|
+
// which is populated from the ns:vue-sfc-registry message.
|
|
357
|
+
if (!candidate && sfcArtifactMap.size > 0) {
|
|
358
|
+
for (const id of sfcArtifactMap.keys()) {
|
|
359
|
+
if (/\.vue$/i.test(id)) {
|
|
360
|
+
candidate = id;
|
|
361
|
+
if (VERBOSE)
|
|
362
|
+
console.log('[hmr][init] rescue candidate from SFC registry:', id);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
230
367
|
break;
|
|
231
368
|
}
|
|
232
369
|
}
|
|
@@ -240,7 +377,7 @@ function applyFullGraph(payload) {
|
|
|
240
377
|
(async () => {
|
|
241
378
|
try {
|
|
242
379
|
let comp = null;
|
|
243
|
-
switch (
|
|
380
|
+
switch (TARGET_FLAVOR) {
|
|
244
381
|
case 'vue':
|
|
245
382
|
comp = await loadSfcComponent(candidate, 'initial_mount_rescue');
|
|
246
383
|
break;
|
|
@@ -285,7 +422,7 @@ function applyFullGraph(payload) {
|
|
|
285
422
|
// to avoid double-mount races that can cause duplicate navigation logs.
|
|
286
423
|
if (ALLOW_INITIAL_MOUNT && !initialMounted && !bootDone && !bootInProgress && !getCurrentApp() && !getRootFrame()) {
|
|
287
424
|
let candidate = null;
|
|
288
|
-
switch (
|
|
425
|
+
switch (TARGET_FLAVOR) {
|
|
289
426
|
case 'vue': {
|
|
290
427
|
const appEntry = graph.get(APP_MAIN_ENTRY_SPEC);
|
|
291
428
|
if (appEntry && Array.isArray(appEntry.deps)) {
|
|
@@ -301,6 +438,17 @@ function applyFullGraph(payload) {
|
|
|
301
438
|
}
|
|
302
439
|
}
|
|
303
440
|
}
|
|
441
|
+
// Fallback: SFC registry (same as rescue mount above)
|
|
442
|
+
if (!candidate && sfcArtifactMap.size > 0) {
|
|
443
|
+
for (const id of sfcArtifactMap.keys()) {
|
|
444
|
+
if (/\.vue$/i.test(id)) {
|
|
445
|
+
candidate = id;
|
|
446
|
+
if (VERBOSE)
|
|
447
|
+
console.log('[hmr][init] initial mount candidate from SFC registry:', id);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
304
452
|
break;
|
|
305
453
|
}
|
|
306
454
|
case 'typescript': {
|
|
@@ -318,7 +466,7 @@ function applyFullGraph(payload) {
|
|
|
318
466
|
(async () => {
|
|
319
467
|
try {
|
|
320
468
|
if (VERBOSE)
|
|
321
|
-
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=',
|
|
469
|
+
console.log('[hmr][init] mounting initial root from', candidate, 'flavor=', TARGET_FLAVOR);
|
|
322
470
|
// Android-only: avoid racing entry-runtime reset and Activity bring-up
|
|
323
471
|
try {
|
|
324
472
|
const g = globalThis;
|
|
@@ -352,7 +500,7 @@ function applyFullGraph(payload) {
|
|
|
352
500
|
}
|
|
353
501
|
catch { }
|
|
354
502
|
let comp = null;
|
|
355
|
-
switch (
|
|
503
|
+
switch (TARGET_FLAVOR) {
|
|
356
504
|
case 'vue':
|
|
357
505
|
comp = await loadSfcComponent(candidate, 'initial_mount');
|
|
358
506
|
break;
|
|
@@ -401,7 +549,7 @@ function applyFullGraph(payload) {
|
|
|
401
549
|
})();
|
|
402
550
|
}
|
|
403
551
|
else if (VERBOSE) {
|
|
404
|
-
console.warn('[hmr][init] no component found in graph to mount initially for flavor',
|
|
552
|
+
console.warn('[hmr][init] no component found in graph to mount initially for flavor', TARGET_FLAVOR);
|
|
405
553
|
}
|
|
406
554
|
}
|
|
407
555
|
}
|
|
@@ -427,7 +575,7 @@ function applyDelta(payload) {
|
|
|
427
575
|
setGraphVersion(payload.newVersion);
|
|
428
576
|
}
|
|
429
577
|
const changed = payload.changed || [];
|
|
430
|
-
switch (
|
|
578
|
+
switch (TARGET_FLAVOR) {
|
|
431
579
|
case 'vue':
|
|
432
580
|
recordVuePayloadChanges(changed, getGraphVersion());
|
|
433
581
|
break;
|
|
@@ -445,6 +593,16 @@ function applyDelta(payload) {
|
|
|
445
593
|
console.log('[hmr][graph] delta applied newVersion', getGraphVersion(), 'changed=', (payload.changed || []).length, 'removed=', (payload.removed || []).length, 'baseVersion=', payload.baseVersion);
|
|
446
594
|
// Queue evaluation of changed modules (placeholder pipeline)
|
|
447
595
|
if (payload.changed?.length) {
|
|
596
|
+
// Gate: Before the first full-graph is received, delta messages are the server
|
|
597
|
+
// discovering modules during initial boot — NOT actual code changes. The entry-runtime
|
|
598
|
+
// already loaded all modules; re-importing them would cause duplicate Application.run(),
|
|
599
|
+
// router initialization, and modal conflicts. Only queue re-imports after the first
|
|
600
|
+
// full-graph confirms the graph is synced and subsequent deltas are real changes.
|
|
601
|
+
if (!hasReceivedFullGraph) {
|
|
602
|
+
if (VERBOSE)
|
|
603
|
+
console.log('[hmr][delta] skipping re-import queue (initial graph build, no full-graph yet)');
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
448
606
|
// HARD SUPPRESS: the very first delta (baseVersion 0) commonly includes the app main entry which is already evaluated during bootstrap.
|
|
449
607
|
// Importing it again with a cache-bust often produces a spurious module-not-found (timestamp param treated as distinct file).
|
|
450
608
|
const isInitial = payload.baseVersion === 0;
|
|
@@ -507,7 +665,7 @@ function applyDelta(payload) {
|
|
|
507
665
|
// Deterministic navigation using the current Vue app instance rather than vendor-held rootApp
|
|
508
666
|
function __nsNavigateUsingApp(comp, opts = {}) {
|
|
509
667
|
const g = globalThis;
|
|
510
|
-
switch (
|
|
668
|
+
switch (TARGET_FLAVOR) {
|
|
511
669
|
case 'vue':
|
|
512
670
|
ensureVueGlobals();
|
|
513
671
|
break;
|
|
@@ -535,7 +693,7 @@ function __nsNavigateUsingApp(comp, opts = {}) {
|
|
|
535
693
|
const existingApp = getCurrentApp();
|
|
536
694
|
const baseProvides = (existingApp && existingApp._context && existingApp._context.provides) || {};
|
|
537
695
|
const app = AppFactory(normalizeComponent(comp, comp && (comp.__name || comp.name)));
|
|
538
|
-
switch (
|
|
696
|
+
switch (TARGET_FLAVOR) {
|
|
539
697
|
case 'vue':
|
|
540
698
|
ensurePiniaOnApp(app);
|
|
541
699
|
break;
|
|
@@ -669,6 +827,25 @@ async function processQueue() {
|
|
|
669
827
|
return;
|
|
670
828
|
if (VERBOSE)
|
|
671
829
|
console.log('[hmr][queue] processing changed ids', drained);
|
|
830
|
+
// Explicit eviction step.
|
|
831
|
+
//
|
|
832
|
+
// On modern runtimes the URL canonicalizer collapses any
|
|
833
|
+
// `__ns_hmr__/<tag>/` segment back to a stable cache key, so
|
|
834
|
+
// without explicit eviction the upcoming `import(url)` would
|
|
835
|
+
// resolve via V8's `g_moduleRegistry` and return the cached
|
|
836
|
+
// stale module — making the queue drain a silent no-op for
|
|
837
|
+
// every save after the first.
|
|
838
|
+
//
|
|
839
|
+
// We hand the canonical eviction URLs to the runtime first;
|
|
840
|
+
// `invalidateModulesByUrls` is a no-op on older runtimes and
|
|
841
|
+
// `requestModuleFromServer` automatically falls back to the
|
|
842
|
+
// legacy `/ns/m/__ns_hmr__/v<N>/` URL versioning path in that
|
|
843
|
+
// case. node_modules and virtual specs are filtered out by
|
|
844
|
+
// `buildEvictionUrls` so vendor modules stay hot.
|
|
845
|
+
const evictUrls = buildEvictionUrls(drained);
|
|
846
|
+
const evicted = invalidateModulesByUrls(evictUrls);
|
|
847
|
+
if (VERBOSE)
|
|
848
|
+
console.log(`[hmr][queue] eviction count=${evictUrls.length} ok=${evicted}`);
|
|
672
849
|
// Evaluate changed modules best-effort; failures shouldn't completely break HMR.
|
|
673
850
|
for (const id of drained) {
|
|
674
851
|
try {
|
|
@@ -686,10 +863,176 @@ async function processQueue() {
|
|
|
686
863
|
}
|
|
687
864
|
}
|
|
688
865
|
// After evaluating the batch, perform flavor-specific UI refresh.
|
|
689
|
-
switch (
|
|
866
|
+
switch (TARGET_FLAVOR) {
|
|
690
867
|
case 'vue':
|
|
691
868
|
// Vue SFCs are handled via the registry update path; nothing to do here.
|
|
692
869
|
break;
|
|
870
|
+
case 'solid': {
|
|
871
|
+
// Solid .tsx components are self-accepting via solid-refresh's inline
|
|
872
|
+
// patchRegistry — re-importing them is sufficient. For non-component
|
|
873
|
+
// .ts utility modules, we must propagate up the import graph to find
|
|
874
|
+
// the .tsx/.jsx component boundaries and re-import those so their
|
|
875
|
+
// solid-refresh proxies pick up the new dependency values.
|
|
876
|
+
try {
|
|
877
|
+
// Build reverse index: dep id → list of importer ids
|
|
878
|
+
const reverseIndex = new Map();
|
|
879
|
+
for (const [id, mod] of graph) {
|
|
880
|
+
for (const dep of mod.deps) {
|
|
881
|
+
let arr = reverseIndex.get(dep);
|
|
882
|
+
if (!arr) {
|
|
883
|
+
arr = [];
|
|
884
|
+
reverseIndex.set(dep, arr);
|
|
885
|
+
}
|
|
886
|
+
arr.push(id);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
// BFS from each non-tsx changed module up to tsx/jsx boundaries
|
|
890
|
+
const boundaries = new Set();
|
|
891
|
+
for (const id of drained) {
|
|
892
|
+
if (/\.(tsx|jsx)$/i.test(id))
|
|
893
|
+
continue; // already self-accepting
|
|
894
|
+
const visited = new Set();
|
|
895
|
+
const queue = [id];
|
|
896
|
+
while (queue.length) {
|
|
897
|
+
const cur = queue.shift();
|
|
898
|
+
if (visited.has(cur))
|
|
899
|
+
continue;
|
|
900
|
+
visited.add(cur);
|
|
901
|
+
const importers = reverseIndex.get(cur);
|
|
902
|
+
if (!importers)
|
|
903
|
+
continue;
|
|
904
|
+
for (const imp of importers) {
|
|
905
|
+
if (/\.(tsx|jsx)$/i.test(imp)) {
|
|
906
|
+
boundaries.add(imp);
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
queue.push(imp);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
// Re-import each boundary so solid-refresh patchRegistry fires.
|
|
915
|
+
// For route files (TanStack Router), capture the new Route export
|
|
916
|
+
// and patch the router's existing route with the fresh loader.
|
|
917
|
+
let routesPatchCount = 0;
|
|
918
|
+
let discoveredRouter = null;
|
|
919
|
+
// Discover router: try __ns_router global (set by createNativeScriptRouter),
|
|
920
|
+
// then scan globalThis for any router-shaped object with routesById.
|
|
921
|
+
const findRouter = () => {
|
|
922
|
+
if (discoveredRouter)
|
|
923
|
+
return discoveredRouter;
|
|
924
|
+
const g = globalThis;
|
|
925
|
+
if (g.__ns_router?.routesById)
|
|
926
|
+
return (discoveredRouter = g.__ns_router);
|
|
927
|
+
// Fallback: scan common global keys for router
|
|
928
|
+
for (const key of ['__ns_router', 'router', '__router']) {
|
|
929
|
+
if (g[key]?.routesById && g[key]?.invalidate)
|
|
930
|
+
return (discoveredRouter = g[key]);
|
|
931
|
+
}
|
|
932
|
+
return null;
|
|
933
|
+
};
|
|
934
|
+
// Convert boundary file path to TanStack Router fullPath.
|
|
935
|
+
// e.g. /src/routes/posts.$postId.tsx → /posts/$postId
|
|
936
|
+
const boundaryToFullPath = (bid) => {
|
|
937
|
+
const m = bid.match(/\/src\/routes\/(.+)\.(tsx|jsx|ts|js)$/i);
|
|
938
|
+
if (!m)
|
|
939
|
+
return null;
|
|
940
|
+
let p = m[1];
|
|
941
|
+
// Replace dots between segments with slashes (posts.$postId → posts/$postId)
|
|
942
|
+
p = p.replace(/\./g, '/');
|
|
943
|
+
// Handle index files
|
|
944
|
+
if (p === 'index')
|
|
945
|
+
return '/';
|
|
946
|
+
if (p.endsWith('/index'))
|
|
947
|
+
p = p.slice(0, -6);
|
|
948
|
+
// Strip leading - (TanStack pathless layout convention)
|
|
949
|
+
p = p.replace(/(^|\/)-([\w])/g, '$1$2');
|
|
950
|
+
return '/' + p;
|
|
951
|
+
};
|
|
952
|
+
// Find existing route by fullPath (since new Route has no id yet)
|
|
953
|
+
const findRouteByFullPath = (router, fp) => {
|
|
954
|
+
if (!router?.routesById)
|
|
955
|
+
return null;
|
|
956
|
+
for (const rid of Object.keys(router.routesById)) {
|
|
957
|
+
const r = router.routesById[rid];
|
|
958
|
+
if (r?.fullPath === fp)
|
|
959
|
+
return r;
|
|
960
|
+
}
|
|
961
|
+
return null;
|
|
962
|
+
};
|
|
963
|
+
// Evict the boundary set so re-importing each .tsx
|
|
964
|
+
// component actually picks up the new transitive
|
|
965
|
+
// dependency code; without this V8 returns the
|
|
966
|
+
// cached boundary module unchanged.
|
|
967
|
+
const boundaryIds = Array.from(boundaries);
|
|
968
|
+
const solidEvictUrls = buildEvictionUrls(boundaryIds);
|
|
969
|
+
const solidEvicted = invalidateModulesByUrls(solidEvictUrls);
|
|
970
|
+
if (VERBOSE)
|
|
971
|
+
console.log(`[hmr][solid] eviction count=${solidEvictUrls.length} ok=${solidEvicted}`);
|
|
972
|
+
for (const id of boundaries) {
|
|
973
|
+
if (seen.has(id))
|
|
974
|
+
continue;
|
|
975
|
+
try {
|
|
976
|
+
const spec = normalizeSpec(id);
|
|
977
|
+
const url = await requestModuleFromServer(spec);
|
|
978
|
+
if (!url)
|
|
979
|
+
continue;
|
|
980
|
+
if (VERBOSE)
|
|
981
|
+
console.log('[hmr][solid] propagated to boundary', { id, url });
|
|
982
|
+
const mod = await import(/* @vite-ignore */ url);
|
|
983
|
+
// Patch TanStack Router route loaders
|
|
984
|
+
try {
|
|
985
|
+
const newRoute = mod?.Route;
|
|
986
|
+
if (newRoute?.options?.loader) {
|
|
987
|
+
const router = findRouter();
|
|
988
|
+
const fullPath = boundaryToFullPath(id);
|
|
989
|
+
if (VERBOSE)
|
|
990
|
+
console.log('[hmr][solid][diag] route patch attempt', { id, fullPath, hasRouter: !!router, routesByIdKeys: router?.routesById ? Object.keys(router.routesById) : 'none' });
|
|
991
|
+
const existingRoute = fullPath && router ? findRouteByFullPath(router, fullPath) : null;
|
|
992
|
+
if (existingRoute?.options) {
|
|
993
|
+
existingRoute.options.loader = newRoute.options.loader;
|
|
994
|
+
if (newRoute.options.component)
|
|
995
|
+
existingRoute.options.component = newRoute.options.component;
|
|
996
|
+
routesPatchCount++;
|
|
997
|
+
if (VERBOSE)
|
|
998
|
+
console.log('[hmr][solid] patched route loader', existingRoute.id, 'fullPath=', fullPath);
|
|
999
|
+
}
|
|
1000
|
+
else if (VERBOSE) {
|
|
1001
|
+
console.log('[hmr][solid] no matching route for fullPath', fullPath);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
catch (e) {
|
|
1006
|
+
if (VERBOSE)
|
|
1007
|
+
console.warn('[hmr][solid] route patch error', id, e);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
catch (e) {
|
|
1011
|
+
if (VERBOSE)
|
|
1012
|
+
console.warn('[hmr][solid] boundary re-import failed', id, e);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
// Route loaders were patched with fresh closures. The data is
|
|
1016
|
+
// correct in router.state.matches[].loaderData (confirmed via
|
|
1017
|
+
// diagnostics), but the currently visible page doesn't re-render
|
|
1018
|
+
// because the NativeScriptRouterProvider's Solid store flush only
|
|
1019
|
+
// fires through history.subscribe. TODO: find the right mechanism
|
|
1020
|
+
// to trigger a Solid reactive update for the active match stores.
|
|
1021
|
+
if (routesPatchCount > 0 && VERBOSE) {
|
|
1022
|
+
console.log('[hmr][solid] patched', routesPatchCount, 'route loaders (data correct in match state, pending UI refresh mechanism)');
|
|
1023
|
+
}
|
|
1024
|
+
if (VERBOSE) {
|
|
1025
|
+
if (boundaries.size)
|
|
1026
|
+
console.log('[hmr][solid] propagated non-component change to', boundaries.size, 'boundaries', Array.from(boundaries));
|
|
1027
|
+
console.log('[hmr][queue] Solid: modules re-imported, solid-refresh handles reactive update', drained);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
catch (e) {
|
|
1031
|
+
if (VERBOSE)
|
|
1032
|
+
console.warn('[hmr][solid] propagation failed', e);
|
|
1033
|
+
}
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
693
1036
|
case 'typescript': {
|
|
694
1037
|
// For TS apps, always reset back to the conventional app root.
|
|
695
1038
|
// This preserves the shell (Frame, ActionBar, etc.) that the app's
|
|
@@ -702,9 +1045,124 @@ async function processQueue() {
|
|
|
702
1045
|
console.warn('[hmr][queue] TS flavor: Application.resetRootView unavailable; skipping UI refresh');
|
|
703
1046
|
break;
|
|
704
1047
|
}
|
|
1048
|
+
// Re-fetch changed XML/CSS files and update the bundled module registry
|
|
1049
|
+
// so Builder.createViewFromEntry picks up fresh content.
|
|
1050
|
+
const rawAssetIds = drained.filter((id) => /\.(xml|css|scss|sass|less)$/i.test(id));
|
|
1051
|
+
if (rawAssetIds.length && typeof g.registerModule === 'function') {
|
|
1052
|
+
const origin = getHttpOriginForVite() || deriveHttpOrigin(getHMRWsUrl());
|
|
1053
|
+
if (origin) {
|
|
1054
|
+
for (const id of rawAssetIds) {
|
|
1055
|
+
try {
|
|
1056
|
+
const spec = normalizeSpec(id);
|
|
1057
|
+
// Fetch the raw file content directly from Vite's dev server.
|
|
1058
|
+
// Use the project-relative path which Vite serves as static files.
|
|
1059
|
+
const fetchUrl = origin + (spec.startsWith('/') ? spec : '/' + spec);
|
|
1060
|
+
if (VERBOSE)
|
|
1061
|
+
console.log('[hmr][queue] fetching raw asset', { id, fetchUrl });
|
|
1062
|
+
const resp = await fetch(fetchUrl);
|
|
1063
|
+
if (resp.ok) {
|
|
1064
|
+
const rawContent = await resp.text();
|
|
1065
|
+
// Register under all nickname variants the module registry uses.
|
|
1066
|
+
// The bundler context registers XML as e.g., './main-page.xml' and 'main-page.xml'
|
|
1067
|
+
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
1068
|
+
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
1069
|
+
if (relPath.startsWith(appVirtual))
|
|
1070
|
+
relPath = relPath.slice(appVirtual.length);
|
|
1071
|
+
const nicknames = ['./' + relPath, relPath];
|
|
1072
|
+
// Also add without extension for CSS
|
|
1073
|
+
const extIdx = relPath.lastIndexOf('.');
|
|
1074
|
+
if (extIdx > 0) {
|
|
1075
|
+
const baseName = relPath.slice(0, extIdx);
|
|
1076
|
+
if (!relPath.endsWith('.xml'))
|
|
1077
|
+
nicknames.push(baseName, './' + baseName);
|
|
1078
|
+
}
|
|
1079
|
+
for (const name of nicknames) {
|
|
1080
|
+
if (VERBOSE)
|
|
1081
|
+
console.log('[hmr][queue] re-registering module', name);
|
|
1082
|
+
g.registerModule(name, () => rawContent);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
else if (VERBOSE) {
|
|
1086
|
+
console.warn('[hmr][queue] raw asset fetch failed', id, resp.status);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
catch (e) {
|
|
1090
|
+
if (VERBOSE)
|
|
1091
|
+
console.warn('[hmr][queue] raw asset refresh failed for', id, e);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
// Determine if we can navigate in-place to a changed page
|
|
1097
|
+
// instead of resetting all the way back to app-root.
|
|
1098
|
+
// This keeps the user on the page they're editing for faster iteration.
|
|
1099
|
+
const changedXmlPages = drained
|
|
1100
|
+
.filter((id) => /\.xml$/i.test(id))
|
|
1101
|
+
.map((id) => {
|
|
1102
|
+
const spec = normalizeSpec(id);
|
|
1103
|
+
const appVirtual = APP_VIRTUAL_WITH_SLASH.replace(/^\//, '');
|
|
1104
|
+
let relPath = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
1105
|
+
if (relPath.startsWith(appVirtual))
|
|
1106
|
+
relPath = relPath.slice(appVirtual.length);
|
|
1107
|
+
// Strip .xml extension to get the moduleName (e.g., 'pages/status-bar')
|
|
1108
|
+
return relPath.replace(/\.xml$/i, '');
|
|
1109
|
+
})
|
|
1110
|
+
.filter((m) => m && m !== 'app-root');
|
|
1111
|
+
// Resolve the topmost Frame from the bundled realm.
|
|
1112
|
+
// Frame.topmost() relies on an internal frameStack array, so we must
|
|
1113
|
+
// call it on the bundled-realm class. Multiple strategies to find it:
|
|
1114
|
+
const FrameClass = getCore('Frame') || g.Frame;
|
|
1115
|
+
let topFrame = null;
|
|
1116
|
+
// 1) Try the vendor-realm static topmost()
|
|
1117
|
+
try {
|
|
1118
|
+
topFrame = FrameClass?.topmost?.();
|
|
1119
|
+
}
|
|
1120
|
+
catch { }
|
|
1121
|
+
// 2) Try getting the root view from Application — if it's a Frame, use it
|
|
1122
|
+
if (!topFrame) {
|
|
1123
|
+
try {
|
|
1124
|
+
const rootView = App.getRootView?.() || App._rootView;
|
|
1125
|
+
if (rootView) {
|
|
1126
|
+
// rootView could be a Frame itself, or contain a Frame
|
|
1127
|
+
const isFrame = rootView.constructor?.name === 'Frame' || rootView.navigate;
|
|
1128
|
+
if (isFrame) {
|
|
1129
|
+
topFrame = rootView;
|
|
1130
|
+
}
|
|
1131
|
+
else if (rootView.getChildAt) {
|
|
1132
|
+
// Walk direct children looking for a Frame
|
|
1133
|
+
for (let i = 0; i < (rootView.getChildrenCount?.() || 0); i++) {
|
|
1134
|
+
const child = rootView.getChildAt(i);
|
|
1135
|
+
if (child?.constructor?.name === 'Frame' || child?.navigate) {
|
|
1136
|
+
topFrame = child;
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
catch { }
|
|
1144
|
+
}
|
|
705
1145
|
if (VERBOSE)
|
|
706
|
-
console.log('[hmr][queue] TS
|
|
707
|
-
|
|
1146
|
+
console.log('[hmr][queue] TS: changedXmlPages=', changedXmlPages, 'topFrame=', !!topFrame);
|
|
1147
|
+
if (changedXmlPages.length > 0 && topFrame) {
|
|
1148
|
+
// Navigate the current frame to the changed page directly.
|
|
1149
|
+
// Use the last changed XML page (most specific).
|
|
1150
|
+
const moduleName = changedXmlPages[changedXmlPages.length - 1];
|
|
1151
|
+
if (VERBOSE)
|
|
1152
|
+
console.log('[hmr][queue] TS: navigating in-place to', moduleName);
|
|
1153
|
+
try {
|
|
1154
|
+
topFrame.navigate({ moduleName, clearHistory: false, animated: false });
|
|
1155
|
+
}
|
|
1156
|
+
catch (navErr) {
|
|
1157
|
+
console.warn('[hmr][queue] TS flavor: in-place navigate failed, falling back to resetRootView', navErr);
|
|
1158
|
+
App.resetRootView({ moduleName: 'app-root' });
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
if (VERBOSE)
|
|
1163
|
+
console.log('[hmr][queue] TS flavor: resetRootView(app-root) after changes');
|
|
1164
|
+
App.resetRootView({ moduleName: 'app-root' });
|
|
1165
|
+
}
|
|
708
1166
|
}
|
|
709
1167
|
catch (e) {
|
|
710
1168
|
console.warn('[hmr][queue] TS flavor: resetRootView(app-root) failed', e);
|
|
@@ -752,6 +1210,11 @@ function connectHmr() {
|
|
|
752
1210
|
console.log('[hmr-client] Already connecting to HMR WebSocket, skipping');
|
|
753
1211
|
return;
|
|
754
1212
|
}
|
|
1213
|
+
try {
|
|
1214
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1215
|
+
}
|
|
1216
|
+
catch { }
|
|
1217
|
+
const overlayStage = hasOpenedHmrSocket ? 'reconnecting' : 'connecting';
|
|
755
1218
|
const baseUrl = getHMRWsUrl() || 'ws://localhost:5173/ns-hmr';
|
|
756
1219
|
const buildCandidates = (url) => {
|
|
757
1220
|
let candidates = [];
|
|
@@ -795,7 +1258,7 @@ function connectHmr() {
|
|
|
795
1258
|
if (seen.has(key))
|
|
796
1259
|
continue;
|
|
797
1260
|
seen.add(key);
|
|
798
|
-
const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}`;
|
|
1261
|
+
const cand = `${p}://${host}:${port}${u.pathname || '/ns-hmr'}${u.search || ''}`;
|
|
799
1262
|
candidates.push(cand);
|
|
800
1263
|
}
|
|
801
1264
|
}
|
|
@@ -811,10 +1274,19 @@ function connectHmr() {
|
|
|
811
1274
|
let idx = 0;
|
|
812
1275
|
const tryNext = () => {
|
|
813
1276
|
if (idx >= candidates.length) {
|
|
1277
|
+
showConnectionOverlayNow('offline', 'Waiting for the Vite websocket to come back.');
|
|
814
1278
|
console.warn('[hmr-client] All WS candidates failed:', candidates.join(', '));
|
|
1279
|
+
setTimeout(connectHmr, 1500);
|
|
815
1280
|
return;
|
|
816
1281
|
}
|
|
817
1282
|
const url = candidates[idx++];
|
|
1283
|
+
const connectionDetail = `${overlayStage === 'reconnecting' ? 'Retrying' : 'Opening'} ${url}`;
|
|
1284
|
+
if (connectionOverlayVisible) {
|
|
1285
|
+
updateConnectionOverlay(overlayStage, connectionDetail);
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
scheduleConnectionOverlay(overlayStage, connectionDetail);
|
|
1289
|
+
}
|
|
818
1290
|
try {
|
|
819
1291
|
if (__NS_ENV_VERBOSE__)
|
|
820
1292
|
console.log('[hmr-client] Connecting to HMR WebSocket:', url);
|
|
@@ -836,7 +1308,25 @@ function connectHmr() {
|
|
|
836
1308
|
sock.onopen = () => {
|
|
837
1309
|
opened = true;
|
|
838
1310
|
clearTimeout(timeout);
|
|
1311
|
+
clearConnectionOverlayTimer();
|
|
1312
|
+
hasOpenedHmrSocket = true;
|
|
1313
|
+
awaitingHealthyHmrMessage = true;
|
|
1314
|
+
try {
|
|
1315
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = true;
|
|
1316
|
+
}
|
|
1317
|
+
catch { }
|
|
1318
|
+
if (connectionOverlayVisible) {
|
|
1319
|
+
showConnectionOverlayNow('synchronizing', 'Connected. Synchronizing the HMR graph.');
|
|
1320
|
+
}
|
|
839
1321
|
VERBOSE && console.log('[hmr-client] Connected to HMR WebSocket');
|
|
1322
|
+
// Print the active module reload mode once on first
|
|
1323
|
+
// successful connect so the user can correlate HMR latency
|
|
1324
|
+
// with runtime capability without grepping for protocol
|
|
1325
|
+
// details. The banner is verbose-gated.
|
|
1326
|
+
try {
|
|
1327
|
+
emitHmrModeBannerOnce();
|
|
1328
|
+
}
|
|
1329
|
+
catch { }
|
|
840
1330
|
};
|
|
841
1331
|
sock.onmessage = handleHmrMessage;
|
|
842
1332
|
sock.onerror = (error) => {
|
|
@@ -845,6 +1335,10 @@ function connectHmr() {
|
|
|
845
1335
|
};
|
|
846
1336
|
sock.onclose = (ev) => {
|
|
847
1337
|
clearTimeout(timeout);
|
|
1338
|
+
try {
|
|
1339
|
+
globalThis.__NS_HMR_CLIENT_SOCKET_READY__ = false;
|
|
1340
|
+
}
|
|
1341
|
+
catch { }
|
|
848
1342
|
if (!opened) {
|
|
849
1343
|
// immediate failure during connect → try another candidate
|
|
850
1344
|
if (VERBOSE)
|
|
@@ -854,6 +1348,7 @@ function connectHmr() {
|
|
|
854
1348
|
else {
|
|
855
1349
|
if (VERBOSE)
|
|
856
1350
|
console.log('[hmr-client] WebSocket closed (code', ev?.code, '), will reconnect…');
|
|
1351
|
+
scheduleConnectionOverlay('reconnecting', 'The websocket closed. Waiting to reconnect.', 700);
|
|
857
1352
|
// try to reconnect with full candidate list again
|
|
858
1353
|
setTimeout(connectHmr, 1000);
|
|
859
1354
|
}
|
|
@@ -874,6 +1369,12 @@ async function handleHmrMessage(ev) {
|
|
|
874
1369
|
catch {
|
|
875
1370
|
return;
|
|
876
1371
|
}
|
|
1372
|
+
if (awaitingHealthyHmrMessage && msg) {
|
|
1373
|
+
markHmrConnectionHealthy();
|
|
1374
|
+
}
|
|
1375
|
+
if (VERBOSE && msg?.type) {
|
|
1376
|
+
console.log('[hmr-client] received message', msg.type);
|
|
1377
|
+
}
|
|
877
1378
|
// Notify optional app-level hook after an HMR batch is applied.
|
|
878
1379
|
function notifyAppHmrUpdate(kind, changedIds) {
|
|
879
1380
|
try {
|
|
@@ -885,6 +1386,17 @@ async function handleHmrMessage(ev) {
|
|
|
885
1386
|
catch { }
|
|
886
1387
|
}
|
|
887
1388
|
if (msg) {
|
|
1389
|
+
// `ns:hmr-pending` is a fire-and-forget UX hint emitted by the
|
|
1390
|
+
// server at the START of handleHotUpdate. We drive the
|
|
1391
|
+
// HMR-applying overlay's 'received' frame here (synchronously),
|
|
1392
|
+
// well before the authoritative payload (`ns:angular-update` /
|
|
1393
|
+
// `ns:css-updates`) lands. Skip running any other handlers —
|
|
1394
|
+
// the pending message has no module payload and intentionally
|
|
1395
|
+
// does not bump the graph version.
|
|
1396
|
+
if (msg.type === 'ns:hmr-pending' && typeof msg.path === 'string') {
|
|
1397
|
+
setHmrPendingOverlay(msg.path);
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
888
1400
|
if (msg.type === 'ns:hmr-full-graph') {
|
|
889
1401
|
// Bump a monotonic nonce so HTTP ESM imports can always be cache-busted per update.
|
|
890
1402
|
try {
|
|
@@ -896,6 +1408,34 @@ async function handleHmrMessage(ev) {
|
|
|
896
1408
|
const prevGraph = new Map(graph);
|
|
897
1409
|
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
898
1410
|
applyFullGraph(msg);
|
|
1411
|
+
hasReceivedFullGraph = true;
|
|
1412
|
+
// Gate: On first boot, the entry-runtime handles all initial module loading
|
|
1413
|
+
// (with the import map already configured). Don't re-import here — the graph
|
|
1414
|
+
// is stored above for future HMR delta comparisons, but modules are already
|
|
1415
|
+
// loaded correctly via the entry-runtime boot sequence.
|
|
1416
|
+
//
|
|
1417
|
+
// Two cases to catch:
|
|
1418
|
+
// 1. Boot still in progress (__NS_HMR_BOOT_COMPLETE__ is false)
|
|
1419
|
+
// 2. Boot already finished but this is the FIRST full-graph (prevGraph was
|
|
1420
|
+
// empty). The WebSocket often connects after entry-runtime finishes, so
|
|
1421
|
+
// boot is "complete" but we still shouldn't re-import — all modules were
|
|
1422
|
+
// just loaded fresh. Only re-import on subsequent full-graphs (reconnect
|
|
1423
|
+
// scenarios) where prevGraph already has entries.
|
|
1424
|
+
if (!globalThis.__NS_HMR_BOOT_COMPLETE__) {
|
|
1425
|
+
if (VERBOSE)
|
|
1426
|
+
console.info('[hmr][full-graph] skipping initial re-import (boot in progress)');
|
|
1427
|
+
const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
|
|
1428
|
+
notifyAppHmrUpdate('full-graph', fullIds);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (prevGraph.size === 0) {
|
|
1432
|
+
if (VERBOSE)
|
|
1433
|
+
console.info('[hmr][full-graph] skipping re-import on first graph after boot (modules already fresh)');
|
|
1434
|
+
const fullIds = Array.isArray(msg.modules) ? msg.modules.map((m) => m?.id).filter(Boolean) : [];
|
|
1435
|
+
notifyAppHmrUpdate('full-graph', fullIds);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
// Reconnect / resync case — re-import changed modules as normal.
|
|
899
1439
|
// In some cases (e.g. server chooses full-graph resync / page reload), we won't
|
|
900
1440
|
// receive a delta queue to re-import changed TS modules. Without re-import,
|
|
901
1441
|
// HTTP ESM caching means module bodies (and side effects) won't re-run.
|
|
@@ -926,6 +1466,14 @@ async function handleHmrMessage(ev) {
|
|
|
926
1466
|
});
|
|
927
1467
|
if (toReimport.length && VERBOSE)
|
|
928
1468
|
console.log('[hmr][full-graph] inferred changed modules; re-importing', toReimport);
|
|
1469
|
+
// Evict the inferred changed set before re-importing.
|
|
1470
|
+
// See `processQueue` for the architectural rationale; the
|
|
1471
|
+
// full-graph code path is the resync fallback (server chose
|
|
1472
|
+
// not to send a delta) and shares the same V8 cache pitfall.
|
|
1473
|
+
const fgEvictUrls = buildEvictionUrls(toReimport);
|
|
1474
|
+
const fgEvicted = invalidateModulesByUrls(fgEvictUrls);
|
|
1475
|
+
if (VERBOSE)
|
|
1476
|
+
console.log(`[hmr][full-graph] eviction count=${fgEvictUrls.length} ok=${fgEvicted}`);
|
|
929
1477
|
for (const id of toReimport) {
|
|
930
1478
|
try {
|
|
931
1479
|
const spec = normalizeSpec(id);
|
|
@@ -998,8 +1546,13 @@ async function handleHmrMessage(ev) {
|
|
|
998
1546
|
notifyAppHmrUpdate('delta', deltaIds);
|
|
999
1547
|
return;
|
|
1000
1548
|
}
|
|
1001
|
-
else
|
|
1002
|
-
|
|
1549
|
+
else {
|
|
1550
|
+
if (msg.type === 'ns:angular-update' && typeof msg.version === 'number') {
|
|
1551
|
+
setGraphVersion(Number(msg.version || getGraphVersion() || 0));
|
|
1552
|
+
}
|
|
1553
|
+
if (await handleAngularHotUpdateMessage(msg, { getCore, verbose: VERBOSE })) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1003
1556
|
}
|
|
1004
1557
|
}
|
|
1005
1558
|
// On-demand module fetch response (Option A)
|
|
@@ -1140,7 +1693,7 @@ async function performResetRoot(newComponent) {
|
|
|
1140
1693
|
if (cachedRoot)
|
|
1141
1694
|
return cachedRoot;
|
|
1142
1695
|
try {
|
|
1143
|
-
switch (
|
|
1696
|
+
switch (TARGET_FLAVOR) {
|
|
1144
1697
|
case 'vue':
|
|
1145
1698
|
cachedRoot = getRootForVue(newComponent, state);
|
|
1146
1699
|
break;
|
|
@@ -1455,9 +2008,29 @@ export function initHmrClient(opts) {
|
|
|
1455
2008
|
}
|
|
1456
2009
|
g.__NS_HMR_CLIENT_ACTIVE__ = true;
|
|
1457
2010
|
ensureCoreAliasesOnGlobalThis();
|
|
1458
|
-
|
|
2011
|
+
// Defer WebSocket connection until boot completes to avoid native V8 crashes
|
|
2012
|
+
// caused by concurrent WebSocket message handling + HTTP fetch during early startup.
|
|
2013
|
+
// The WebSocket is only needed for HMR updates, not the initial boot sequence.
|
|
2014
|
+
if (g.__NS_HMR_BOOT_COMPLETE__) {
|
|
2015
|
+
connectHmr();
|
|
2016
|
+
}
|
|
2017
|
+
else {
|
|
2018
|
+
const waitForBoot = () => {
|
|
2019
|
+
if (globalThis.__NS_HMR_BOOT_COMPLETE__) {
|
|
2020
|
+
if (VERBOSE)
|
|
2021
|
+
console.log('[hmr-client] boot complete, connecting HMR WebSocket');
|
|
2022
|
+
connectHmr();
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
2025
|
+
setTimeout(waitForBoot, 100);
|
|
2026
|
+
}
|
|
2027
|
+
};
|
|
2028
|
+
if (VERBOSE)
|
|
2029
|
+
console.log('[hmr-client] deferring WebSocket connection until boot completes');
|
|
2030
|
+
setTimeout(waitForBoot, 100);
|
|
2031
|
+
}
|
|
1459
2032
|
// Best-effort: install back wrapper even before first remount; original root may be captured later
|
|
1460
|
-
switch (
|
|
2033
|
+
switch (TARGET_FLAVOR) {
|
|
1461
2034
|
case 'vue':
|
|
1462
2035
|
ensureBackWrapperInstalled(performResetRoot, getCore);
|
|
1463
2036
|
break;
|