@nativescript/vite 8.0.0-alpha.2 → 8.0.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/configuration/angular.d.ts +34 -1
  2. package/configuration/angular.js +380 -34
  3. package/configuration/angular.js.map +1 -1
  4. package/configuration/base.js +171 -7
  5. package/configuration/base.js.map +1 -1
  6. package/configuration/solid.js +27 -1
  7. package/configuration/solid.js.map +1 -1
  8. package/configuration/typescript.js +1 -1
  9. package/configuration/typescript.js.map +1 -1
  10. package/helpers/angular/angular-linker.js +3 -12
  11. package/helpers/angular/angular-linker.js.map +1 -1
  12. package/helpers/angular/inject-component-hmr-registration.d.ts +112 -0
  13. package/helpers/angular/inject-component-hmr-registration.js +359 -0
  14. package/helpers/angular/inject-component-hmr-registration.js.map +1 -0
  15. package/helpers/angular/inject-hmr-vite-ignore.d.ts +75 -0
  16. package/helpers/angular/inject-hmr-vite-ignore.js +288 -0
  17. package/helpers/angular/inject-hmr-vite-ignore.js.map +1 -0
  18. package/helpers/angular/util.d.ts +1 -0
  19. package/helpers/angular/util.js +88 -0
  20. package/helpers/angular/util.js.map +1 -1
  21. package/helpers/commonjs-plugins.d.ts +5 -2
  22. package/helpers/commonjs-plugins.js +126 -0
  23. package/helpers/commonjs-plugins.js.map +1 -1
  24. package/helpers/config-as-json.js +10 -0
  25. package/helpers/config-as-json.js.map +1 -1
  26. package/helpers/dev-host.d.ts +274 -0
  27. package/helpers/dev-host.js +491 -0
  28. package/helpers/dev-host.js.map +1 -0
  29. package/helpers/global-defines.d.ts +51 -0
  30. package/helpers/global-defines.js +77 -0
  31. package/helpers/global-defines.js.map +1 -1
  32. package/helpers/logging.d.ts +1 -0
  33. package/helpers/logging.js +63 -3
  34. package/helpers/logging.js.map +1 -1
  35. package/helpers/main-entry.d.ts +3 -1
  36. package/helpers/main-entry.js +450 -125
  37. package/helpers/main-entry.js.map +1 -1
  38. package/helpers/nativeclass-transformer-plugin.d.ts +9 -2
  39. package/helpers/nativeclass-transformer-plugin.js +157 -14
  40. package/helpers/nativeclass-transformer-plugin.js.map +1 -1
  41. package/helpers/ns-core-url.d.ts +88 -0
  42. package/helpers/ns-core-url.js +191 -0
  43. package/helpers/ns-core-url.js.map +1 -0
  44. package/helpers/prelink-angular.js +1 -4
  45. package/helpers/prelink-angular.js.map +1 -1
  46. package/helpers/project.d.ts +35 -0
  47. package/helpers/project.js +120 -2
  48. package/helpers/project.js.map +1 -1
  49. package/helpers/resolver.js +9 -1
  50. package/helpers/resolver.js.map +1 -1
  51. package/helpers/solid-jsx-deps.d.ts +15 -0
  52. package/helpers/solid-jsx-deps.js +178 -0
  53. package/helpers/solid-jsx-deps.js.map +1 -0
  54. package/helpers/ts-config-paths.js +50 -2
  55. package/helpers/ts-config-paths.js.map +1 -1
  56. package/helpers/workers.d.ts +20 -19
  57. package/helpers/workers.js +620 -3
  58. package/helpers/workers.js.map +1 -1
  59. package/hmr/client/css-handler.d.ts +1 -0
  60. package/hmr/client/css-handler.js +34 -5
  61. package/hmr/client/css-handler.js.map +1 -1
  62. package/hmr/client/css-update-overlay.d.ts +18 -0
  63. package/hmr/client/css-update-overlay.js +27 -0
  64. package/hmr/client/css-update-overlay.js.map +1 -0
  65. package/hmr/client/hmr-pending-overlay.d.ts +27 -0
  66. package/hmr/client/hmr-pending-overlay.js +50 -0
  67. package/hmr/client/hmr-pending-overlay.js.map +1 -0
  68. package/hmr/client/index.js +483 -33
  69. package/hmr/client/index.js.map +1 -1
  70. package/hmr/client/utils.d.ts +5 -0
  71. package/hmr/client/utils.js +283 -12
  72. package/hmr/client/utils.js.map +1 -1
  73. package/hmr/client/vue-sfc-update-overlay.d.ts +82 -0
  74. package/hmr/client/vue-sfc-update-overlay.js +133 -0
  75. package/hmr/client/vue-sfc-update-overlay.js.map +1 -0
  76. package/hmr/entry-runtime.d.ts +2 -1
  77. package/hmr/entry-runtime.js +253 -66
  78. package/hmr/entry-runtime.js.map +1 -1
  79. package/hmr/frameworks/angular/client/index.d.ts +3 -1
  80. package/hmr/frameworks/angular/client/index.js +802 -10
  81. package/hmr/frameworks/angular/client/index.js.map +1 -1
  82. package/hmr/frameworks/angular/server/linker.js +1 -4
  83. package/hmr/frameworks/angular/server/linker.js.map +1 -1
  84. package/hmr/frameworks/angular/server/strategy.js +30 -6
  85. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  86. package/hmr/frameworks/typescript/server/strategy.js +8 -2
  87. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  88. package/hmr/frameworks/vue/client/index.js +18 -42
  89. package/hmr/frameworks/vue/client/index.js.map +1 -1
  90. package/hmr/helpers/ast-normalizer.js +52 -5
  91. package/hmr/helpers/ast-normalizer.js.map +1 -1
  92. package/hmr/helpers/cjs-named-exports.d.ts +23 -0
  93. package/hmr/helpers/cjs-named-exports.js +152 -0
  94. package/hmr/helpers/cjs-named-exports.js.map +1 -0
  95. package/hmr/helpers/package-exports.d.ts +16 -0
  96. package/hmr/helpers/package-exports.js +396 -0
  97. package/hmr/helpers/package-exports.js.map +1 -0
  98. package/hmr/server/constants.js +13 -4
  99. package/hmr/server/constants.js.map +1 -1
  100. package/hmr/server/core-sanitize.d.ts +93 -8
  101. package/hmr/server/core-sanitize.js +222 -49
  102. package/hmr/server/core-sanitize.js.map +1 -1
  103. package/hmr/server/import-map.js +80 -22
  104. package/hmr/server/import-map.js.map +1 -1
  105. package/hmr/server/index.d.ts +2 -1
  106. package/hmr/server/index.js.map +1 -1
  107. package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
  108. package/hmr/server/ns-core-cjs-shape.js +271 -0
  109. package/hmr/server/ns-core-cjs-shape.js.map +1 -0
  110. package/hmr/server/ns-rt-bridge.d.ts +51 -0
  111. package/hmr/server/ns-rt-bridge.js +131 -0
  112. package/hmr/server/ns-rt-bridge.js.map +1 -0
  113. package/hmr/server/perf-instrumentation.d.ts +114 -0
  114. package/hmr/server/perf-instrumentation.js +195 -0
  115. package/hmr/server/perf-instrumentation.js.map +1 -0
  116. package/hmr/server/runtime-graph-filter.d.ts +5 -0
  117. package/hmr/server/runtime-graph-filter.js +21 -0
  118. package/hmr/server/runtime-graph-filter.js.map +1 -0
  119. package/hmr/server/shared-transform-request.d.ts +12 -0
  120. package/hmr/server/shared-transform-request.js +144 -0
  121. package/hmr/server/shared-transform-request.js.map +1 -0
  122. package/hmr/server/vite-plugin.d.ts +21 -1
  123. package/hmr/server/vite-plugin.js +497 -58
  124. package/hmr/server/vite-plugin.js.map +1 -1
  125. package/hmr/server/websocket-angular-entry.d.ts +2 -0
  126. package/hmr/server/websocket-angular-entry.js +68 -0
  127. package/hmr/server/websocket-angular-entry.js.map +1 -0
  128. package/hmr/server/websocket-angular-hot-update.d.ts +78 -0
  129. package/hmr/server/websocket-angular-hot-update.js +413 -0
  130. package/hmr/server/websocket-angular-hot-update.js.map +1 -0
  131. package/hmr/server/websocket-core-bridge.d.ts +58 -0
  132. package/hmr/server/websocket-core-bridge.js +368 -0
  133. package/hmr/server/websocket-core-bridge.js.map +1 -0
  134. package/hmr/server/websocket-css-hot-update.d.ts +33 -0
  135. package/hmr/server/websocket-css-hot-update.js +65 -0
  136. package/hmr/server/websocket-css-hot-update.js.map +1 -0
  137. package/hmr/server/websocket-graph-upsert.d.ts +21 -0
  138. package/hmr/server/websocket-graph-upsert.js +33 -0
  139. package/hmr/server/websocket-graph-upsert.js.map +1 -0
  140. package/hmr/server/websocket-hmr-pending.d.ts +43 -0
  141. package/hmr/server/websocket-hmr-pending.js +55 -0
  142. package/hmr/server/websocket-hmr-pending.js.map +1 -0
  143. package/hmr/server/websocket-module-bindings.d.ts +6 -0
  144. package/hmr/server/websocket-module-bindings.js +471 -0
  145. package/hmr/server/websocket-module-bindings.js.map +1 -0
  146. package/hmr/server/websocket-module-specifiers.d.ts +101 -0
  147. package/hmr/server/websocket-module-specifiers.js +820 -0
  148. package/hmr/server/websocket-module-specifiers.js.map +1 -0
  149. package/hmr/server/websocket-ns-m-finalize.d.ts +22 -0
  150. package/hmr/server/websocket-ns-m-finalize.js +88 -0
  151. package/hmr/server/websocket-ns-m-finalize.js.map +1 -0
  152. package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
  153. package/hmr/server/websocket-ns-m-paths.js +92 -0
  154. package/hmr/server/websocket-ns-m-paths.js.map +1 -0
  155. package/hmr/server/websocket-ns-m-request.d.ts +45 -0
  156. package/hmr/server/websocket-ns-m-request.js +196 -0
  157. package/hmr/server/websocket-ns-m-request.js.map +1 -0
  158. package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
  159. package/hmr/server/websocket-served-module-helpers.js +644 -0
  160. package/hmr/server/websocket-served-module-helpers.js.map +1 -0
  161. package/hmr/server/websocket-txn.d.ts +6 -0
  162. package/hmr/server/websocket-txn.js +45 -0
  163. package/hmr/server/websocket-txn.js.map +1 -0
  164. package/hmr/server/websocket-vendor-unifier.d.ts +10 -0
  165. package/hmr/server/websocket-vendor-unifier.js +51 -0
  166. package/hmr/server/websocket-vendor-unifier.js.map +1 -0
  167. package/hmr/server/websocket-vue-sfc.d.ts +26 -0
  168. package/hmr/server/websocket-vue-sfc.js +1053 -0
  169. package/hmr/server/websocket-vue-sfc.js.map +1 -0
  170. package/hmr/server/websocket.d.ts +58 -75
  171. package/hmr/server/websocket.js +2230 -1802
  172. package/hmr/server/websocket.js.map +1 -1
  173. package/hmr/shared/package-classifier.d.ts +9 -0
  174. package/hmr/shared/package-classifier.js +58 -0
  175. package/hmr/shared/package-classifier.js.map +1 -0
  176. package/hmr/shared/runtime/boot-placeholder-ui.d.ts +69 -0
  177. package/hmr/shared/runtime/boot-placeholder-ui.js +101 -0
  178. package/hmr/shared/runtime/boot-placeholder-ui.js.map +1 -0
  179. package/hmr/shared/runtime/boot-progress.d.ts +40 -0
  180. package/hmr/shared/runtime/boot-progress.js +128 -0
  181. package/hmr/shared/runtime/boot-progress.js.map +1 -0
  182. package/hmr/shared/runtime/boot-timeline.d.ts +18 -0
  183. package/hmr/shared/runtime/boot-timeline.js +52 -0
  184. package/hmr/shared/runtime/boot-timeline.js.map +1 -0
  185. package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
  186. package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
  187. package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
  188. package/hmr/shared/runtime/dev-overlay.d.ts +78 -3
  189. package/hmr/shared/runtime/dev-overlay.js +1094 -26
  190. package/hmr/shared/runtime/dev-overlay.js.map +1 -1
  191. package/hmr/shared/runtime/module-provenance.js +1 -4
  192. package/hmr/shared/runtime/module-provenance.js.map +1 -1
  193. package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
  194. package/hmr/shared/runtime/root-placeholder.js +1019 -151
  195. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  196. package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
  197. package/hmr/shared/runtime/session-bootstrap.js +309 -0
  198. package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
  199. package/hmr/shared/runtime/vendor-bootstrap.js +1 -9
  200. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  201. package/hmr/shared/vendor/manifest.d.ts +32 -0
  202. package/hmr/shared/vendor/manifest.js +411 -46
  203. package/hmr/shared/vendor/manifest.js.map +1 -1
  204. package/index.d.ts +1 -0
  205. package/index.js +5 -0
  206. package/index.js.map +1 -1
  207. package/package.json +9 -1
  208. package/runtime/core-aliases-early.js +94 -67
  209. package/runtime/core-aliases-early.js.map +1 -1
@@ -1,12 +1,14 @@
1
1
  import { getPackageJson, getProjectFilePath, getProjectRootPath } from './project.js';
2
2
  import fs from 'fs';
3
- import os from 'os';
4
3
  import path from 'path';
5
4
  import { preprocessCSS } from 'vite';
5
+ import { parse as parseCssToAst } from 'css';
6
6
  import { getProjectFlavor } from './flavor.js';
7
7
  import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from './utils.js';
8
8
  import { getResolvedAppComponents } from './app-components.js';
9
9
  import { toStaticImportSpecifier } from './import-specifier.js';
10
+ import { buildCoreUrl } from './ns-core-url.js';
11
+ import { resolveDeviceReachableOrigin } from './dev-host.js';
10
12
  // Switched to runtime modules to avoid fragile string injection and enable TS checks
11
13
  const projectRoot = getProjectRootPath();
12
14
  const appRootDir = getProjectAppPath();
@@ -52,6 +54,35 @@ const APP_CSS_RESOLVED = '\0' + APP_CSS_VIRTUAL_ID;
52
54
  // polyfill is installed during module evaluation (before zone.js patches run).
53
55
  const XHR_POLYFILL_VIRTUAL_ID = 'virtual:ns-xhr-polyfill';
54
56
  const XHR_POLYFILL_RESOLVED = '\0' + XHR_POLYFILL_VIRTUAL_ID;
57
+ // Virtual module that seeds compile-time defines (`__APPLE__`, `__IOS__`,
58
+ // `__DEV__`, etc.) on `globalThis` BEFORE any other module evaluates.
59
+ //
60
+ // Why this exists. The per-module shim that `processCodeForDevice` injects
61
+ // at the top of every served module reads these values from `globalThis`:
62
+ // const __APPLE__ = globalThis.__APPLE__ !== undefined
63
+ // ? globalThis.__APPLE__
64
+ // : (__IOS__ || __VISIONOS__);
65
+ // `const` evaluates ONCE at module instantiation. So every module needs
66
+ // `globalThis.__APPLE__` to already be set when it instantiates — otherwise
67
+ // it locks in `false` for the lifetime of the module.
68
+ //
69
+ // In ESM, all `import` statements hoist to the top of the module's
70
+ // evaluation phase: imports run in DFS post-order BEFORE the importing
71
+ // module's body. If we put the seed assignments inline in the entry's
72
+ // body (e.g. `globalThis.__APPLE__ = true`), they run AFTER every module
73
+ // transitively imported via `bundle-entry-points` (which reaches the
74
+ // user's `main.ts` → `app.module.ts` → services → util files). Those
75
+ // utility modules then snapshot `globalThis.__APPLE__ = undefined` and
76
+ // fall through to the `false` branch — landing iOS code in the
77
+ // `else { /* Android */ }` branch and crashing on `Utils.android.*`.
78
+ //
79
+ // The fix is to import this virtual module FIRST in the entry. As a leaf
80
+ // in the dependency graph it evaluates before every sibling import, so
81
+ // its body assignments happen before any user module instantiates and
82
+ // reads `globalThis.__*`. This is the architecturally-correct way to
83
+ // make values available to other modules across the import graph in ESM.
84
+ const DEFINES_SEED_VIRTUAL_ID = 'virtual:ns-defines-seed';
85
+ const DEFINES_SEED_RESOLVED = '\0' + DEFINES_SEED_VIRTUAL_ID;
55
86
  export function mainEntryPlugin(opts) {
56
87
  let resolvedConfig;
57
88
  return {
@@ -59,6 +90,95 @@ export function mainEntryPlugin(opts) {
59
90
  configResolved(config) {
60
91
  resolvedConfig = config;
61
92
  },
93
+ // Warm chokidar with `app.css`'s @import dependency tree at
94
+ // server startup. Without this, the FIRST save to a workspace
95
+ // `@import` dep (e.g. `<repo>/libs/.../index.css`) is silently
96
+ // dropped: nothing in the cold-boot path causes the dev server
97
+ // to transform `app.css`, so `vite:css` never registers
98
+ // `@import`-resolved deps with the watcher. Eagerly resolving
99
+ // them here breaks the chicken-and-egg.
100
+ //
101
+ // We ALSO stash the resolved dep set on the server itself so
102
+ // the HMR websocket handler can recognize when a non-CSS edit
103
+ // (e.g. a `.html` template or `.ts` file scanned by Tailwind's
104
+ // content config) should trigger a fresh `app.css` fetch. The
105
+ // `preprocessCSS` deps include every file PostCSS reported as
106
+ // a `dependency` message — for Tailwind 3 with content globs,
107
+ // Vite's `compileCSS` expands the `dir-dependency` glob into
108
+ // the individual file paths, so we get the full content set.
109
+ //
110
+ // `app.css` is NEVER a real Vite module in NS HMR (the virtual
111
+ // `:ns-app-css` module re-runs `preprocessCSS` in its load
112
+ // hook), so the standard moduleGraph `addWatchFile` →
113
+ // `_addedImports` → file-only-entry-importer chain doesn't
114
+ // populate, and `ctx.modules` for a content-file edit never
115
+ // links back to a CSS module. The dep set fills that gap.
116
+ configureServer(server) {
117
+ if (server.config.command !== 'serve')
118
+ return;
119
+ const appCssPath = path.resolve(projectRoot, getProjectAppRelativePath('app.css'));
120
+ if (!fs.existsSync(appCssPath))
121
+ return;
122
+ const normalizeFsPath = (p) => path.resolve(p).replace(/\\/g, '/');
123
+ const normalizedAppCssPath = normalizeFsPath(appCssPath);
124
+ const watchedDeps = new Set([normalizedAppCssPath]);
125
+ const refreshDeps = async () => {
126
+ try {
127
+ const code = fs.readFileSync(appCssPath, 'utf-8');
128
+ const result = await preprocessCSS(code, appCssPath, server.config);
129
+ server.watcher.add(appCssPath);
130
+ const next = new Set([normalizedAppCssPath]);
131
+ for (const dep of result?.deps ?? []) {
132
+ if (typeof dep === 'string' && dep) {
133
+ server.watcher.add(dep);
134
+ next.add(normalizeFsPath(dep));
135
+ }
136
+ }
137
+ // Atomic-ish replace: clear + repopulate the existing
138
+ // Set so any concurrent reader sees a consistent view
139
+ // when iterating with `.has()`.
140
+ watchedDeps.clear();
141
+ for (const f of next)
142
+ watchedDeps.add(f);
143
+ }
144
+ catch { }
145
+ };
146
+ server.__nsAppCssPath = normalizedAppCssPath;
147
+ server.__nsAppCssDeps = watchedDeps;
148
+ server.__nsRefreshAppCssDeps = refreshDeps;
149
+ refreshDeps();
150
+ // Re-scan when `app.css` itself or a Tailwind config file
151
+ // changes — Tailwind's content list and utility definitions
152
+ // can both shift, so the dep set has to follow. Debounced
153
+ // because watcher events for the same edit can fire in
154
+ // quick succession (chokidar's atomic save handling).
155
+ let pendingRefresh = null;
156
+ const scheduleRefresh = () => {
157
+ if (pendingRefresh)
158
+ clearTimeout(pendingRefresh);
159
+ pendingRefresh = setTimeout(() => {
160
+ pendingRefresh = null;
161
+ refreshDeps();
162
+ }, 50);
163
+ };
164
+ const isAppCssOrTailwindConfig = (file) => {
165
+ const normalized = normalizeFsPath(file);
166
+ if (normalized === normalizedAppCssPath)
167
+ return true;
168
+ return /\/tailwind\.config\.[mc]?[jt]s$/.test(normalized);
169
+ };
170
+ server.watcher.on('change', (file) => {
171
+ if (isAppCssOrTailwindConfig(file))
172
+ scheduleRefresh();
173
+ });
174
+ // `add`/`unlink` cover Tailwind's content set growing or
175
+ // shrinking — new template files, deleted partials, etc.
176
+ // Re-running `preprocessCSS` is the safest way to keep the
177
+ // glob expansion accurate without re-implementing the
178
+ // match.
179
+ server.watcher.on('add', scheduleRefresh);
180
+ server.watcher.on('unlink', scheduleRefresh);
181
+ },
62
182
  resolveId(id) {
63
183
  if (id === VIRTUAL_ID)
64
184
  return RESOLVED;
@@ -66,19 +186,111 @@ export function mainEntryPlugin(opts) {
66
186
  return APP_CSS_RESOLVED;
67
187
  if (id === XHR_POLYFILL_VIRTUAL_ID)
68
188
  return XHR_POLYFILL_RESOLVED;
189
+ if (id === DEFINES_SEED_VIRTUAL_ID)
190
+ return DEFINES_SEED_RESOLVED;
69
191
  return null;
70
192
  },
71
193
  async load(id) {
72
- // Virtual module that processes app.css through PostCSS/Tailwind and returns as a JS string.
73
- // This avoids using ?inline which conflicts with @analogjs/vite-plugin-angular's CSS
74
- // interception in Vite 8 the Angular plugin converts ?inline CSS to JS via its load hook
75
- // but doesn't set moduleType:'js', so vite:css still tries to run PostCSS on the JS output.
194
+ // Compute the dev server origin for HMR mode. Under HMR we emit
195
+ // `@nativescript/core*` imports as FULL HTTP URLs so iOS's ESM loader
196
+ // can fetch them directly during bundle.mjs module instantiation
197
+ // the import map isn't installed yet at that phase. For non-HMR
198
+ // builds, we keep bare specifiers so production bundlers inline core
199
+ // the normal way.
200
+ //
201
+ // Routes through `resolveDeviceReachableOrigin` so the URL baked
202
+ // into the bundle is something the DEVICE can reach: wildcard
203
+ // binds (`0.0.0.0`) and Android loopback get remapped to a real
204
+ // LAN IP (preferred) or the platform's reachable fallback
205
+ // (`10.0.2.2` for the Android emulator). Without this, Android
206
+ // crashes at boot trying to fetch `http://0.0.0.0:5173/ns/core/xhr`.
207
+ const getBootOrigin = () => {
208
+ if (!opts.hmrActive)
209
+ return null;
210
+ try {
211
+ const { origin } = resolveDeviceReachableOrigin({
212
+ host: resolvedConfig.server.host,
213
+ platform: opts.platform,
214
+ protocol: resolvedConfig.server.https || opts.useHttps ? 'https' : 'http',
215
+ port: Number(resolvedConfig.server.port || 5173),
216
+ });
217
+ return origin;
218
+ }
219
+ catch {
220
+ return null;
221
+ }
222
+ };
223
+ // Return a spec string for @nativescript/core or a subpath that is
224
+ // guaranteed to resolve at iOS module-instantiation time. Under HMR
225
+ // this is always a full HTTP URL into the /ns/core bridge (no
226
+ // import-map dependency). Under non-HMR it's the bare specifier for
227
+ // the bundler to handle.
228
+ //
229
+ // Under HMR, delegates to buildCoreUrl() — the ONE canonical URL
230
+ // generator. Every URL emitter in the build/runtime pipeline (this
231
+ // function, ns-core-external-urls, rewriteSpec, runtime import map)
232
+ // uses the same function so iOS's HTTP ESM cache sees byte-identical
233
+ // URLs.
234
+ const coreSpec = (subpath) => {
235
+ const origin = getBootOrigin();
236
+ if (origin) {
237
+ return buildCoreUrl(origin, subpath);
238
+ }
239
+ const sub = subpath ? String(subpath).replace(/^\/+/, '') : '';
240
+ return sub ? `@nativescript/core/${sub}` : '@nativescript/core';
241
+ };
242
+ // Virtual module that processes app.css through PostCSS/Tailwind and emits a
243
+ // JS module that BOTH applies the CSS as a side-effect AND exports the raw
244
+ // CSS string as default.
245
+ //
246
+ // Background: Vite's default `?inline` CSS handling collides with
247
+ // @analogjs/vite-plugin-angular's load hook in Vite 8 (it converts ?inline
248
+ // CSS to JS without setting moduleType:'js', so vite:css still tries to run
249
+ // PostCSS on the JS output). This virtual module sidesteps that.
250
+ //
251
+ // We still export the raw CSS string as default so the entry can seed
252
+ // `globalThis.__NS_HMR_APP_CSS__` for HMR's HTTP-core-realm replay path.
253
+ //
254
+ // We always pre-parse with rework `css` and either:
255
+ // - non-HMR: call `addTaggedAdditionalCSS(astJson, 'app.css')` directly
256
+ // so the bundled `style-scope` realm gets the styles before any view
257
+ // is created.
258
+ // - HMR: stash the AST on `globalThis.__NS_HMR_APP_CSS_AST__` so
259
+ // `installHttpCoreCssSupport` can apply it via the SAME AST path
260
+ // in the HTTP-core realm. Without this, the HTTP path would fall
261
+ // back to applying the raw text via `cssTreeParse` at runtime,
262
+ // which has produced subtle behavioral mismatches with the rework
263
+ // AST (e.g. `.text-sm { line-height: 20 }` rendering with extra
264
+ // line spacing under HMR but not under the no-HMR rolldown
265
+ // bundle, even though both bundles serialize the identical
266
+ // declaration). Pre-parsing once at build time and shipping the
267
+ // AST to BOTH paths keeps cold-boot rendering identical between
268
+ // the two modes. Live HMR edits still arrive as raw text via the
269
+ // dev-server WebSocket, so `installHttpCoreCssSupport` keeps the
270
+ // raw-text fallback for that case.
76
271
  if (id === APP_CSS_RESOLVED) {
77
272
  const appCssPath = path.resolve(projectRoot, getProjectAppRelativePath('app.css'));
78
273
  const code = fs.readFileSync(appCssPath, 'utf-8');
79
274
  const result = await preprocessCSS(code, appCssPath, resolvedConfig);
275
+ const ast = parseCssToAst(result.code, { silent: true });
276
+ // `css` emits `position` metadata on every AST node. NS doesn't
277
+ // use it, and stripping it ~halves the inlined JSON size — same
278
+ // thing webpack's css2json-loader does.
279
+ const astJson = JSON.stringify(ast, (key, value) => (key === 'position' ? undefined : value));
280
+ const lines = [];
281
+ if (opts.hmrActive) {
282
+ // Stash AST on globalThis BEFORE the entry seeds
283
+ // `__NS_HMR_APP_CSS__` from this module's default export, so
284
+ // `installHttpCoreCssSupport` can prefer the AST and match
285
+ // the no-HMR application path exactly.
286
+ lines.push(`try { (globalThis).__NS_HMR_APP_CSS_AST__ = ${astJson}; } catch {}`);
287
+ }
288
+ else {
289
+ lines.push(`import { addTaggedAdditionalCSS } from ${JSON.stringify(coreSpec('ui/styling/style-scope'))};`, `addTaggedAdditionalCSS(${astJson}, 'app.css');`);
290
+ }
291
+ lines.push(`export default ${JSON.stringify(result.code)};`);
80
292
  return {
81
- code: `export default ${JSON.stringify(result.code)};`,
293
+ code: lines.join('\n'),
82
294
  moduleType: 'js',
83
295
  };
84
296
  }
@@ -86,65 +298,213 @@ export function mainEntryPlugin(opts) {
86
298
  // guaranteeing XMLHttpRequest is on globalThis before zone.js or any other code accesses it.
87
299
  if (id === XHR_POLYFILL_RESOLVED) {
88
300
  return {
89
- code: ["import * as xhrImpl from '@nativescript/core/xhr';", "var polyfills = ['XMLHttpRequest','FormData','Blob','File','FileReader'];", 'for (var i = 0; i < polyfills.length; i++) {', ' var n = polyfills[i];', ' if (!(n in globalThis) && xhrImpl[n]) globalThis[n] = xhrImpl[n];', '}'].join('\n'),
301
+ code: [`import * as xhrImpl from ${JSON.stringify(coreSpec('xhr'))};`, "var polyfills = ['XMLHttpRequest','FormData','Blob','File','FileReader'];", 'for (var i = 0; i < polyfills.length; i++) {', ' var n = polyfills[i];', ' if (!(n in globalThis) && xhrImpl[n]) globalThis[n] = xhrImpl[n];', '}'].join('\n'),
302
+ moduleType: 'js',
303
+ };
304
+ }
305
+ // Virtual module that seeds compile-time defines on globalThis.
306
+ // Imported FIRST in the entry so it evaluates as a leaf before
307
+ // any other module — including the `bundle-entry-points` chain
308
+ // that transitively reaches the user's app code. See the
309
+ // DEFINES_SEED_VIRTUAL_ID comment at the top of this file.
310
+ if (id === DEFINES_SEED_RESOLVED) {
311
+ const isApple = opts.platform === 'ios' || opts.platform === 'visionos';
312
+ const seedLines = [
313
+ `globalThis.__DEV__ = ${opts.isDevMode ? 'true' : 'false'};`,
314
+ `globalThis.__ANDROID__ = ${opts.platform === 'android' ? 'true' : 'false'};`,
315
+ `globalThis.__IOS__ = ${opts.platform === 'ios' ? 'true' : 'false'};`,
316
+ `globalThis.__VISIONOS__ = ${opts.platform === 'visionos' ? 'true' : 'false'};`,
317
+ `globalThis.__APPLE__ = ${isApple ? 'true' : 'false'};`,
318
+ 'globalThis.__COMMONJS__ = false;',
319
+ 'globalThis.__NS_WEBPACK__ = false;',
320
+ `globalThis.__NS_ENV_VERBOSE__ = ${opts.verbose ? 'true' : 'false'};`,
321
+ // Seed the runtime flavor so the HMR client (which is loaded from
322
+ // node_modules and therefore NOT processed by Vite's `define`
323
+ // substitution) can resolve `TARGET_FLAVOR` reliably. Without
324
+ // this, `resolveTargetFlavor()` in `hmr/client/index.ts` only
325
+ // detects 'angular' (via __reboot_ng_modules__) and 'vue' (via
326
+ // __VUE_HMR_RUNTIME__), and 'solid' / 'typescript' fall through
327
+ // to undefined — so any flavor-specific switch case in
328
+ // `processQueue` (Solid component boundary discovery, route-loader
329
+ // patching, overlay stage transitions) silently never fires.
330
+ `globalThis.__NS_TARGET_FLAVOR__ = ${JSON.stringify(flavor)};`,
331
+ 'globalThis.__UI_USE_XML_PARSER__ = true;',
332
+ 'globalThis.__UI_USE_EXTERNAL_RENDERER__ = false;',
333
+ "globalThis.__CSS_PARSER__ = 'css-tree';",
334
+ 'globalThis.__TEST__ = false;',
335
+ ];
336
+ return {
337
+ code: seedLines.join('\n') + '\n',
90
338
  moduleType: 'js',
91
339
  };
92
340
  }
93
341
  if (id !== RESOLVED)
94
342
  return null;
343
+ let imports = '';
344
+ // Under HMR: import the defines-seed virtual module FIRST so its
345
+ // body — which sets `globalThis.__APPLE__`, `__IOS__`, `__DEV__`,
346
+ // etc. — evaluates as a leaf in the dependency graph BEFORE any
347
+ // other module instantiates. Per-module shims injected by
348
+ // `processCodeForDevice` read these from globalThis and snapshot
349
+ // them at instantiation time, so they MUST be set first.
350
+ //
351
+ // Under non-HMR: Vite's `define` config handles substitution
352
+ // statically at build time — no runtime seeding needed.
353
+ if (opts.hmrActive) {
354
+ imports += `import '${DEFINES_SEED_VIRTUAL_ID}';\n`;
355
+ }
95
356
  // consistent verbose flag to easily reference below
96
- let imports = "const __nsVerboseLog = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;\n";
97
- // Ensure any CommonJS-style tooling requires (e.g. from Babel or other
98
- // build-time libraries that may be accidentally bundled) do not attempt
99
- // to resolve Node built-ins like 'fs' or 'path' on device. These modules
100
- // are not used at runtime for NativeScript apps, so we safely return an
101
- // empty object from a global require shim when present.
102
- imports += "try { if (typeof globalThis !== 'undefined') { var _nsReq = function () { return {}; }; _nsReq.context = function() { var _c = { keys: function() { return []; } }; _c.__esModule = true; return _c; }; globalThis.require = _nsReq; } } catch {}\n";
357
+ imports += "const __nsVerboseLog = typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__;\n";
358
+ // Ensure any CommonJS-style tooling requires (e.g. from Babel or
359
+ // other build-time libraries that may be accidentally bundled) do
360
+ // not attempt to resolve Node built-ins like 'fs' or 'path' on
361
+ // device. These modules are not used at runtime for NativeScript
362
+ // apps, so we safely return an empty object from a shim.
363
+ //
364
+ // IMPORTANT: Under HMR, vendor packages call the real NativeScript
365
+ // CommonJS require() with `@nativescript/core/<sub>` specifiers
366
+ // (e.g. `require('@nativescript/core/ui/core/view').View` in
367
+ // `@nativescript-community/gesturehandler`). If we overwrite
368
+ // globalThis.require with a blanket stub, every such call returns
369
+ // `{}` and any property access on the result (e.g. `.View`) is
370
+ // `undefined`, cascading into `TypeError: Cannot read properties
371
+ // of undefined (reading 'prototype')` inside `applyMixins` when
372
+ // vendor install() hooks run.
373
+ //
374
+ // Instead, install a DELEGATING shim:
375
+ // - If the specifier is a Node built-in (fs, path, os, …) or
376
+ // a webpack-only runtime hook (require.context), return a
377
+ // safe empty stub.
378
+ // - Otherwise, delegate to the preserved original
379
+ // `globalThis.require` (NativeScript's native CJS loader),
380
+ // which routes `@nativescript/core*` through the HTTP bridge
381
+ // or, for already-HTTP-loaded modules, through the
382
+ // `globalThis.__NS_CORE_MODULES__` registry populated by the
383
+ // `/ns/core` bridge preamble.
384
+ imports += "try { if (typeof globalThis !== 'undefined') {\n";
385
+ imports += " var __nsOrigRequire = typeof globalThis.require === 'function' ? globalThis.require : null;\n";
386
+ imports += ' var __nsNodeBuiltins = { fs: 1, path: 1, os: 1, url: 1, crypto: 1, util: 1, stream: 1, events: 1, buffer: 1, http: 1, https: 1, net: 1, tls: 1, dns: 1, child_process: 1, module: 1, zlib: 1, querystring: 1, assert: 1, constants: 1, vm: 1 };\n';
387
+ // Mirror helpers/ns-core-url.ts normalizeCoreSub() inline so the
388
+ // lookup against __NS_CORE_MODULES__ uses the same keys the
389
+ // /ns/core handler registers under.
390
+ imports += ' var __nsNormSub = function (s) {\n';
391
+ imports += " if (!s) return '';\n";
392
+ imports += " var t = String(s).split('?')[0].split('#')[0].trim();\n";
393
+ imports += " t = t.replace(/^\\/+/, '').replace(/\\/+$/, '');\n";
394
+ imports += " t = t.replace(/\\.(?:mjs|cjs|js)$/, '');\n";
395
+ imports += " if (t.length >= 6 && t.substring(t.length - 6) === '/index') t = t.substring(0, t.length - 6);\n";
396
+ imports += " if (!t || t === 'index') return '';\n";
397
+ imports += ' return t;\n';
398
+ imports += ' };\n';
399
+ // Invariant D: CJS/ESM interop shape helper.
400
+ //
401
+ // Install a global, idempotent shape function that converts
402
+ // ESM Module Namespace Objects (which have [[Prototype]] = null
403
+ // per spec §9.4.6) into plain Objects that inherit from
404
+ // Object.prototype. CJS consumers — especially zone.js's
405
+ // patchMethod() — call `hasOwnProperty`, `toString`, etc. on
406
+ // their require() result; a null-proto namespace throws
407
+ // "X is not a function" on the first such call.
408
+ //
409
+ // Properties:
410
+ // - Recursive: @nativescript/core re-exports Utils/Http/Trace
411
+ // as nested namespaces (`export * as Utils from './utils'`),
412
+ // each also null-proto. Shallow wrapping leaves those.
413
+ // - Identity-preserving via a WeakMap cache keyed on the
414
+ // underlying namespace. zone.js MUTATES its target (stashes
415
+ // delegate symbols, overwrites methods); a fresh copy per
416
+ // require() would lose those mutations on the next lookup.
417
+ // - Installed ONCE on globalThis so the /ns/core handler's
418
+ // registration footer, the vendor shim's createRequire, and
419
+ // any other consumer share the same cache and see
420
+ // mutation-consistent shapes.
421
+ imports += ' var __nsShapeCache = globalThis.__NS_CJS_SHAPE_CACHE__ || (globalThis.__NS_CJS_SHAPE_CACHE__ = new WeakMap());\n';
422
+ imports += ' var __nsShapeCjs = globalThis.__NS_CJS_SHAPE__ || (globalThis.__NS_CJS_SHAPE__ = function __nsShape(obj) {\n';
423
+ imports += " if (!obj || typeof obj !== 'object') return obj;\n";
424
+ imports += ' var proto = Object.getPrototypeOf(obj);\n';
425
+ imports += ' var isNsModule = false;\n';
426
+ imports += " try { isNsModule = obj[Symbol.toStringTag] === 'Module'; } catch (e) {}\n";
427
+ imports += ' if (proto !== null && !isNsModule) return obj;\n';
428
+ imports += ' if (__nsShapeCache.has(obj)) return __nsShapeCache.get(obj);\n';
429
+ imports += ' var out = {};\n';
430
+ imports += ' __nsShapeCache.set(obj, out);\n';
431
+ imports += ' try {\n';
432
+ imports += ' var keys = Object.keys(obj);\n';
433
+ imports += ' for (var i = 0; i < keys.length; i++) {\n';
434
+ imports += ' var k = keys[i];\n';
435
+ imports += ' try { out[k] = __nsShape(obj[k]); } catch (e) {}\n';
436
+ imports += ' }\n';
437
+ imports += ' } catch (e) {}\n';
438
+ imports += ' return out;\n';
439
+ imports += ' });\n';
440
+ imports += ' var _nsReq = function (id) {\n';
441
+ imports += ' try {\n';
442
+ imports += " var n = String(id || '');\n";
443
+ imports += " var stripped = n.indexOf('node:') === 0 ? n.slice(5) : n;\n";
444
+ imports += ' if (__nsNodeBuiltins[stripped]) return {};\n';
445
+ imports += " if (n === '@nativescript/core' || n.indexOf('@nativescript/core/') === 0) {\n";
446
+ imports += ' var table = globalThis.__NS_CORE_MODULES__;\n';
447
+ imports += ' if (table) {\n';
448
+ // Table entries are ALREADY shaped (the /ns/core footer stores
449
+ // the shape, not the raw namespace). But we pass through
450
+ // __nsShapeCjs anyway — it's a no-op on already-shaped values
451
+ // (fast path: `proto !== null && !isNsModule` returns obj as-is)
452
+ // and guards against future changes to how the registry is
453
+ // populated (e.g., by direct assignment from test code).
454
+ imports += ' if (table[n]) return __nsShapeCjs(table[n]);\n';
455
+ imports += " var rawSub = n === '@nativescript/core' ? '' : n.slice('@nativescript/core/'.length);\n";
456
+ imports += ' var normSub = __nsNormSub(rawSub);\n';
457
+ imports += " var bareKey = normSub ? '@nativescript/core/' + normSub : '@nativescript/core';\n";
458
+ imports += ' if (table[bareKey]) return __nsShapeCjs(table[bareKey]);\n';
459
+ imports += ' if (table[normSub]) return __nsShapeCjs(table[normSub]);\n';
460
+ imports += ' }\n';
461
+ imports += ' }\n';
462
+ // Fallback to native require (NativeScript CJS loader via HTTP
463
+ // bridge). Shape the result too — the native loader may return
464
+ // a raw ESM namespace for core subpaths served before the /ns/core
465
+ // footer runs.
466
+ imports += ' if (__nsOrigRequire) return __nsShapeCjs(__nsOrigRequire(id));\n';
467
+ imports += ' } catch (e) {}\n';
468
+ imports += ' return {};\n';
469
+ imports += ' };\n';
470
+ imports += ' _nsReq.context = function () { var _c = { keys: function () { return []; } }; _c.__esModule = true; return _c; };\n';
471
+ imports += ' if (__nsOrigRequire) { try { _nsReq.resolve = __nsOrigRequire.resolve ? __nsOrigRequire.resolve.bind(__nsOrigRequire) : function (id) { return id; }; } catch (e) {} }\n';
472
+ imports += ' globalThis.require = _nsReq;\n';
473
+ imports += ' globalThis.__nsOrigRequire = __nsOrigRequire;\n';
474
+ imports += '} } catch {}\n';
103
475
  // Banner diagnostics for visibility at runtime
104
476
  if (opts.verbose) {
105
477
  imports += `console.info('[ns-entry] begin', { platform: ${JSON.stringify(opts.platform)}, dev: ${JSON.stringify(opts.isDevMode)}, hmr: ${JSON.stringify(opts.hmrActive)}, verbose: ${JSON.stringify(opts.verbose)}, mainEntry: ${JSON.stringify(mainEntry)}, mainRel: ${JSON.stringify(mainEntryRelPosix)}, time: new Date().toISOString() });\n`;
106
478
  }
107
479
  if (opts.hmrActive) {
108
- // Seed ALL compile-time defines on globalThis so that every execution
109
- // context the primary bundle, HTTP ESM modules, and cross-realm
110
- // calls can reliably access them. The per-module injection in
111
- // processCodeForDevice() reads from globalThis as the source of truth;
112
- // these seeds ensure the values are always available.
113
- imports += `globalThis.__DEV__ = ${opts.isDevMode ? 'true' : 'false'};\n`;
114
- imports += `globalThis.__ANDROID__ = ${opts.platform === 'android' ? 'true' : 'false'};\n`;
115
- imports += `globalThis.__IOS__ = ${opts.platform === 'ios' ? 'true' : 'false'};\n`;
116
- imports += `globalThis.__VISIONOS__ = ${opts.platform === 'visionos' ? 'true' : 'false'};\n`;
117
- imports += `globalThis.__APPLE__ = ${opts.platform === 'ios' || opts.platform === 'visionos' ? 'true' : 'false'};\n`;
118
- imports += `globalThis.__COMMONJS__ = false;\n`;
119
- imports += `globalThis.__NS_WEBPACK__ = false;\n`;
120
- imports += `globalThis.__NS_ENV_VERBOSE__ = ${opts.verbose ? 'true' : 'false'};\n`;
121
- imports += `globalThis.__UI_USE_XML_PARSER__ = true;\n`;
122
- imports += `globalThis.__UI_USE_EXTERNAL_RENDERER__ = false;\n`;
123
- imports += `globalThis.__CSS_PARSER__ = 'css-tree';\n`;
124
- imports += `globalThis.__TEST__ = false;\n`;
480
+ // NOTE: globalThis defines (`__APPLE__`, `__IOS__`, `__DEV__`,
481
+ // etc.) are seeded by the `virtual:ns-defines-seed` import at
482
+ // the very top of this entry see the import on line ~192.
483
+ // They MUST run as a leaf module ahead of any other graph
484
+ // node, so we can't seed them inline here (ESM imports hoist
485
+ // past inline body code, so any module reachable through
486
+ // `bundle-entry-points` would otherwise see undefined).
125
487
  imports += "import { installModuleProvenanceRecorder } from '@nativescript/vite/hmr/shared/runtime/module-provenance.js';\n";
126
488
  imports += 'installModuleProvenanceRecorder(__nsVerboseLog);\n';
127
- // ---- Vendor manifest bootstrap ----
128
- // Use single self-contained vendor module to avoid extra imports affecting chunking
129
- imports += "import vendorManifest, { __nsVendorModuleMap } from '@nativescript/vendor';\n";
130
- imports += "import { installVendorBootstrap } from '@nativescript/vite/hmr/shared/runtime/vendor-bootstrap.js';\n";
131
- if (opts.verbose) {
132
- imports += `console.info('[ns-entry] vendor manifest imported', { keys: Object.keys(vendorManifest||{}).length, hasMap: typeof __nsVendorModuleMap === 'object' });\n`;
133
- }
134
- imports += 'installVendorBootstrap(vendorManifest, __nsVendorModuleMap, __nsVerboseLog);\n';
135
- if (opts.verbose) {
136
- imports += `console.info('[ns-entry] vendor bootstrap installed');\n`;
137
- }
138
489
  }
139
490
  // ---- Core runtime globals (always-needed) ----
140
491
  // Install XHR polyfill FIRST — its virtual module body runs during import evaluation,
141
492
  // before any subsequent import (like zone.js) can reference XMLHttpRequest.
142
493
  imports += `import '${XHR_POLYFILL_VIRTUAL_ID}';\n`;
143
- // Load globals early
144
- imports += "import '@nativescript/core/globals/index';\n";
494
+ // Load globals early. Under HMR we use a full HTTP URL so iOS's
495
+ // ESM loader can fetch it directly at module-instantiation time —
496
+ // the import map isn't installed yet at that phase.
497
+ imports += `import ${JSON.stringify(coreSpec('globals/index'))};\n`;
145
498
  if (opts.verbose) {
146
499
  imports += `console.info('[ns-entry] core globals loaded');\n`;
147
500
  }
501
+ // Seed the real NativeScript Application singleton before any early HMR/placeholder
502
+ // code runs. Dynamic discovery is too late for iOS placeholder startup.
503
+ imports += `import { Application as __nsEarlyApplication } from ${JSON.stringify(coreSpec('application'))};\n`;
504
+ imports += `try { if (__nsEarlyApplication && (typeof __nsEarlyApplication.run === 'function' || typeof __nsEarlyApplication.on === 'function' || typeof __nsEarlyApplication.resetRootView === 'function')) { globalThis.Application = __nsEarlyApplication; } } catch {}\n`;
505
+ if (opts.verbose) {
506
+ imports += `console.info('[ns-entry] early Application seeded', { hasRun: typeof globalThis.Application?.run === 'function', hasOn: typeof globalThis.Application?.on === 'function', hasResetRootView: typeof globalThis.Application?.resetRootView === 'function' });\n`;
507
+ }
148
508
  // In dev mode for Angular apps, ensure @angular/compiler (JIT) is loaded.
149
509
  // With experimentalDecorators:true, TypeScript emits __decorate patterns.
150
510
  // On watch-mode rebuilds the Angular compiler may not re-emit ɵfac for
@@ -176,7 +536,7 @@ export function mainEntryPlugin(opts) {
176
536
  }
177
537
  }
178
538
  // Load NS bundle entry points after early hook
179
- imports += "import '@nativescript/core/bundle-entry-points';\n";
539
+ imports += `import ${JSON.stringify(coreSpec('bundle-entry-points'))};\n`;
180
540
  if (opts.verbose) {
181
541
  imports += `console.info('[ns-entry] bundle-entry-points loaded');\n`;
182
542
  }
@@ -211,50 +571,13 @@ export function mainEntryPlugin(opts) {
211
571
  }
212
572
  }
213
573
  // ---- Platform-specific always-needed modules ----
214
- // Track if we need to defer Android activity import (non-HMR only)
215
574
  let needsAndroidActivityDefer = false;
216
575
  if (opts.platform === 'android') {
217
- if (opts.hmrActive) {
218
- /**
219
- * Ensure the Java Activity class exists by executing the vendor-packed
220
- * activity registration module via the vendor registry (not ESM import).
221
- * This avoids any on-disk vendor.mjs export mismatches and guarantees the
222
- * class is registered before Android tries to instantiate it.
223
- */
224
- imports += `
225
- (function __nsEnsureAndroidActivityForHMR(){
226
- try {
227
- const g = globalThis;
228
- const req = (g.__nsVendorRequire || g.__nsRequire);
229
- if (!req) {
230
- ${opts.verbose ? "console.warn('[ns-entry] vendor require not available yet; activity registration may be deferred');" : ''}
231
- return;
232
- }
233
- const candidates = [
234
- '@nativescript/core/ui/frame/activity.android',
235
- '@nativescript/core/ui/frame/activity.android.js'
236
- ];
237
- for (const id of candidates) {
238
- try {
239
- req(id);
240
- ${opts.verbose ? "console.info('[ns-entry] android activity registered via vendor', id);" : ''}
241
- break;
242
- } catch {}
243
- }
244
- } catch (e) {
245
- try { console.error('[ns-entry] failed to require android activity from vendor', e); } catch {}
246
- }
247
- })();\n`;
248
- }
249
- else {
250
- /**
251
- * Non-HMR: Defer activity lifecycle wiring until native Application is ready
252
- * to avoid "application is null" errors at production boot.
253
- * We set a flag here and emit the actual code after the static Application import
254
- * to avoid mixing dynamic and static imports of @nativescript/core.
255
- */
256
- needsAndroidActivityDefer = true;
257
- }
576
+ /**
577
+ * Defer activity lifecycle wiring until native Application is ready
578
+ * to avoid "application is null" errors during startup.
579
+ */
580
+ needsAndroidActivityDefer = true;
258
581
  }
259
582
  // ---- Optional polyfills ----
260
583
  if (polyfillsExists) {
@@ -273,9 +596,6 @@ export function mainEntryPlugin(opts) {
273
596
  if (opts.verbose) {
274
597
  imports += "console.info('[ns-entry] websockets polyfill imported');\n";
275
598
  }
276
- // Load HMR client for WebSocket connection to Vite dev server before HTTP-only boot attempts.
277
- imports += "import 'virtual:ns-hmr-client';\n";
278
- imports += "console.info('@nativescript/vite HMR client loaded.');\n";
279
599
  }
280
600
  // ---- Global CSS injection (always-needed if file exists) ----
281
601
  const appCssPath = path.resolve(projectRoot, getProjectAppRelativePath('app.css'));
@@ -283,19 +603,30 @@ export function mainEntryPlugin(opts) {
283
603
  // Import Application statically if needed for CSS or Android activity defer
284
604
  if (hasAppCss || needsAndroidActivityDefer) {
285
605
  if (hasAppCss) {
286
- imports += `// Import and apply global CSS before app bootstrap\n`;
606
+ // The virtual module's body either:
607
+ // - non-HMR: calls `addTaggedAdditionalCSS(ast, 'app.css')`
608
+ // as an import-time side-effect, landing CSS in NS's
609
+ // selector tables before any view is created.
610
+ // - HMR: stashes the rework AST on
611
+ // `globalThis.__NS_HMR_APP_CSS_AST__` so
612
+ // `installHttpCoreCssSupport` can apply it via the SAME
613
+ // AST path in the HTTP-core realm. The default export
614
+ // is the raw CSS string, also seeded onto
615
+ // `globalThis.__NS_HMR_APP_CSS__` here as a
616
+ // raw-text fallback (used for live HMR edits via the
617
+ // dev-server WebSocket).
618
+ imports += `// Apply global CSS before app bootstrap (AST under non-HMR; AST stashed for HMR cold boot)\n`;
287
619
  imports += `import appCssContent from '${APP_CSS_VIRTUAL_ID}';\n`;
288
620
  if (opts.hmrActive) {
289
621
  imports += `try { globalThis.__NS_HMR_APP_CSS__ = appCssContent; } catch {}\n`;
290
622
  }
291
- }
292
- imports += `import { Application } from '@nativescript/core';\n`;
293
- if (hasAppCss) {
294
- imports += `if (appCssContent) { try { Application.addCss(appCssContent); } catch (error) { console.error('Error applying CSS:', error); } }\n`;
295
623
  if (opts.verbose) {
296
- imports += `console.info('[ns-entry] app.css applied');\n`;
624
+ imports += `console.info('[ns-entry] app.css applied as AST');\n`;
297
625
  }
298
626
  }
627
+ if (needsAndroidActivityDefer) {
628
+ imports += `import { Application } from ${JSON.stringify(coreSpec())};\n`;
629
+ }
299
630
  }
300
631
  // ---- Deferred Android activity import (non-HMR only) ----
301
632
  // Uses the statically imported Application to avoid mixing dynamic and static imports
@@ -318,37 +649,31 @@ export function mainEntryPlugin(opts) {
318
649
  }
319
650
  // ---- Application main entry ----
320
651
  if (opts.hmrActive) {
321
- // HTTP-only dev boot: try to import the entire app over HTTP; if not reachable, keep retrying.
652
+ // Deterministic dev boot: fetch one session descriptor and let the runtime
653
+ // import the session client + app entry over HTTP.
322
654
  if (opts.verbose) {
323
- imports += `console.info('[ns-entry] including HTTP-only boot', { platform: ${JSON.stringify(opts.platform)}, mainRel: ${JSON.stringify(mainEntryRelPosix)} });\n`;
655
+ imports += `console.info('[ns-entry] including deterministic dev session bootstrap', { platform: ${JSON.stringify(opts.platform)}, mainRel: ${JSON.stringify(mainEntryRelPosix)} });\n`;
324
656
  }
325
- const guessLanHost = () => {
326
- try {
327
- const nets = os.networkInterfaces();
328
- for (const name of Object.keys(nets)) {
329
- const addrs = nets[name] || [];
330
- for (const a of addrs) {
331
- if (!a)
332
- continue;
333
- const family = a.family;
334
- const internal = !!a.internal;
335
- const address = String(a.address || '');
336
- if (internal)
337
- continue;
338
- if ((family === 'IPv4' || family === 4) && address && address !== '127.0.0.1')
339
- return address;
340
- }
341
- }
342
- }
343
- catch { }
344
- return undefined;
345
- };
346
- // Prefer LAN IP so physical devices work by default; emulator will still be tried as a fallback.
347
- const defaultHost = opts.platform === 'android' ? guessLanHost() || '10.0.2.2' : guessLanHost() || 'localhost';
348
- imports += "import { startHttpOnlyBoot } from '@nativescript/vite/hmr/shared/runtime/http-only-boot.js';\n";
349
- imports += `startHttpOnlyBoot(${JSON.stringify(opts.platform)}, ${JSON.stringify(mainEntryRelPosix)}, ${JSON.stringify((process.env.NS_HMR_HOST || '')) || JSON.stringify('')} || ${JSON.stringify(defaultHost)}, __nsVerboseLog);\n`;
657
+ // Same device-reachable origin as `getBootOrigin()` so the
658
+ // session-descriptor URL and every `/ns/core/*` URL baked
659
+ // into bundle.mjs come from one canonical source. Without
660
+ // this, Android's `bundle.mjs` would hit `0.0.0.0:5173` for
661
+ // both endpoints (server.host's wildcard bind) and fail at
662
+ // boot before any user code runs.
663
+ const { origin: bootOrigin } = resolveDeviceReachableOrigin({
664
+ host: resolvedConfig.server.host,
665
+ platform: opts.platform,
666
+ protocol: resolvedConfig.server.https || opts.useHttps ? 'https' : 'http',
667
+ port: Number(resolvedConfig.server.port || 5173),
668
+ });
669
+ const sessionUrl = bootOrigin + '/__ns_dev__/session';
670
+ imports += "import { startBrowserRuntimeSession } from '@nativescript/vite/hmr/shared/runtime/session-bootstrap.js';\n";
671
+ imports += `startBrowserRuntimeSession(${JSON.stringify(sessionUrl)}, __nsVerboseLog).catch((error) => {\n`;
672
+ imports += ` try { globalThis.__NS_ENTRY_ERROR__ = { phase: 'deterministic-dev-session', message: String(error && (error.message || error)), stack: error && error.stack ? String(error.stack) : '' }; } catch {}\n`;
673
+ imports += ` console.error('[ns-entry] deterministic dev session bootstrap failed', error && error.stack ? error.stack : error);\n`;
674
+ imports += `});\n`;
350
675
  if (opts.verbose) {
351
- imports += `console.info('[ns-entry] HTTP-only boot code appended');\n`;
676
+ imports += `console.info('[ns-entry] deterministic dev session bootstrap appended');\n`;
352
677
  }
353
678
  }
354
679
  else {
@@ -359,7 +684,7 @@ export function mainEntryPlugin(opts) {
359
684
  }
360
685
  if (opts.isDevMode) {
361
686
  // debug tools support
362
- imports += "import '@nativescript/core/inspector_modules';\n";
687
+ imports += `import ${JSON.stringify(coreSpec('inspector_modules'))};\n`;
363
688
  if (opts.verbose) {
364
689
  imports += "console.info('[ns-entry] inspector modules imported');\n";
365
690
  }