@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
|
@@ -1,3 +1,71 @@
|
|
|
1
|
+
// Opt-out flag for the apply-progress overlay (default: enabled).
|
|
2
|
+
// Driven by `NS_VITE_PROGRESS_OVERLAY=0` (or `false`/`off`/`no`) on the
|
|
3
|
+
// dev server; baked into the bundle via
|
|
4
|
+
// `__NS_HMR_PROGRESS_OVERLAY_ENABLED__` at build time. We collapse the
|
|
5
|
+
// build-time constant into a runtime boolean once so each call-site is
|
|
6
|
+
// a single property check rather than a try/typeof. Tests that re-run
|
|
7
|
+
// the angular client (via vitest) see `undefined` and default to
|
|
8
|
+
// enabled — matching the production dev-server experience.
|
|
9
|
+
const overlayEnabled = (() => {
|
|
10
|
+
try {
|
|
11
|
+
return typeof __NS_HMR_PROGRESS_OVERLAY_ENABLED__ === 'boolean' ? __NS_HMR_PROGRESS_OVERLAY_ENABLED__ : true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
function getHmrOverlayApi() {
|
|
18
|
+
if (!overlayEnabled)
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
return globalThis.__NS_HMR_DEV_OVERLAY__ || null;
|
|
22
|
+
}
|
|
23
|
+
catch { }
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function setUpdateOverlayStage(stage, info) {
|
|
27
|
+
if (!overlayEnabled)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
const api = getHmrOverlayApi();
|
|
31
|
+
if (api && typeof api.setUpdateStage === 'function') {
|
|
32
|
+
api.setUpdateStage(stage, info);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
}
|
|
37
|
+
function hideUpdateOverlay() {
|
|
38
|
+
if (!overlayEnabled)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
const api = getHmrOverlayApi();
|
|
42
|
+
if (api && typeof api.hide === 'function') {
|
|
43
|
+
api.hide('hmr-applied');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
47
|
+
}
|
|
48
|
+
// Safe accessor for the build-time `__NS_ENV_VERBOSE__` constant.
|
|
49
|
+
//
|
|
50
|
+
// Vite replaces `__NS_ENV_VERBOSE__` at build time via `define`. In
|
|
51
|
+
// unit tests (vitest, plain ts-node, etc.) the identifier is
|
|
52
|
+
// undeclared at runtime, so a bare reference throws ReferenceError —
|
|
53
|
+
// `typeof` is the only safe way to probe it. We collapse the
|
|
54
|
+
// build-time constant into a runtime boolean once at module load and
|
|
55
|
+
// then short-circuit on `verbose` arguments at every call-site.
|
|
56
|
+
//
|
|
57
|
+
// Always order `verbose && envVerbose` so the runtime flag
|
|
58
|
+
// short-circuits the build-time constant; that prevents reference
|
|
59
|
+
// errors in test environments where the `define` substitution does
|
|
60
|
+
// not run.
|
|
61
|
+
const envVerbose = (() => {
|
|
62
|
+
try {
|
|
63
|
+
return typeof __NS_ENV_VERBOSE__ === 'boolean' ? __NS_ENV_VERBOSE__ : false;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
1
69
|
export function installAngularHmrClientHooks() {
|
|
2
70
|
const g = globalThis;
|
|
3
71
|
if (g.__NS_ANGULAR_HMR_CLIENT_INSTALLED__) {
|
|
@@ -5,53 +73,781 @@ export function installAngularHmrClientHooks() {
|
|
|
5
73
|
}
|
|
6
74
|
try {
|
|
7
75
|
g.__NS_ANGULAR_HMR_CLIENT_INSTALLED__ = true;
|
|
8
|
-
if (
|
|
76
|
+
if (envVerbose) {
|
|
9
77
|
console.log('[hmr-angular] client hooks installed');
|
|
10
78
|
}
|
|
11
79
|
}
|
|
12
80
|
catch { }
|
|
13
81
|
}
|
|
14
|
-
|
|
82
|
+
// HMR cycle serialization mutex.
|
|
83
|
+
//
|
|
84
|
+
// A back-to-back save (e.g., the user holds Cmd+S, or `runOnSave`
|
|
85
|
+
// saves a file twice during one tick) could overlap two HMR cycles:
|
|
86
|
+
// the first cycle's `import(entry)` would still be resolving when the
|
|
87
|
+
// second cycle's `__nsInvalidateModules` ran, leaving the registry in
|
|
88
|
+
// a half-evicted state and producing flaky "module already evaluated"
|
|
89
|
+
// errors.
|
|
90
|
+
//
|
|
91
|
+
// Each `handleAngularHotUpdateMessage` call publishes a promise to
|
|
92
|
+
// `inFlightHmrCycle` and awaits the previous publication before
|
|
93
|
+
// starting its own evict + import. The previous-cycle promise resolves
|
|
94
|
+
// regardless of success or failure (we always run `resolveCycle()` in
|
|
95
|
+
// `finally`), so a stuck cycle can't deadlock the queue. The mutex is
|
|
96
|
+
// intentionally process-wide and resets naturally on cold-boot or
|
|
97
|
+
// websocket reconnect (both blow away the JS realm).
|
|
98
|
+
let inFlightHmrCycle = null;
|
|
99
|
+
// Explicit module eviction.
|
|
100
|
+
//
|
|
101
|
+
// `__nsInvalidateModules` is a runtime-installed global that takes an
|
|
102
|
+
// array of canonical /ns/m/<rel> URLs and removes each one from V8's
|
|
103
|
+
// module registry (`g_moduleRegistry`). The runtime canonicalizer
|
|
104
|
+
// strips legacy `__ns_hmr__/<tag>/` and `__ns_boot__/b1/` segments
|
|
105
|
+
// before lookup, so the same URL evicts every cache entry historically
|
|
106
|
+
// created for that module — even if a stale tagged URL is still around.
|
|
107
|
+
//
|
|
108
|
+
// Soft-fails on older runtimes that don't expose the function. In that
|
|
109
|
+
// case we fall through to the legacy URL-versioning path and emit a
|
|
110
|
+
// one-time warning so the user knows the eviction protocol is
|
|
111
|
+
// unavailable.
|
|
112
|
+
// Dispatch one of Vite's standard HMR lifecycle events through the iOS
|
|
113
|
+
// runtime's `__NS_DISPATCH_HOT_EVENT__` global. The runtime owns the
|
|
114
|
+
// listener registry that `import.meta.hot.on(event, cb)` populates; this
|
|
115
|
+
// function is the producer side that lets the Angular HMR client emit the
|
|
116
|
+
// canonical Vite events (`vite:beforeUpdate`, `vite:afterUpdate`,
|
|
117
|
+
// `vite:beforeFullReload`, `vite:invalidate`, `vite:error`) at the right
|
|
118
|
+
// moments. User code that does
|
|
119
|
+
// `import.meta.hot.on('vite:beforeUpdate', cb)` only fires when this is
|
|
120
|
+
// called.
|
|
121
|
+
//
|
|
122
|
+
// Soft-fails on older runtimes that don't expose the dispatcher (the
|
|
123
|
+
// global is undefined; we no-op). Per-listener failures are swallowed
|
|
124
|
+
// inside the runtime, so a single bad listener can't break the HMR cycle.
|
|
125
|
+
function dispatchHotEvent(g, event, payload) {
|
|
126
|
+
try {
|
|
127
|
+
const fn = g && g.__NS_DISPATCH_HOT_EVENT__;
|
|
128
|
+
if (typeof fn === 'function') {
|
|
129
|
+
fn.call(g, event, payload);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
// Defensive: if the dispatcher itself throws, we'd already have
|
|
134
|
+
// no listeners (per-callback failures swallowed inside the
|
|
135
|
+
// runtime), so this is purely a safety net for runtime
|
|
136
|
+
// regressions. Log only under verbose to avoid noisy dev logs.
|
|
137
|
+
if (envVerbose) {
|
|
138
|
+
console.warn(`[ns-hmr-diag][client] __NS_DISPATCH_HOT_EVENT__('${event}') threw:`, err?.message ?? err);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Trigger a full app reload via the runtime's `__nsReloadDevApp` global,
|
|
143
|
+
// dispatching `vite:beforeFullReload` first so user-code listeners can
|
|
144
|
+
// react. Returns `true` if the reload was triggered, `false` if the
|
|
145
|
+
// runtime doesn't expose the API. Used both by `import.meta.hot.invalidate()`
|
|
146
|
+
// (handled directly inside the runtime) AND by the declined-module check
|
|
147
|
+
// below (where the JS layer makes the decision and asks for the reload).
|
|
148
|
+
function triggerFullReload(g, message) {
|
|
149
|
+
const fn = g && g.__nsReloadDevApp;
|
|
150
|
+
if (typeof fn !== 'function') {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
dispatchHotEvent(g, 'vite:beforeFullReload', { message });
|
|
154
|
+
try {
|
|
155
|
+
// Fire-and-forget — `__nsReloadDevApp` returns a Promise, but we
|
|
156
|
+
// don't await it here because the JS realm is about to be torn
|
|
157
|
+
// down. The HMR cycle's caller treats this as terminal and
|
|
158
|
+
// short-circuits the rest of the cycle.
|
|
159
|
+
fn.call(g);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
if (envVerbose) {
|
|
163
|
+
console.warn('[ns-hmr-diag][client] __nsReloadDevApp threw:', err?.message ?? err);
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
// Check if any module being touched by this HMR update has called
|
|
170
|
+
// `import.meta.hot.decline()`. If so, per Vite spec we MUST do a full
|
|
171
|
+
// reload instead of a hot-update — declining means "I refuse to be
|
|
172
|
+
// hot-updated". Returns `true` if a full reload was triggered (caller
|
|
173
|
+
// should short-circuit the rest of the cycle).
|
|
174
|
+
//
|
|
175
|
+
// Older runtimes without `__nsHasDeclinedModule` skip the check entirely
|
|
176
|
+
// (legacy behaviour: HMR proceeds with the reboot, which is the same
|
|
177
|
+
// behaviour they had before this fix).
|
|
178
|
+
function checkDeclinedAndReload(g, msg, options) {
|
|
179
|
+
const checker = g && g.__nsHasDeclinedModule;
|
|
180
|
+
if (typeof checker !== 'function')
|
|
181
|
+
return false;
|
|
182
|
+
// `evictPaths` is the eviction set the dev server computed for this
|
|
183
|
+
// HMR cycle — every module that needs to be re-evaluated. If ANY of
|
|
184
|
+
// those modules declined HMR, the whole cycle has to convert to a
|
|
185
|
+
// full reload. Pass them through verbatim; the runtime canonicalizes
|
|
186
|
+
// internally when matching against `g_hotDeclined`.
|
|
187
|
+
const evictPaths = Array.isArray(msg?.evictPaths) ? msg.evictPaths : [];
|
|
188
|
+
let declined = false;
|
|
189
|
+
try {
|
|
190
|
+
declined = !!checker.call(g, evictPaths);
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
if (options.verbose && envVerbose) {
|
|
194
|
+
console.warn('[ns-hmr-diag][client] __nsHasDeclinedModule threw:', err?.message ?? err);
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (!declined)
|
|
199
|
+
return false;
|
|
200
|
+
const filePath = typeof msg?.path === 'string' ? msg.path : '<unknown>';
|
|
201
|
+
const message = `module declined HMR (file=${filePath}, evictPaths=${evictPaths.length})`;
|
|
202
|
+
console.info(`[ns-hmr][decline] ${message} — falling back to full reload`);
|
|
203
|
+
const reloaded = triggerFullReload(g, message);
|
|
204
|
+
if (!reloaded && options.verbose && envVerbose) {
|
|
205
|
+
console.warn('[ns-hmr-diag][client] declined module detected but __nsReloadDevApp unavailable; HMR will proceed (older runtime)');
|
|
206
|
+
}
|
|
207
|
+
return reloaded;
|
|
208
|
+
}
|
|
209
|
+
// Drain `import.meta.hot.dispose(cb)` callbacks via the iOS runtime's
|
|
210
|
+
// `globalThis.__nsRunHmrDispose()` global before Angular's reboot.
|
|
211
|
+
//
|
|
212
|
+
// The runtime owns the dispose registry (`g_hotDispose` in
|
|
213
|
+
// `HMRSupport.mm`) — every call to `import.meta.hot.dispose(cb)` from
|
|
214
|
+
// user code lands there. This client just asks the runtime to drain.
|
|
215
|
+
// We pass NO key argument so the runtime drains every registered
|
|
216
|
+
// callback across every module, which matches the wholesale-reboot
|
|
217
|
+
// semantics of `__reboot_ng_modules__`: when Angular tears down its
|
|
218
|
+
// component tree and re-bootstraps, every module's accumulated side
|
|
219
|
+
// effects are conceptually being thrown away. (A future per-module
|
|
220
|
+
// HMR client could pass an explicit key list to limit the drain.)
|
|
221
|
+
//
|
|
222
|
+
// Older runtimes (before this hook shipped) silently no-op via the
|
|
223
|
+
// optional chain; in that case, dispose callbacks register but never
|
|
224
|
+
// fire, and the worker-specific safety net in `terminateTrackedWorkers`
|
|
225
|
+
// below still catches the worker pile-up case.
|
|
226
|
+
function runHmrDisposeCallbacks(g, options) {
|
|
227
|
+
let runner;
|
|
228
|
+
try {
|
|
229
|
+
const candidate = g && g.__nsRunHmrDispose;
|
|
230
|
+
if (typeof candidate === 'function') {
|
|
231
|
+
runner = candidate;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// `globalThis` access defensiveness — Proxy-wrapped globals can
|
|
236
|
+
// throw on property reads.
|
|
237
|
+
}
|
|
238
|
+
if (!runner) {
|
|
239
|
+
// Older runtime: the dispose callback registry exists in the
|
|
240
|
+
// native runtime but no JS-callable drain entrypoint is
|
|
241
|
+
// available. Silently no-op (worker terminator below still
|
|
242
|
+
// runs). The diagnostic message is gated to verbose so we
|
|
243
|
+
// don't nag users on every cycle of every project that hasn't
|
|
244
|
+
// adopted the new runtime yet.
|
|
245
|
+
if (options.verbose && envVerbose) {
|
|
246
|
+
console.info('[ns-hmr-diag][client] runtime missing __nsRunHmrDispose; skipping import.meta.hot.dispose drain (upgrade @nativescript/ios for support)');
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
let executed = null;
|
|
251
|
+
try {
|
|
252
|
+
const result = runner.call(g);
|
|
253
|
+
executed = typeof result === 'number' ? result : 0;
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
// One bad disposer should already be swallowed inside the
|
|
257
|
+
// runtime per-callback try/catch. If the runtime itself throws
|
|
258
|
+
// (out-of-memory, isolate teardown race, etc.) we MUST NOT
|
|
259
|
+
// take down the HMR cycle.
|
|
260
|
+
if (options.verbose && envVerbose) {
|
|
261
|
+
console.warn('[ns-hmr-diag][client] __nsRunHmrDispose threw:', err?.message ?? err);
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Surface ONE concise log per HMR cycle that actually had disposers
|
|
266
|
+
// to run. Quiet on cycles where no module had registered any.
|
|
267
|
+
if (executed && executed > 0) {
|
|
268
|
+
console.info(`[ns-hmr][dispose] ran ${executed} import.meta.hot.dispose callback${executed === 1 ? '' : 's'} before reboot`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Terminate every live worker before Angular's reboot. Two-tier strategy:
|
|
272
|
+
//
|
|
273
|
+
// 1. PREFERRED — `globalThis.__nsTerminateAllWorkers()` (NS iOS runtime
|
|
274
|
+
// ≥ the version that ships `Worker::TerminateAllWorkersCallback`).
|
|
275
|
+
// Native code iterates `Caches::Workers` (the runtime's authoritative
|
|
276
|
+
// worker registry) and calls `WorkerWrapper::Terminate()` on each
|
|
277
|
+
// live entry. Universal: catches every worker regardless of how it
|
|
278
|
+
// was created — `new Worker(new URL(...))`, raw `Worker('~/x.js')`,
|
|
279
|
+
// dynamic-import-spawned workers, plugin-spawned workers, etc.
|
|
280
|
+
//
|
|
281
|
+
// 2. FALLBACK — drain `globalThis.__NS_HMR_WORKERS__` (a Set populated
|
|
282
|
+
// by `__nsHmrTrackWorker`, the helper that `workerHmrUrlPlugin`
|
|
283
|
+
// injects at the top of every dev module containing a
|
|
284
|
+
// `new Worker(new URL(...))` call). This keeps HMR worker cleanup
|
|
285
|
+
// working on older runtimes that don't yet expose
|
|
286
|
+
// `__nsTerminateAllWorkers`. It only catches workers spawned via
|
|
287
|
+
// the Vite-rewritten path, so the runtime API is strictly better
|
|
288
|
+
// coverage.
|
|
289
|
+
//
|
|
290
|
+
// Either way: producer-side wraps are still useful as diagnostic
|
|
291
|
+
// metadata (which user code spawned which worker), so we always clear
|
|
292
|
+
// the JS Set after termination — irrespective of which tier ran — so
|
|
293
|
+
// it doesn't grow unbounded across HMR cycles.
|
|
294
|
+
//
|
|
295
|
+
// Tolerant of:
|
|
296
|
+
// * Missing globals — non-worker apps have neither global; we no-op.
|
|
297
|
+
// * `terminate()` throwing — the runtime can throw on already-dead
|
|
298
|
+
// workers; we catch per-entry so subsequent terminations and the
|
|
299
|
+
// reboot itself still proceed.
|
|
300
|
+
// * Native API throwing — wrapped in try/catch so a runtime regression
|
|
301
|
+
// can't take down the HMR cycle; we degrade to the JS-Set fallback.
|
|
302
|
+
function terminateTrackedWorkers(g, options) {
|
|
303
|
+
let nativeApi;
|
|
304
|
+
try {
|
|
305
|
+
const candidate = g && g.__nsTerminateAllWorkers;
|
|
306
|
+
if (typeof candidate === 'function') {
|
|
307
|
+
nativeApi = candidate;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// Reading the global threw (extremely defensive — `globalThis`
|
|
312
|
+
// access shouldn't, but Proxy-wrapped globals can).
|
|
313
|
+
}
|
|
314
|
+
let nativeTerminated = null;
|
|
315
|
+
if (nativeApi) {
|
|
316
|
+
try {
|
|
317
|
+
const result = nativeApi.call(g);
|
|
318
|
+
// Native API returns the count of workers terminated as a
|
|
319
|
+
// number. Older betas may return undefined; treat any
|
|
320
|
+
// non-number as "ran successfully, count unknown".
|
|
321
|
+
nativeTerminated = typeof result === 'number' ? result : 0;
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
if (options.verbose && envVerbose) {
|
|
325
|
+
console.warn('[ns-hmr-diag][client] __nsTerminateAllWorkers threw; falling back to JS-tracked Set:', err?.message ?? err);
|
|
326
|
+
}
|
|
327
|
+
nativeApi = undefined; // force fallback below
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Always touch the JS-tracked Set so producer-side bookkeeping
|
|
331
|
+
// doesn't outlive its workers across HMR cycles. If the native API
|
|
332
|
+
// already terminated everything, this is just a `.clear()`. If the
|
|
333
|
+
// native API isn't available (older runtime), this is the actual
|
|
334
|
+
// cleanup path.
|
|
335
|
+
let workers;
|
|
336
|
+
try {
|
|
337
|
+
workers = g && g.__NS_HMR_WORKERS__;
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// fall through with `workers === undefined`
|
|
341
|
+
}
|
|
342
|
+
let fallbackTerminated = 0;
|
|
343
|
+
let fallbackFailed = 0;
|
|
344
|
+
const fallbackTotal = workers && typeof workers.size === 'number' ? workers.size : 0;
|
|
345
|
+
if (!nativeApi && workers && fallbackTotal > 0) {
|
|
346
|
+
// No native API — drain the JS-tracked Set as the primary path.
|
|
347
|
+
for (const worker of workers) {
|
|
348
|
+
try {
|
|
349
|
+
if (worker && typeof worker.terminate === 'function') {
|
|
350
|
+
worker.terminate();
|
|
351
|
+
fallbackTerminated++;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
fallbackFailed++;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (workers) {
|
|
360
|
+
try {
|
|
361
|
+
workers.clear();
|
|
362
|
+
}
|
|
363
|
+
catch { }
|
|
364
|
+
}
|
|
365
|
+
// Surface ONE concise log per HMR cycle when there was actually work
|
|
366
|
+
// to do. This isn't gated on `verbose` so the user can see in the
|
|
367
|
+
// dev terminal which cleanup tier ran without flipping a flag —
|
|
368
|
+
// it's a single line per HMR-cycle-with-workers, never per cycle
|
|
369
|
+
// when no workers exist. Filter on "actually did something" so we
|
|
370
|
+
// stay quiet on no-worker apps and on cycles where neither tier
|
|
371
|
+
// found anything.
|
|
372
|
+
if (nativeApi && (nativeTerminated ?? 0) > 0) {
|
|
373
|
+
console.info(`[ns-hmr][workers] terminated ${nativeTerminated} via runtime __nsTerminateAllWorkers before reboot`);
|
|
374
|
+
}
|
|
375
|
+
else if (!nativeApi && fallbackTerminated > 0) {
|
|
376
|
+
console.info(`[ns-hmr][workers] terminated ${fallbackTerminated}/${fallbackTotal} via JS-tracked Set fallback before reboot (older runtime; consider upgrading @nativescript/ios for native API)`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function invalidateModules(urls, verbose) {
|
|
380
|
+
if (!urls || !urls.length)
|
|
381
|
+
return false;
|
|
382
|
+
const g = globalThis;
|
|
383
|
+
const fn = g.__nsInvalidateModules;
|
|
384
|
+
if (typeof fn !== 'function') {
|
|
385
|
+
console.warn(`[ns-hmr-diag][client] runtime missing __nsInvalidateModules; falling back to legacy URL versioning. evict=${urls.length}`);
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
if (verbose && envVerbose) {
|
|
390
|
+
console.info(`[ns-hmr-diag][client] invalidateModules calling __nsInvalidateModules urls=${urls.length}`);
|
|
391
|
+
}
|
|
392
|
+
fn.call(null, urls);
|
|
393
|
+
if (verbose && envVerbose) {
|
|
394
|
+
console.info(`[ns-hmr-diag][client] invalidateModules OK urls=${urls.length}`);
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
// Real exception path — the runtime hook itself threw. Always
|
|
400
|
+
// surfaced for the same reason as the missing-hook warn above.
|
|
401
|
+
console.warn('[ns-hmr-diag][client] __nsInvalidateModules threw', error?.message || error);
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Eviction-set size cap for the kickstart.
|
|
406
|
+
//
|
|
407
|
+
// `__NS_HMR_KICKSTART_MAX_URLS__` is a build-time literal injected
|
|
408
|
+
// by `helpers/global-defines.ts` (default 32). It is a pure
|
|
409
|
+
// performance gate: the kickstart never affects correctness, so
|
|
410
|
+
// skipping it for large fan-out edits simply reverts the runtime to
|
|
411
|
+
// sequential `HttpFetchText`. See the doc comment on
|
|
412
|
+
// `resolveHmrKickstartMaxUrls` (in global-defines.ts) for the
|
|
413
|
+
// rationale and the empirical numbers behind the default.
|
|
414
|
+
//
|
|
415
|
+
// The constant is read once at module load (the same pattern used
|
|
416
|
+
// for `overlayEnabled`) so each call site is a single inequality
|
|
417
|
+
// check rather than a try/typeof. Tests that re-import this module
|
|
418
|
+
// see `undefined` and fall back to the default.
|
|
419
|
+
const KICKSTART_DEFAULT_MAX_URLS = 32;
|
|
420
|
+
const kickstartMaxUrls = (() => {
|
|
421
|
+
try {
|
|
422
|
+
const raw = __NS_HMR_KICKSTART_MAX_URLS__;
|
|
423
|
+
if (typeof raw !== 'number')
|
|
424
|
+
return KICKSTART_DEFAULT_MAX_URLS;
|
|
425
|
+
if (!Number.isFinite(raw))
|
|
426
|
+
return Number.POSITIVE_INFINITY;
|
|
427
|
+
if (raw < 0)
|
|
428
|
+
return KICKSTART_DEFAULT_MAX_URLS;
|
|
429
|
+
return Math.floor(raw);
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return KICKSTART_DEFAULT_MAX_URLS;
|
|
433
|
+
}
|
|
434
|
+
})();
|
|
435
|
+
// Decide whether to run the kickstart for a given eviction set.
|
|
436
|
+
// Exported for unit testing — callers in this module use the inline
|
|
437
|
+
// `evictPaths.length` comparison below for a slightly cheaper
|
|
438
|
+
// hot-path.
|
|
439
|
+
//
|
|
440
|
+
// Semantics:
|
|
441
|
+
// - `0` urls → no work to do; skip (returns false).
|
|
442
|
+
// - `urls.length <= cap` → kickstart eligible (returns true).
|
|
443
|
+
// - `urls.length > cap` → skip kickstart (returns false). V8 falls
|
|
444
|
+
// back to per-module synchronous fetches inside its module walk.
|
|
445
|
+
// - `cap === 0` → kickstart disabled regardless of size.
|
|
446
|
+
// - `cap === Infinity` → kickstart always eligible (no cap).
|
|
447
|
+
export function shouldRunKickstart(urlCount, maxUrls = kickstartMaxUrls) {
|
|
448
|
+
if (!Number.isFinite(urlCount) || urlCount <= 0)
|
|
449
|
+
return false;
|
|
450
|
+
if (!Number.isFinite(maxUrls))
|
|
451
|
+
return maxUrls > 0;
|
|
452
|
+
if (maxUrls <= 0)
|
|
453
|
+
return false;
|
|
454
|
+
return urlCount <= maxUrls;
|
|
455
|
+
}
|
|
456
|
+
function kickstartHmrPrefetch(urls, verbose) {
|
|
457
|
+
if (!urls || !urls.length)
|
|
458
|
+
return null;
|
|
459
|
+
const g = globalThis;
|
|
460
|
+
const fn = g.__nsKickstartHmrPrefetch;
|
|
461
|
+
if (typeof fn !== 'function') {
|
|
462
|
+
if (verbose && envVerbose) {
|
|
463
|
+
console.info(`[hmr-angular] runtime missing __nsKickstartHmrPrefetch; serial fetches will be used. urls=${urls.length}`);
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
// Concurrency cap of 16 matches the runtime's documented
|
|
469
|
+
// default for the kickstart BFS. Timeout at 10s tracks the
|
|
470
|
+
// runtime's `HttpFetchText` retry envelope so we don't
|
|
471
|
+
// hold the JS thread forever on a stalled dev server.
|
|
472
|
+
const result = fn.call(null, urls.slice(), { maxConcurrent: 16, timeoutMs: 10000 });
|
|
473
|
+
if (result && typeof result === 'object') {
|
|
474
|
+
const ok = !!result.ok;
|
|
475
|
+
const fetched = typeof result.fetched === 'number' ? result.fetched : 0;
|
|
476
|
+
const ms = typeof result.ms === 'number' ? result.ms : 0;
|
|
477
|
+
return { ok, fetched, ms };
|
|
478
|
+
}
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
if (verbose && envVerbose) {
|
|
483
|
+
console.warn('[hmr-angular] __nsKickstartHmrPrefetch threw', error?.message || error);
|
|
484
|
+
}
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function getAngularBootstrapEntryCandidates(msg) {
|
|
489
|
+
const root = typeof __NS_APP_ROOT_VIRTUAL__ === 'string' && __NS_APP_ROOT_VIRTUAL__ ? __NS_APP_ROOT_VIRTUAL__ : '/src';
|
|
490
|
+
// Server announces the canonical bootstrap entry as
|
|
491
|
+
// `importerEntry`, computed from `package.json#main`. Fall back to
|
|
492
|
+
// the legacy `entryCandidates` array (for older servers) and finally
|
|
493
|
+
// to a hard-coded list. All candidates must be project-relative
|
|
494
|
+
// posix paths (e.g. `/src/main.ts`) so they slot directly behind
|
|
495
|
+
// `${origin}/ns/m`.
|
|
496
|
+
const explicit = typeof msg?.importerEntry === 'string' && msg.importerEntry.startsWith('/') ? [msg.importerEntry] : [];
|
|
497
|
+
const legacy = Array.isArray(msg?.entryCandidates) && msg.entryCandidates.length ? msg.entryCandidates : [];
|
|
498
|
+
const fallback = [`${root}/main.ts`, `${root}/app.ts`];
|
|
499
|
+
const merged = [...explicit, ...legacy, ...fallback];
|
|
500
|
+
const seen = new Set();
|
|
501
|
+
const result = [];
|
|
502
|
+
for (const candidate of merged) {
|
|
503
|
+
if (typeof candidate !== 'string' || !candidate.startsWith('/'))
|
|
504
|
+
continue;
|
|
505
|
+
if (seen.has(candidate))
|
|
506
|
+
continue;
|
|
507
|
+
seen.add(candidate);
|
|
508
|
+
result.push(candidate);
|
|
509
|
+
}
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
async function importAngularBootstrapEntry(url) {
|
|
513
|
+
const g = globalThis;
|
|
514
|
+
if (typeof g.__NS_HMR_IMPORT__ === 'function') {
|
|
515
|
+
return g.__NS_HMR_IMPORT__(url);
|
|
516
|
+
}
|
|
517
|
+
return import(/* @vite-ignore */ url);
|
|
518
|
+
}
|
|
519
|
+
export async function refreshAngularBootstrapOptions(msg, options) {
|
|
520
|
+
const g = globalThis;
|
|
521
|
+
if (typeof g.__NS_UPDATE_ANGULAR_APP_OPTIONS__ !== 'function') {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const originSource = typeof msg?.origin === 'string' && /^https?:\/\//.test(msg.origin) ? msg.origin : g.__NS_HTTP_ORIGIN__;
|
|
525
|
+
if (typeof originSource !== 'string' || !/^https?:\/\//.test(originSource)) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const origin = originSource.replace(/\/$/, '');
|
|
529
|
+
// Explicit eviction set takes precedence over URL versioning. The
|
|
530
|
+
// server walks the inverse-dep closure of the changed file
|
|
531
|
+
// (collectAngularEvictionUrls) and emits canonical /ns/m/<rel> URLs
|
|
532
|
+
// in `evictPaths`. We hand the list to the runtime before
|
|
533
|
+
// re-importing the entry; the runtime drops those entries from
|
|
534
|
+
// `g_moduleRegistry` so V8's subsequent `import(entry)` walks the
|
|
535
|
+
// dep graph and re-fetches ONLY those modules. Everything else stays
|
|
536
|
+
// hot in the cache.
|
|
537
|
+
const evictPaths = Array.isArray(msg?.evictPaths) ? msg.evictPaths.filter((u) => typeof u === 'string' && /^https?:\/\//.test(u)) : [];
|
|
538
|
+
// Diagnostic: log eviction set client-side so we can verify what
|
|
539
|
+
// the runtime is being asked to drop. Up to 32 entries are sampled
|
|
540
|
+
// so large constants edits don't swamp the console. Gated behind
|
|
541
|
+
// the standard `__NS_ENV_VERBOSE__` build define (`NS_VITE_VERBOSE`)
|
|
542
|
+
// so the lines stay silent on normal saves.
|
|
543
|
+
if (options.verbose && envVerbose) {
|
|
544
|
+
const _path = typeof msg?.path === 'string' ? msg.path : '(unknown)';
|
|
545
|
+
console.info(`[ns-hmr-diag][client] received ns:angular-update path=${_path} evictPaths.length=${evictPaths.length} importerEntry=${msg?.importerEntry ?? '(none)'}`);
|
|
546
|
+
if (evictPaths.length) {
|
|
547
|
+
const sample = evictPaths.slice(0, 32);
|
|
548
|
+
console.info(`[ns-hmr-diag][client] evictPaths firstN=`, sample);
|
|
549
|
+
if (evictPaths.length > sample.length) {
|
|
550
|
+
console.info(`[ns-hmr-diag][client] evictPaths hidden=${evictPaths.length - sample.length}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// Drive the apply-progress overlay through the 'evicting' frame
|
|
555
|
+
// *before* the runtime invalidate call so the user sees the count
|
|
556
|
+
// even on the cheapest stage. Eviction count also doubles as a
|
|
557
|
+
// useful debug breadcrumb in the overlay's detail line — large
|
|
558
|
+
// counts (e.g. constants edits → 100+ importers) explain why a
|
|
559
|
+
// cycle takes longer than an HTML edit.
|
|
560
|
+
setUpdateOverlayStage('evicting', {
|
|
561
|
+
detail: evictPaths.length ? `Invalidating ${evictPaths.length} module${evictPaths.length === 1 ? '' : 's'}` : 'Invalidating module cache',
|
|
562
|
+
});
|
|
563
|
+
const evicted = invalidateModules(evictPaths, options.verbose);
|
|
564
|
+
if (options.verbose && envVerbose) {
|
|
565
|
+
console.info(`[ns-hmr-diag][client] evict count=${evictPaths.length} ok=${evicted ? 'yes' : 'no'}`);
|
|
566
|
+
}
|
|
567
|
+
// Parallel HTTP prefetch for the freshly-evicted modules.
|
|
568
|
+
//
|
|
569
|
+
// Order matters here: the kickstart MUST run after the eviction so
|
|
570
|
+
// that it actually re-populates `g_prefetchCache` for the modules
|
|
571
|
+
// V8 will ask for. If we kickstarted before the eviction, every URL
|
|
572
|
+
// would skip on the "already cached" check (the walk consumes
|
|
573
|
+
// destructively but the prior non-evicted modules sit in V8's
|
|
574
|
+
// `g_moduleRegistry`, not the prefetch cache).
|
|
575
|
+
//
|
|
576
|
+
// We only kickstart when the eviction succeeded, because the
|
|
577
|
+
// fallback path (legacy URL-versioning, see `useStableUrls = evicted`
|
|
578
|
+
// below) doesn't go through canonical /ns/m URLs and the runtime
|
|
579
|
+
// would not match its prefetch cache lookups against the version-
|
|
580
|
+
// prefixed forms V8 will end up requesting. Better to skip the
|
|
581
|
+
// optimization than risk a confusing partial cache hit.
|
|
582
|
+
//
|
|
583
|
+
// `shouldRunKickstart` gates large eviction sets out of the
|
|
584
|
+
// parallel wave. A `.ts` file with deep inverse-dep fan-in
|
|
585
|
+
// (constants files, design-system enums) can produce a
|
|
586
|
+
// 200–300-URL closure that overwhelms Vite's single-threaded
|
|
587
|
+
// transform pipeline. The kickstart fan-out then makes the cycle
|
|
588
|
+
// 5–8× slower than letting V8 fetch sequentially as it walks the
|
|
589
|
+
// real forward path. The cap (default 32, override with
|
|
590
|
+
// `NS_VITE_KICKSTART_MAX_URLS`) keeps component-shaped closures
|
|
591
|
+
// (typically 5–30 URLs) on the fast path. Correctness is
|
|
592
|
+
// unaffected — V8's per-module synchronous fetch is the same fall-
|
|
593
|
+
// back code path the runtime has always used.
|
|
594
|
+
if (evicted && evictPaths.length > 0) {
|
|
595
|
+
if (shouldRunKickstart(evictPaths.length)) {
|
|
596
|
+
const result = kickstartHmrPrefetch(evictPaths, options.verbose);
|
|
597
|
+
if (options.verbose && envVerbose && result) {
|
|
598
|
+
console.info(`[ns-hmr][angular] kickstart urls=${evictPaths.length} fetched=${result.fetched} ok=${result.ok ? 'yes' : 'no'} ms=${result.ms}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
else if (options.verbose && envVerbose) {
|
|
602
|
+
console.info(`[ns-hmr][angular] kickstart skipped urls=${evictPaths.length} cap=${Number.isFinite(kickstartMaxUrls) ? kickstartMaxUrls : 'Infinity'} (eviction set too large; falling back to sequential fetch)`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// URL strategy:
|
|
606
|
+
//
|
|
607
|
+
// * `evicted=true` → modern runtime accepted the eviction; we
|
|
608
|
+
// re-import the entry under its STABLE canonical URL. V8's
|
|
609
|
+
// registry no longer holds the evicted modules so the import
|
|
610
|
+
// triggers fresh fetches for them; the rest of the graph is a
|
|
611
|
+
// cache hit.
|
|
612
|
+
// * `evicted=false` → either we have no eviction set (older server)
|
|
613
|
+
// or the runtime lacks `__nsInvalidateModules` (older runtime).
|
|
614
|
+
// Fall back to the legacy `__ns_hmr__/v<version>/` URL pattern so
|
|
615
|
+
// V8 sees a fresh URL and re-fetches the entry. The runtime
|
|
616
|
+
// canonicalizer collapses the path back to the stable key, which
|
|
617
|
+
// keeps cache identity consistent across saves.
|
|
618
|
+
const versionRaw = typeof msg?.version === 'number' ? msg.version : 0;
|
|
619
|
+
const useStableUrls = evicted;
|
|
620
|
+
let lastError;
|
|
621
|
+
// 'reimporting' is the entry point for the long tail of an HMR
|
|
622
|
+
// cycle: V8 walks the freshly-evicted graph and the iOS runtime
|
|
623
|
+
// re-fetches each node from the dev server. We announce the stage
|
|
624
|
+
// once, before the loop, so the user sees a progress jump even when
|
|
625
|
+
// the import resolves on the first candidate (the common case).
|
|
626
|
+
setUpdateOverlayStage('reimporting', {
|
|
627
|
+
detail: 'Re-importing Angular bootstrap entry',
|
|
628
|
+
});
|
|
629
|
+
for (const entry of getAngularBootstrapEntryCandidates(msg)) {
|
|
630
|
+
const url = useStableUrls ? `${origin}/ns/m${entry}` : versionRaw ? `${origin}/ns/m/__ns_hmr__/v${versionRaw}${entry}` : `${origin}/ns/m${entry}`;
|
|
631
|
+
try {
|
|
632
|
+
g.__NS_ANGULAR_HMR_REGISTER_ONLY__ = true;
|
|
633
|
+
await importAngularBootstrapEntry(url);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
catch (error) {
|
|
637
|
+
lastError = error;
|
|
638
|
+
}
|
|
639
|
+
finally {
|
|
640
|
+
g.__NS_ANGULAR_HMR_REGISTER_ONLY__ = false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (options.verbose && envVerbose && lastError) {
|
|
644
|
+
console.warn('[hmr-angular] failed to refresh Angular bootstrap entry', lastError?.message || lastError);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
export async function handleAngularHotUpdateMessage(msg, options) {
|
|
15
648
|
if (!msg || msg.type !== 'ns:angular-update') {
|
|
16
649
|
return false;
|
|
17
650
|
}
|
|
651
|
+
// Cycle mutex. See `inFlightHmrCycle` for why this is necessary. We
|
|
652
|
+
// publish `thisCycle` *before* awaiting the previous one so a third
|
|
653
|
+
// concurrent message sees the latest in-flight, not the stale
|
|
654
|
+
// previous. This serializes N concurrent updates into a single FIFO
|
|
655
|
+
// chain.
|
|
656
|
+
const previousCycle = inFlightHmrCycle;
|
|
657
|
+
let resolveCycle;
|
|
658
|
+
const thisCycle = new Promise((resolve) => {
|
|
659
|
+
resolveCycle = resolve;
|
|
660
|
+
});
|
|
661
|
+
inFlightHmrCycle = thisCycle;
|
|
662
|
+
if (previousCycle) {
|
|
663
|
+
try {
|
|
664
|
+
await previousCycle;
|
|
665
|
+
}
|
|
666
|
+
catch { }
|
|
667
|
+
}
|
|
668
|
+
// Single-line log lands in iOS device console as `CONSOLE INFO` so the user can
|
|
669
|
+
// correlate with the server's `[hmr-ws][update] ...` line and see
|
|
670
|
+
// where the save's wall time actually goes (refresh vs. reboot).
|
|
671
|
+
const t0 = Date.now();
|
|
672
|
+
let tAfterRefresh = t0;
|
|
673
|
+
let tEnd = t0;
|
|
674
|
+
const filePath = typeof msg?.path === 'string' ? msg.path : '<unknown>';
|
|
675
|
+
const emitTiming = (ok, errorMessage) => {
|
|
676
|
+
const refreshMs = Math.max(0, tAfterRefresh - t0);
|
|
677
|
+
const rebootMs = Math.max(0, tEnd - tAfterRefresh);
|
|
678
|
+
const totalMs = Math.max(0, tEnd - t0);
|
|
679
|
+
const status = ok ? 'ok' : 'FAILED';
|
|
680
|
+
const suffix = errorMessage ? ` error=${errorMessage}` : '';
|
|
681
|
+
console.info(`[ns-hmr][angular] ${status} file=${filePath} refresh=${refreshMs}ms reboot=${rebootMs}ms total=${totalMs}ms${suffix}`);
|
|
682
|
+
};
|
|
683
|
+
// Show the apply-progress overlay as soon as the
|
|
684
|
+
// mutex unblocks us. Posting this *after* the awaited
|
|
685
|
+
// previousCycle keeps each cycle's stages visually grouped in the
|
|
686
|
+
// overlay (cycle-1 finishes its 'complete' before cycle-2's
|
|
687
|
+
// 'received' overrides it).
|
|
688
|
+
//
|
|
689
|
+
// The detail line surfaces the changed file path so the user can
|
|
690
|
+
// confirm at a glance which save the overlay is reflecting.
|
|
691
|
+
const overlayDetail = filePath && filePath !== '<unknown>' ? `Updating ${filePath}` : 'Preparing update';
|
|
692
|
+
setUpdateOverlayStage('received', { detail: overlayDetail });
|
|
18
693
|
try {
|
|
19
694
|
const g = globalThis;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
695
|
+
// Vite-spec entry: `vite:beforeUpdate` fires as soon as the
|
|
696
|
+
// client receives an update message, BEFORE any work begins
|
|
697
|
+
// (refresh, dispose, reboot). User listeners can use this to
|
|
698
|
+
// pause animations, cancel in-flight requests, etc. Payload
|
|
699
|
+
// matches Vite's `Update[]` shape loosely — `path` is the
|
|
700
|
+
// canonical changed file, `evictPaths` is the eviction
|
|
701
|
+
// closure the server already computed.
|
|
702
|
+
const updatePayload = {
|
|
703
|
+
type: 'js-update',
|
|
704
|
+
path: typeof msg?.path === 'string' ? msg.path : null,
|
|
705
|
+
evictPaths: Array.isArray(msg?.evictPaths) ? msg.evictPaths : [],
|
|
706
|
+
timestamp: t0,
|
|
707
|
+
};
|
|
708
|
+
dispatchHotEvent(g, 'vite:beforeUpdate', { updates: [updatePayload] });
|
|
709
|
+
// Declined-module short-circuit. If any updated module called
|
|
710
|
+
// `import.meta.hot.decline()`, we MUST do a full reload per
|
|
711
|
+
// Vite spec rather than the hot-update path. `triggerFullReload`
|
|
712
|
+
// also dispatches `vite:beforeFullReload`. The reload tears the
|
|
713
|
+
// JS realm down so we don't need to do any further bookkeeping
|
|
714
|
+
// here — return early.
|
|
715
|
+
if (checkDeclinedAndReload(g, msg, options)) {
|
|
716
|
+
tEnd = Date.now();
|
|
717
|
+
emitTiming(true);
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
// Prefer __reboot_ng_modules__ — it properly disposes existing modules,
|
|
721
|
+
// re-bootstraps Angular inside NgZone, and sets the root view internally.
|
|
722
|
+
// __NS_ANGULAR_BOOTSTRAP__ is exposed as a secondary signal that the
|
|
723
|
+
// Angular HMR system is ready, but __reboot_ng_modules__ is the
|
|
724
|
+
// canonical reboot entry point.
|
|
725
|
+
const reboot = g.__reboot_ng_modules__;
|
|
726
|
+
if (typeof reboot === 'function') {
|
|
727
|
+
if (options.verbose && envVerbose) {
|
|
728
|
+
console.info('[ns-hmr-diag][client] calling __reboot_ng_modules__');
|
|
25
729
|
}
|
|
730
|
+
await refreshAngularBootstrapOptions(msg, options);
|
|
731
|
+
tAfterRefresh = Date.now();
|
|
732
|
+
try {
|
|
733
|
+
g.__NS_HMR_IMPORT_NONCE__ = (typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0) + 1;
|
|
734
|
+
}
|
|
735
|
+
catch { }
|
|
26
736
|
try {
|
|
27
737
|
g.__NS_DEV_RESET_IN_PROGRESS__ = true;
|
|
28
738
|
}
|
|
29
739
|
catch { }
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
740
|
+
// Two-phase pre-reboot cleanup. Order matters: dispose runs
|
|
741
|
+
// FIRST so user-code disposers see a still-live runtime
|
|
742
|
+
// (sockets, workers, etc.) and can issue graceful "I'm
|
|
743
|
+
// going away" messages; then we hard-terminate any worker
|
|
744
|
+
// the user didn't manually clean up.
|
|
745
|
+
//
|
|
746
|
+
// Phase 1 — `import.meta.hot.dispose(cb)` callbacks
|
|
747
|
+
// ────────────────────────────────────────────────
|
|
748
|
+
// The NS iOS runtime exposes
|
|
749
|
+
// `globalThis.__nsRunHmrDispose()` (registered in
|
|
750
|
+
// `Worker.mm`'s sibling `HMRSupport.mm`). It walks
|
|
751
|
+
// `g_hotDispose` — the per-module callback registry that
|
|
752
|
+
// `import.meta.hot.dispose(cb)` populates — and invokes
|
|
753
|
+
// every callback with that module's `hot.data` object,
|
|
754
|
+
// matching the Vite spec. Soft-fails on older runtimes
|
|
755
|
+
// without the API (call is optional-chained).
|
|
756
|
+
//
|
|
757
|
+
// This is the standards-compliant cleanup hook every Vite
|
|
758
|
+
// user already knows. Apps register their intervals,
|
|
759
|
+
// listeners, sockets, store subscriptions, etc. via
|
|
760
|
+
// `import.meta.hot.dispose(...)` and they're guaranteed to
|
|
761
|
+
// fire before the next Angular reboot — no NS-specific
|
|
762
|
+
// knowledge required.
|
|
763
|
+
runHmrDisposeCallbacks(g, options);
|
|
764
|
+
// Phase 2 — worker auto-terminate (defence in depth)
|
|
765
|
+
// ───────────────────────────────────────────────────
|
|
766
|
+
// Even with perfect dispose() coverage, workers spawned
|
|
767
|
+
// from constructors that re-run on Angular reboot (e.g.
|
|
768
|
+
// `new Worker(...)` inside `AppComponent`'s constructor)
|
|
769
|
+
// don't fire dispose for `app.component.ts` when only
|
|
770
|
+
// `login.component.html` changes — `app.component.ts`
|
|
771
|
+
// isn't being replaced, so its disposers don't run per
|
|
772
|
+
// Vite spec. The runtime API
|
|
773
|
+
// `__nsTerminateAllWorkers()` doesn't care about module
|
|
774
|
+
// boundaries; it just kills every live worker in
|
|
775
|
+
// `Caches::Workers`. Falls back to the
|
|
776
|
+
// `__NS_HMR_WORKERS__` JS Set on older runtimes.
|
|
777
|
+
terminateTrackedWorkers(g, options);
|
|
778
|
+
// 'rebooting' marks the long tail of the cycle: NgZone
|
|
779
|
+
// teardown, module re-instantiation, and resetRootView.
|
|
780
|
+
// The detail line shows refresh-so-far ms so a user can
|
|
781
|
+
// already see if the slow phase is re-import or reboot.
|
|
782
|
+
setUpdateOverlayStage('rebooting', {
|
|
783
|
+
detail: `Re-bootstrapping Angular (refresh ${Math.max(0, tAfterRefresh - t0)}ms)`,
|
|
784
|
+
});
|
|
785
|
+
try {
|
|
786
|
+
reboot(true);
|
|
787
|
+
}
|
|
788
|
+
finally {
|
|
789
|
+
try {
|
|
790
|
+
g.__NS_DEV_RESET_IN_PROGRESS__ = false;
|
|
791
|
+
}
|
|
792
|
+
catch { }
|
|
793
|
+
}
|
|
794
|
+
tEnd = Date.now();
|
|
795
|
+
emitTiming(true);
|
|
796
|
+
// Vite-spec exit: `vite:afterUpdate` fires after the update
|
|
797
|
+
// has been successfully applied. User code can re-attach
|
|
798
|
+
// any state torn down by `vite:beforeUpdate` (resume
|
|
799
|
+
// animations, re-fetch, etc.). Same payload shape as
|
|
800
|
+
// `vite:beforeUpdate` so listeners can correlate.
|
|
801
|
+
dispatchHotEvent(g, 'vite:afterUpdate', { updates: [updatePayload] });
|
|
802
|
+
// 'complete' surfaces the wall-clock total so a user gets a
|
|
803
|
+
// single-glance confirmation matching the [ns-hmr][angular]
|
|
804
|
+
// log line in the terminal. The overlay auto-hides shortly
|
|
805
|
+
// after.
|
|
806
|
+
setUpdateOverlayStage('complete', {
|
|
807
|
+
detail: `Total ${Math.max(0, tEnd - t0)}ms`,
|
|
42
808
|
});
|
|
43
809
|
return true;
|
|
44
810
|
}
|
|
45
|
-
if (options.verbose &&
|
|
46
|
-
console.warn('[hmr-angular]
|
|
811
|
+
if (options.verbose && envVerbose) {
|
|
812
|
+
console.warn('[hmr-angular] No Angular HMR handler found. Ensure runNativeScriptAngularApp() has been called.');
|
|
47
813
|
}
|
|
814
|
+
tEnd = Date.now();
|
|
815
|
+
emitTiming(false, 'no-reboot-handler');
|
|
816
|
+
// No reboot handler → we can't apply the update; hide the
|
|
817
|
+
// overlay rather than leaving stale progress on screen.
|
|
818
|
+
hideUpdateOverlay();
|
|
48
819
|
}
|
|
49
820
|
catch (error) {
|
|
50
821
|
if (options.verbose) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
822
|
+
console.warn('[hmr-angular] failed to handle update', (error && error.message) || error);
|
|
823
|
+
}
|
|
824
|
+
tEnd = Date.now();
|
|
825
|
+
const errorMessage = (error && error.message) || String(error);
|
|
826
|
+
emitTiming(false, errorMessage);
|
|
827
|
+
// Vite-spec error event — fires on any HMR cycle that throws.
|
|
828
|
+
// Payload shape mirrors Vite's `ErrorPayload`: `err: { message,
|
|
829
|
+
// stack? }` plus optional `path`. Soft-fails if no listeners.
|
|
830
|
+
dispatchHotEvent(globalThis, 'vite:error', {
|
|
831
|
+
type: 'error',
|
|
832
|
+
err: {
|
|
833
|
+
message: errorMessage,
|
|
834
|
+
stack: (error && error.stack) || undefined,
|
|
835
|
+
},
|
|
836
|
+
path: typeof msg?.path === 'string' ? msg.path : undefined,
|
|
837
|
+
});
|
|
838
|
+
// Errors → drop the overlay so the user isn't left looking
|
|
839
|
+
// at indefinite progress; the underlying error is already
|
|
840
|
+
// logged above (and surfaced via Vite's standard error
|
|
841
|
+
// pipeline through the websocket).
|
|
842
|
+
hideUpdateOverlay();
|
|
843
|
+
}
|
|
844
|
+
finally {
|
|
845
|
+
try {
|
|
846
|
+
resolveCycle();
|
|
847
|
+
}
|
|
848
|
+
catch { }
|
|
849
|
+
if (inFlightHmrCycle === thisCycle) {
|
|
850
|
+
inFlightHmrCycle = null;
|
|
55
851
|
}
|
|
56
852
|
}
|
|
57
853
|
return true;
|