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

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 (96) hide show
  1. package/configuration/angular.d.ts +1 -1
  2. package/configuration/angular.js +286 -119
  3. package/configuration/angular.js.map +1 -1
  4. package/configuration/base.js +40 -35
  5. package/configuration/base.js.map +1 -1
  6. package/configuration/javascript.js +3 -3
  7. package/configuration/javascript.js.map +1 -1
  8. package/configuration/solid.js +7 -0
  9. package/configuration/solid.js.map +1 -1
  10. package/configuration/typescript.js +3 -3
  11. package/configuration/typescript.js.map +1 -1
  12. package/helpers/angular/angular-linker.js +39 -34
  13. package/helpers/angular/angular-linker.js.map +1 -1
  14. package/helpers/angular/inline-decorator-component-templates.d.ts +3 -0
  15. package/helpers/angular/inline-decorator-component-templates.js +400 -0
  16. package/helpers/angular/inline-decorator-component-templates.js.map +1 -0
  17. package/helpers/angular/shared-linker.d.ts +7 -0
  18. package/helpers/angular/shared-linker.js +37 -1
  19. package/helpers/angular/shared-linker.js.map +1 -1
  20. package/helpers/angular/synthesize-decorator-ctor-parameters.d.ts +1 -0
  21. package/helpers/angular/synthesize-decorator-ctor-parameters.js +256 -0
  22. package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +1 -0
  23. package/helpers/angular/synthesize-injectable-factories.d.ts +3 -0
  24. package/helpers/angular/synthesize-injectable-factories.js +414 -0
  25. package/helpers/angular/synthesize-injectable-factories.js.map +1 -0
  26. package/helpers/esbuild-platform-resolver.js +5 -5
  27. package/helpers/esbuild-platform-resolver.js.map +1 -1
  28. package/helpers/external-configs.d.ts +9 -1
  29. package/helpers/external-configs.js +31 -6
  30. package/helpers/external-configs.js.map +1 -1
  31. package/helpers/import-meta-path.d.ts +4 -0
  32. package/helpers/import-meta-path.js +5 -0
  33. package/helpers/import-meta-path.js.map +1 -0
  34. package/helpers/import-specifier.d.ts +1 -0
  35. package/helpers/import-specifier.js +18 -0
  36. package/helpers/import-specifier.js.map +1 -0
  37. package/helpers/main-entry.d.ts +4 -2
  38. package/helpers/main-entry.js +86 -10
  39. package/helpers/main-entry.js.map +1 -1
  40. package/helpers/nativeclass-transform.js +8 -127
  41. package/helpers/nativeclass-transform.js.map +1 -1
  42. package/helpers/nativeclass-transformer-plugin.d.ts +12 -1
  43. package/helpers/nativeclass-transformer-plugin.js +175 -31
  44. package/helpers/nativeclass-transformer-plugin.js.map +1 -1
  45. package/helpers/preserve-imports.js +2 -17
  46. package/helpers/preserve-imports.js.map +1 -1
  47. package/hmr/client/css-handler.js +60 -20
  48. package/hmr/client/css-handler.js.map +1 -1
  49. package/hmr/client/index.js +453 -5
  50. package/hmr/client/index.js.map +1 -1
  51. package/hmr/entry-runtime.d.ts +10 -0
  52. package/hmr/entry-runtime.js +263 -21
  53. package/hmr/entry-runtime.js.map +1 -1
  54. package/hmr/frameworks/angular/client/index.js +22 -18
  55. package/hmr/frameworks/angular/client/index.js.map +1 -1
  56. package/hmr/frameworks/angular/server/linker.js +36 -2
  57. package/hmr/frameworks/angular/server/linker.js.map +1 -1
  58. package/hmr/helpers/ast-normalizer.js +22 -10
  59. package/hmr/helpers/ast-normalizer.js.map +1 -1
  60. package/hmr/server/constants.d.ts +1 -0
  61. package/hmr/server/constants.js +2 -0
  62. package/hmr/server/constants.js.map +1 -1
  63. package/hmr/server/core-sanitize.d.ts +42 -0
  64. package/hmr/server/core-sanitize.js +189 -0
  65. package/hmr/server/core-sanitize.js.map +1 -1
  66. package/hmr/server/import-map.d.ts +65 -0
  67. package/hmr/server/import-map.js +213 -0
  68. package/hmr/server/import-map.js.map +1 -0
  69. package/hmr/server/websocket.d.ts +79 -3
  70. package/hmr/server/websocket.js +1888 -233
  71. package/hmr/server/websocket.js.map +1 -1
  72. package/hmr/shared/runtime/dev-overlay.d.ts +38 -0
  73. package/hmr/shared/runtime/dev-overlay.js +664 -0
  74. package/hmr/shared/runtime/dev-overlay.js.map +1 -0
  75. package/hmr/shared/runtime/http-only-boot.d.ts +1 -0
  76. package/hmr/shared/runtime/http-only-boot.js +53 -6
  77. package/hmr/shared/runtime/http-only-boot.js.map +1 -1
  78. package/hmr/shared/runtime/module-provenance.d.ts +1 -0
  79. package/hmr/shared/runtime/module-provenance.js +66 -0
  80. package/hmr/shared/runtime/module-provenance.js.map +1 -0
  81. package/hmr/shared/runtime/platform-polyfills.d.ts +26 -0
  82. package/hmr/shared/runtime/platform-polyfills.js +122 -0
  83. package/hmr/shared/runtime/platform-polyfills.js.map +1 -0
  84. package/hmr/shared/runtime/root-placeholder.js +175 -53
  85. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  86. package/hmr/shared/runtime/vendor-bootstrap.js +51 -6
  87. package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
  88. package/hmr/shared/vendor/manifest.d.ts +5 -0
  89. package/hmr/shared/vendor/manifest.js +339 -13
  90. package/hmr/shared/vendor/manifest.js.map +1 -1
  91. package/hmr/shared/vendor/registry.js +104 -7
  92. package/hmr/shared/vendor/registry.js.map +1 -1
  93. package/package.json +6 -2
  94. package/shims/solid-jsx-runtime.d.ts +7 -0
  95. package/shims/solid-jsx-runtime.js +17 -0
  96. package/shims/solid-jsx-runtime.js.map +1 -0
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'node:module';
2
- import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCoreSpecToBridge } from './core-sanitize.js';
2
+ import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCoreSpecToBridge, isDeepCoreSubpath, rewriteSpecifiersForDevice } from './core-sanitize.js';
3
3
  // AST tooling for robust transformations
4
4
  import { parse as babelParse } from '@babel/parser';
5
5
  import { genCode } from '../helpers/babel.js';
@@ -30,6 +30,23 @@ import { typescriptServerStrategy } from '../frameworks/typescript/server/strate
30
30
  import { buildInlineTemplateBlock, createProcessSfcCode, extractTemplateRender, processTemplateVariantMinimal } from '../frameworks/vue/server/sfc-transforms.js';
31
31
  import { astExtractImportsAndStripTypes } from '../helpers/ast-extract.js';
32
32
  import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
33
+ import { buildRuntimeConfig, generateImportMap } from './import-map.js';
34
+ import { getCliFlags } from '../../helpers/cli-flags.js';
35
+ // Build a serialized process.env object from CLI --env.* flags.
36
+ // This is injected into every HTTP-served module so app code referencing
37
+ // process.env.TEST_ENV (etc.) works on device in HMR dev mode.
38
+ const __processEnvEntries = { NODE_ENV: 'development' };
39
+ try {
40
+ const flags = getCliFlags();
41
+ for (const [k, v] of Object.entries(flags)) {
42
+ // Skip internal NativeScript build flags
43
+ if (['ios', 'android', 'visionos', 'platform', 'hmr', 'verbose'].includes(k))
44
+ continue;
45
+ __processEnvEntries[k] = String(v);
46
+ }
47
+ }
48
+ catch { }
49
+ const __processEnvJson = JSON.stringify(__processEnvEntries);
33
50
  const { parse, compileTemplate, compileScript } = vueSfcCompiler;
34
51
  const APP_ROOT_DIR = getProjectAppPath();
35
52
  const APP_VIRTUAL_PREFIX = getProjectAppVirtualPath();
@@ -84,6 +101,33 @@ function isNativeScriptCoreModule(spec) {
84
101
  function isNativeScriptPluginModule(spec) {
85
102
  return /^@nativescript\//i.test(spec || '') && !isNativeScriptCoreModule(spec || '');
86
103
  }
104
+ function isLikelyNativeScriptRuntimePluginSpecifier(spec) {
105
+ if (!spec)
106
+ return false;
107
+ const s = spec.replace(PAT.QUERY_PATTERN, '');
108
+ if (/^(?:\.|\/|https?:\/\/)/i.test(s))
109
+ return false;
110
+ if (s.startsWith('@@/'))
111
+ return false;
112
+ if (s.startsWith('~/'))
113
+ return false;
114
+ if (s.startsWith('@/'))
115
+ return false;
116
+ if (/\.vue(?:\?|$)/i.test(s))
117
+ return false;
118
+ const root = extractRootPackageName(s) || s;
119
+ if (!root)
120
+ return false;
121
+ if (isNativeScriptCoreModule(root))
122
+ return false;
123
+ if (/^(?:vue|nativescript-vue)(?:\b|\/)/i.test(root))
124
+ return false;
125
+ if (/^@nativescript\//i.test(root))
126
+ return true;
127
+ if (/^(?:@nativescript-community|@nstudio|@mleleux)\//i.test(root))
128
+ return true;
129
+ return /(?:^|\/)nativescript(?:$|[-_])/i.test(root);
130
+ }
87
131
  // Looser detector for NativeScript plugin-style specifiers that should be resolved
88
132
  // via device require() rather than HTTP during HMR. This includes popular community
89
133
  // scopes in addition to @nativescript/* (excluding core).
@@ -112,7 +156,518 @@ function isLikelyNativeScriptPluginSpecifier(spec) {
112
156
  // Treat any other bare package id as device-resolved (require) during HMR
113
157
  return true;
114
158
  }
115
- export function ensureNativeScriptModuleBindings(code) {
159
+ export function tryReadRawExplicitJavaScriptModule(spec, projectRoot) {
160
+ if (!spec || !spec.startsWith('/'))
161
+ return null;
162
+ if (spec.startsWith('/@id/') || spec.startsWith('/@fs/'))
163
+ return null;
164
+ if (!/\.js$/i.test(spec) || /\.(?:mjs|cjs)$/i.test(spec))
165
+ return null;
166
+ // NativeScript runtime plugins rely on Vite's transform pipeline to apply
167
+ // platform-aware entry resolution and preserve root-entry routing semantics.
168
+ // Reading their on-disk JS directly recreates raw relative imports such as
169
+ // `./gesturehandler`, which can reintroduce HTTP ESM cycles during HMR.
170
+ try {
171
+ const nodeModulesSpecifier = normalizeNodeModulesSpecifier(spec);
172
+ if (nodeModulesSpecifier) {
173
+ const pkgName = extractRootPackageName(nodeModulesSpecifier);
174
+ if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName)) {
175
+ return null;
176
+ }
177
+ }
178
+ }
179
+ catch { }
180
+ const root = path.resolve(projectRoot);
181
+ const absPath = path.resolve(root, `.${spec}`);
182
+ const rel = path.relative(root, absPath);
183
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel))
184
+ return null;
185
+ if (!existsSync(absPath))
186
+ return null;
187
+ try {
188
+ const code = readFileSync(absPath, 'utf-8');
189
+ if (/from\s*['"](?:node:)?module['"]/.test(code) || /\bcreateRequire\b/.test(code)) {
190
+ return null;
191
+ }
192
+ return {
193
+ code,
194
+ resolvedId: spec,
195
+ };
196
+ }
197
+ catch {
198
+ return null;
199
+ }
200
+ }
201
+ function canonicalizeTransformRequestCacheKey(url, projectRoot) {
202
+ if (!url)
203
+ return url;
204
+ const [rawPath, rawQuery = ''] = url.split('?', 2);
205
+ let normalizedPath = rawPath;
206
+ const root = projectRoot ? projectRoot.replace(/\\/g, '/') : '';
207
+ if (normalizedPath.startsWith('/@fs/')) {
208
+ const fsPath = normalizedPath.slice('/@fs'.length).replace(/\\/g, '/');
209
+ if (root && fsPath.startsWith(root)) {
210
+ const rel = fsPath.slice(root.length);
211
+ normalizedPath = rel.startsWith('/') ? rel : `/${rel}`;
212
+ }
213
+ }
214
+ else if (root && normalizedPath.replace(/\\/g, '/').startsWith(root)) {
215
+ const rel = normalizedPath.replace(/\\/g, '/').slice(root.length);
216
+ normalizedPath = rel.startsWith('/') ? rel : `/${rel}`;
217
+ }
218
+ if (!rawQuery) {
219
+ return normalizedPath;
220
+ }
221
+ const params = new URLSearchParams(rawQuery);
222
+ params.delete('t');
223
+ params.delete('v');
224
+ const kept = Array.from(params.entries()).sort(([leftKey, leftValue], [rightKey, rightValue]) => {
225
+ if (leftKey === rightKey) {
226
+ return leftValue.localeCompare(rightValue);
227
+ }
228
+ return leftKey.localeCompare(rightKey);
229
+ });
230
+ if (!kept.length) {
231
+ return normalizedPath;
232
+ }
233
+ const normalizedQuery = new URLSearchParams();
234
+ for (const [key, value] of kept) {
235
+ normalizedQuery.append(key, value);
236
+ }
237
+ return `${normalizedPath}?${normalizedQuery.toString()}`;
238
+ }
239
+ const MODULE_IMPORT_ANALYSIS_PLUGINS = ['typescript', 'jsx', 'importMeta', 'topLevelAwait', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'decorators-legacy'];
240
+ function collectTopLevelImportRecords(code) {
241
+ if (!code || typeof code !== 'string' || !/\bimport\b/.test(code)) {
242
+ return [];
243
+ }
244
+ try {
245
+ const ast = babelParse(code, {
246
+ sourceType: 'module',
247
+ plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
248
+ });
249
+ const body = ast?.program?.body;
250
+ if (!Array.isArray(body)) {
251
+ return [];
252
+ }
253
+ return body
254
+ .filter((node) => t.isImportDeclaration(node) && typeof node.start === 'number' && typeof node.end === 'number' && typeof node.source?.value === 'string')
255
+ .map((node) => ({
256
+ start: node.start,
257
+ end: node.end,
258
+ text: code.slice(node.start, node.end),
259
+ source: node.source.value,
260
+ hasOnlyNamedSpecifiers: Array.isArray(node.specifiers) && node.specifiers.length > 0 && node.specifiers.every((spec) => t.isImportSpecifier(spec)),
261
+ namedBindings: Array.isArray(node.specifiers)
262
+ ? node.specifiers
263
+ .filter((spec) => t.isImportSpecifier(spec) && typeof spec.start === 'number' && typeof spec.end === 'number')
264
+ .map((spec) => ({
265
+ importedName: t.isIdentifier(spec.imported) ? spec.imported.name : String(spec.imported?.value || ''),
266
+ text: code.slice(spec.start, spec.end),
267
+ }))
268
+ : [],
269
+ }));
270
+ }
271
+ catch {
272
+ return [];
273
+ }
274
+ }
275
+ function hoistTopLevelStaticImports(code) {
276
+ const imports = collectTopLevelImportRecords(code);
277
+ if (!imports.length) {
278
+ return code;
279
+ }
280
+ let stripped = code;
281
+ for (const imp of [...imports].sort((left, right) => right.start - left.start)) {
282
+ stripped = stripped.slice(0, imp.start) + stripped.slice(imp.end);
283
+ }
284
+ const hoisted = [];
285
+ const seen = new Set();
286
+ for (const imp of imports) {
287
+ const text = imp.text.trim();
288
+ if (!text || seen.has(text)) {
289
+ continue;
290
+ }
291
+ seen.add(text);
292
+ hoisted.push(text);
293
+ }
294
+ if (!hoisted.length) {
295
+ return stripped;
296
+ }
297
+ return `${hoisted.join('\n')}\n${stripped.replace(/^\s*\n+/, '')}`;
298
+ }
299
+ export function buildBootProgressSnippet(bootModuleLabel) {
300
+ const normalizedLabel = JSON.stringify(String(bootModuleLabel || '').replace(/\\/g, '/'));
301
+ return [
302
+ `const __nsBootGlobal=globalThis;`,
303
+ `try{if(!__nsBootGlobal.__NS_HMR_BOOT_COMPLETE__){const __nsBootApi=__nsBootGlobal.__NS_HMR_DEV_OVERLAY__;if(__nsBootApi&&typeof __nsBootApi.setBootStage==='function'){const __nsBootCount=(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__=Number(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__||0)+1);__nsBootGlobal.__NS_HMR_BOOT_LAST_MODULE__=${normalizedLabel};const __nsBootNow=Date.now();const __nsBootLast=Number(__nsBootGlobal.__NS_HMR_BOOT_LAST_PROGRESS_AT__||0);if(__nsBootCount<=8||__nsBootCount%6===0||__nsBootNow-__nsBootLast>90){__nsBootGlobal.__NS_HMR_BOOT_LAST_PROGRESS_AT__=__nsBootNow;const __nsBootProgress=Math.min(94,82+Math.min(10,Math.round((Math.log(__nsBootCount+1)/Math.LN2)*2)));__nsBootApi.setBootStage('importing-main',{detail:'Evaluated '+__nsBootCount+' modules\\n'+__nsBootGlobal.__NS_HMR_BOOT_LAST_MODULE__,attempt:Number(__nsBootGlobal.__NS_HMR_BOOT_MAIN_ATTEMPT__||1),attempts:Number(__nsBootGlobal.__NS_HMR_BOOT_MAIN_ATTEMPTS__||6),progress:__nsBootProgress});}}}}catch(__nsBootErr){}`,
304
+ `if(!__nsBootGlobal.__NS_HMR_BOOT_COMPLETE__){const __nsBootCount=Number(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__||0);if(__nsBootCount<=24||__nsBootCount%8===0){await new Promise((resolve)=>setTimeout(resolve,0));}}`,
305
+ '',
306
+ ].join('\n');
307
+ }
308
+ function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
309
+ const imports = collectTopLevelImportRecords(code);
310
+ if (!imports.length) {
311
+ return code;
312
+ }
313
+ const edits = [];
314
+ for (const imp of imports) {
315
+ const source = imp.source;
316
+ const depMatch = source.match(/(?:^|\/)node_modules\/\.vite\/deps\/(.+)$/);
317
+ const depPath = depMatch?.[1] || (source.startsWith('.vite/deps/') ? source.slice('.vite/deps/'.length) : null);
318
+ if (!depPath) {
319
+ continue;
320
+ }
321
+ let replacement = '';
322
+ if (preserveVendorImports) {
323
+ const canonical = resolveVendorFromCandidate(`.vite/deps/${depPath}`);
324
+ const bareSpecifier = canonical || viteDepsPathToBareSpecifier(depPath);
325
+ if (bareSpecifier) {
326
+ replacement = imp.text.replace(source, bareSpecifier);
327
+ }
328
+ }
329
+ edits.push({
330
+ start: imp.start,
331
+ end: imp.end,
332
+ text: replacement,
333
+ });
334
+ }
335
+ if (!edits.length) {
336
+ return code;
337
+ }
338
+ let next = code;
339
+ for (const edit of edits.sort((left, right) => right.start - left.start)) {
340
+ next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
341
+ }
342
+ return next;
343
+ }
344
+ function buildNodeModuleProvenancePrelude(sourceId) {
345
+ if (!sourceId) {
346
+ return '';
347
+ }
348
+ const cleaned = sourceId.replace(PAT.QUERY_PATTERN, '');
349
+ let normalized = normalizeNodeModulesSpecifier(cleaned);
350
+ if (!normalized) {
351
+ const viteDepsMatch = cleaned.match(/(?:^|\/)node_modules\/\.vite\/deps\/([^?#]+)/);
352
+ if (viteDepsMatch?.[1]) {
353
+ normalized = `.vite/deps/${viteDepsMatch[1]}`;
354
+ }
355
+ }
356
+ if (!normalized) {
357
+ return '';
358
+ }
359
+ let packageSpecifier = normalized;
360
+ let via = 'node_modules';
361
+ if (normalized.startsWith('.vite/deps/')) {
362
+ via = 'vite-deps';
363
+ packageSpecifier = viteDepsPathToBareSpecifier(normalized.slice('.vite/deps/'.length)) || normalized;
364
+ }
365
+ const rootPackage = extractRootPackageName(packageSpecifier);
366
+ if (!rootPackage) {
367
+ return '';
368
+ }
369
+ return `try { const __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(${JSON.stringify(rootPackage)}, ${JSON.stringify({ kind: 'http-esm', specifier: packageSpecifier, url: sourceId, via })}); } } catch {}\n`;
370
+ }
371
+ export function collectGraphUpdateModulesForHotUpdate(options) {
372
+ const targets = new Map();
373
+ const addTarget = (mod) => {
374
+ const id = mod?.id?.replace(/\?.*$/, '');
375
+ if (!id)
376
+ return;
377
+ if (!targets.has(id)) {
378
+ targets.set(id, mod);
379
+ }
380
+ };
381
+ if (options.flavor === 'angular' && /\.(html|htm)$/i.test(options.file)) {
382
+ for (const mod of options.modules || []) {
383
+ for (const importer of mod?.importers || []) {
384
+ const importerId = importer?.id || '';
385
+ if (/\.[cm]?[jt]sx?(?:$|\?)/i.test(importerId)) {
386
+ addTarget(importer);
387
+ }
388
+ }
389
+ }
390
+ if (!targets.size) {
391
+ addTarget(options.getModuleById(options.file.replace(/\.(html|htm)$/i, '.ts')));
392
+ addTarget(options.getModuleById(options.file.replace(/\.(html|htm)$/i, '.js')));
393
+ }
394
+ return Array.from(targets.values());
395
+ }
396
+ if (!options.file.endsWith('.vue')) {
397
+ addTarget(options.getModuleById(options.file) || options.getModuleById(options.file + '?vue'));
398
+ }
399
+ return Array.from(targets.values());
400
+ }
401
+ export function collectAngularHotUpdateRoots(options) {
402
+ const roots = [];
403
+ const seenIds = new Set();
404
+ const seenObjects = new Set();
405
+ const addRoot = (mod) => {
406
+ if (!mod) {
407
+ return;
408
+ }
409
+ if (mod.id) {
410
+ if (seenIds.has(mod.id)) {
411
+ return;
412
+ }
413
+ seenIds.add(mod.id);
414
+ roots.push(mod);
415
+ return;
416
+ }
417
+ if (seenObjects.has(mod)) {
418
+ return;
419
+ }
420
+ seenObjects.add(mod);
421
+ roots.push(mod);
422
+ };
423
+ if (/\.(html|htm)$/i.test(options.file)) {
424
+ for (const mod of collectGraphUpdateModulesForHotUpdate({
425
+ file: options.file,
426
+ flavor: 'angular',
427
+ modules: options.modules,
428
+ getModuleById: options.getModuleById,
429
+ })) {
430
+ addRoot(mod);
431
+ }
432
+ return roots;
433
+ }
434
+ if (!/\.(m|c)?ts$/i.test(options.file)) {
435
+ return roots;
436
+ }
437
+ for (const mod of options.modules || []) {
438
+ addRoot(mod);
439
+ }
440
+ for (const mod of options.getModulesByFile?.(options.file) || []) {
441
+ addRoot(mod);
442
+ }
443
+ if (!roots.length) {
444
+ addRoot(options.getModuleById(options.file));
445
+ }
446
+ return roots;
447
+ }
448
+ export function collectAngularTransitiveImportersForInvalidation(options) {
449
+ const visited = new Set();
450
+ const collected = new Map();
451
+ const isExcluded = options.isExcluded ?? ((id) => id.includes('/node_modules/'));
452
+ const maxDepth = Math.max(1, Math.floor(options.maxDepth ?? 16));
453
+ const normalizeId = (value) => (value ?? '').replace(/\?.*$/, '');
454
+ const walk = (mod, depth) => {
455
+ if (!mod || visited.has(mod)) {
456
+ return;
457
+ }
458
+ visited.add(mod);
459
+ if (depth >= maxDepth) {
460
+ return;
461
+ }
462
+ const importers = mod.importers;
463
+ if (!importers) {
464
+ return;
465
+ }
466
+ for (const importer of importers) {
467
+ if (!importer)
468
+ continue;
469
+ const importerId = normalizeId(importer.id);
470
+ if (!importerId) {
471
+ walk(importer, depth + 1);
472
+ continue;
473
+ }
474
+ if (isExcluded(importerId)) {
475
+ continue;
476
+ }
477
+ if (!collected.has(importerId)) {
478
+ collected.set(importerId, importer);
479
+ }
480
+ walk(importer, depth + 1);
481
+ }
482
+ };
483
+ for (const mod of options.modules || []) {
484
+ walk(mod, 0);
485
+ }
486
+ return Array.from(collected.values());
487
+ }
488
+ export function shouldInvalidateAngularTransitiveImporters(options) {
489
+ if (options.flavor !== 'angular') {
490
+ return false;
491
+ }
492
+ return /\.(?:html|htm|(m|c)?[jt]sx?)$/i.test(options.file);
493
+ }
494
+ export function shouldSuppressDefaultViteHotUpdate(options) {
495
+ if (options.flavor !== 'angular') {
496
+ return false;
497
+ }
498
+ return /\.(html|htm|ts)$/i.test(options.file);
499
+ }
500
+ export function normalizeHotReloadMatchPath(raw, root) {
501
+ let normalized = String(raw || '')
502
+ .split('?')[0]
503
+ .replace(/\\/g, '/')
504
+ .replace(/^file:\/\//, '');
505
+ if (root) {
506
+ const rootNormalized = root.replace(/\\/g, '/');
507
+ if (normalized.startsWith(rootNormalized)) {
508
+ normalized = normalized.slice(rootNormalized.length);
509
+ }
510
+ }
511
+ if (!normalized.startsWith('/')) {
512
+ normalized = `/${normalized}`;
513
+ }
514
+ return normalized;
515
+ }
516
+ export function shouldSuppressViteFullReloadPayload(options) {
517
+ const { payload, pendingEntries, root } = options;
518
+ const now = options.now ?? Date.now();
519
+ if (!payload || payload.type !== 'full-reload') {
520
+ return false;
521
+ }
522
+ const payloadPath = typeof payload.path === 'string' && payload.path !== '*' ? normalizeHotReloadMatchPath(payload.path, root) : null;
523
+ const payloadTriggeredBy = typeof payload.triggeredBy === 'string' ? normalizeHotReloadMatchPath(payload.triggeredBy, root) : null;
524
+ for (const entry of pendingEntries) {
525
+ if (!entry || entry.expiresAt <= now) {
526
+ continue;
527
+ }
528
+ if (payloadTriggeredBy === entry.absPath || payloadTriggeredBy === entry.relPath || payloadPath === entry.relPath || payloadPath === entry.absPath) {
529
+ return true;
530
+ }
531
+ }
532
+ return false;
533
+ }
534
+ export function createSharedTransformRequestRunner(transformRequest, onTimeout, options = {}) {
535
+ const inFlight = new Map();
536
+ const recentResults = new Map();
537
+ const cacheGenerations = new Map();
538
+ const queue = [];
539
+ const maxConcurrent = Math.max(1, Math.floor(options.maxConcurrent ?? 1));
540
+ const resultCacheTtlMs = Math.max(0, Math.floor(options.resultCacheTtlMs ?? 0));
541
+ const getResultCacheKey = options.getResultCacheKey ?? ((url) => url);
542
+ let activeCount = 0;
543
+ const getCacheGeneration = (cacheKey) => cacheGenerations.get(cacheKey) ?? 0;
544
+ const invalidateCacheKey = (cacheKey) => {
545
+ cacheGenerations.set(cacheKey, getCacheGeneration(cacheKey) + 1);
546
+ recentResults.delete(cacheKey);
547
+ };
548
+ const pruneRecentResults = () => {
549
+ if (!recentResults.size) {
550
+ return;
551
+ }
552
+ const now = Date.now();
553
+ for (const [key, entry] of recentResults) {
554
+ if (entry.expiresAt <= now) {
555
+ recentResults.delete(key);
556
+ }
557
+ }
558
+ };
559
+ const rememberRecentResult = (url, result, generation) => {
560
+ if (!result || resultCacheTtlMs <= 0) {
561
+ return;
562
+ }
563
+ const cacheKey = getResultCacheKey(url);
564
+ if (getCacheGeneration(cacheKey) !== generation) {
565
+ return;
566
+ }
567
+ recentResults.delete(cacheKey);
568
+ recentResults.set(cacheKey, {
569
+ expiresAt: Date.now() + resultCacheTtlMs,
570
+ result,
571
+ });
572
+ if (recentResults.size > 512) {
573
+ const oldestKey = recentResults.keys().next().value;
574
+ if (oldestKey) {
575
+ recentResults.delete(oldestKey);
576
+ }
577
+ }
578
+ };
579
+ const runNext = () => {
580
+ while (activeCount < maxConcurrent) {
581
+ const next = queue.shift();
582
+ if (!next) {
583
+ return;
584
+ }
585
+ activeCount += 1;
586
+ next();
587
+ }
588
+ };
589
+ const schedule = (task) => {
590
+ let resolveStarted = null;
591
+ const started = new Promise((resolve) => {
592
+ resolveStarted = resolve;
593
+ });
594
+ const execution = new Promise((resolve, reject) => {
595
+ queue.push(() => {
596
+ let started;
597
+ resolveStarted?.();
598
+ try {
599
+ started = Promise.resolve(task());
600
+ }
601
+ catch (error) {
602
+ started = Promise.reject(error);
603
+ }
604
+ started.then(resolve, reject).finally(() => {
605
+ activeCount = Math.max(0, activeCount - 1);
606
+ runNext();
607
+ });
608
+ });
609
+ runNext();
610
+ });
611
+ return { execution, started };
612
+ };
613
+ const withTimeout = (entry, url, timeoutMs) => {
614
+ if (!(timeoutMs > 0)) {
615
+ return entry.execution;
616
+ }
617
+ return entry.started.then(() => new Promise((resolve, reject) => {
618
+ const timer = setTimeout(() => {
619
+ try {
620
+ onTimeout?.(url, timeoutMs);
621
+ }
622
+ catch { }
623
+ }, timeoutMs);
624
+ entry.execution.then(resolve, reject).finally(() => {
625
+ clearTimeout(timer);
626
+ });
627
+ }));
628
+ };
629
+ const runner = ((url, timeoutMs = 120000) => {
630
+ pruneRecentResults();
631
+ const cacheKey = getResultCacheKey(url);
632
+ const generation = getCacheGeneration(cacheKey);
633
+ const recent = recentResults.get(cacheKey);
634
+ if (recent && recent.expiresAt > Date.now()) {
635
+ return Promise.resolve(recent.result);
636
+ }
637
+ const existingExecution = inFlight.get(url);
638
+ if (existingExecution && existingExecution.generation === generation && existingExecution.cacheKey === cacheKey) {
639
+ return withTimeout(existingExecution, url, timeoutMs);
640
+ }
641
+ const scheduled = schedule(async () => {
642
+ const result = await Promise.resolve(transformRequest(url));
643
+ rememberRecentResult(url, result, generation);
644
+ return result;
645
+ });
646
+ let execution;
647
+ execution = scheduled.execution.finally(() => {
648
+ if (inFlight.get(url)?.execution === execution) {
649
+ inFlight.delete(url);
650
+ }
651
+ });
652
+ const entry = { execution, started: scheduled.started, cacheKey, generation };
653
+ inFlight.set(url, entry);
654
+ return withTimeout(entry, url, timeoutMs);
655
+ });
656
+ runner.invalidate = (url) => {
657
+ invalidateCacheKey(getResultCacheKey(url));
658
+ };
659
+ runner.invalidateMany = (urls) => {
660
+ for (const url of urls || []) {
661
+ runner.invalidate(url);
662
+ }
663
+ };
664
+ runner.clear = () => {
665
+ recentResults.clear();
666
+ cacheGenerations.clear();
667
+ };
668
+ return runner;
669
+ }
670
+ export function ensureNativeScriptModuleBindings(code, options) {
116
671
  // Proceed even if a vendor manifest isn't available; we'll still vendor-bind
117
672
  // likely NativeScript plugin-style specifiers (e.g., 'pinia', '@scope/pkg')
118
673
  // via require() so device can resolve them from the app bundle.
@@ -165,6 +720,11 @@ export function ensureNativeScriptModuleBindings(code) {
165
720
  }
166
721
  const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
167
722
  let canonical = resolveVendorFromCandidate(specifier);
723
+ const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier);
724
+ if (options?.preserveNonPluginVendorImports && !runtimePluginSpecifier) {
725
+ preservedImports.push(original);
726
+ return pfx || '';
727
+ }
168
728
  // If not found in vendor manifest, treat well-known NativeScript plugin-style packages
169
729
  // as require() based modules so the device can resolve them from the app bundle or vendor.
170
730
  if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
@@ -216,6 +776,11 @@ export function ensureNativeScriptModuleBindings(code) {
216
776
  const original = full.replace(/^\n/, '');
217
777
  const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
218
778
  let canonical = resolveVendorFromCandidate(specifier);
779
+ const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier);
780
+ if (options?.preserveNonPluginVendorImports && !runtimePluginSpecifier) {
781
+ preservedImports.push(original);
782
+ return _pfx || '';
783
+ }
219
784
  if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
220
785
  canonical = specifier;
221
786
  }
@@ -318,6 +883,34 @@ function guardBareDynamicImports(code) {
318
883
  return code;
319
884
  }
320
885
  }
886
+ function ensureDynamicHmrImportHelper(code) {
887
+ try {
888
+ if (!code.includes('__nsDynamicHmrImport('))
889
+ return code;
890
+ if (code.includes('const __nsDynamicHmrImport ='))
891
+ return code;
892
+ const helper = 'const __nsDynamicHmrImport = (spec) => {\n' +
893
+ " const __nsm = '/ns' + '/m';\n" +
894
+ " try { if (!spec || spec === '@') { return import(new URL(__nsm + '/__invalid_at__.mjs', import.meta.url).href); } } catch {}\n" +
895
+ ' try {\n' +
896
+ " if (typeof spec === 'string' && spec.startsWith(__nsm + '/')) {\n" +
897
+ " if (spec.includes('/__ns_hmr__/')) { return import(new URL(spec, import.meta.url).href); }\n" +
898
+ ' const g = globalThis;\n' +
899
+ " const nonce = typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0;\n" +
900
+ " const tag = nonce ? `n${nonce}` : 'live';\n" +
901
+ " const nextPath = __nsm + '/__ns_hmr__/' + encodeURIComponent(tag) + spec.slice(__nsm.length);\n" +
902
+ " const origin = typeof g.__NS_HTTP_ORIGIN__ === 'string' && /^https?:\\/\\//.test(g.__NS_HTTP_ORIGIN__) ? g.__NS_HTTP_ORIGIN__ : '';\n" +
903
+ ' return import(origin ? origin + nextPath : new URL(nextPath, import.meta.url).href);\n' +
904
+ ' }\n' +
905
+ ' } catch {}\n' +
906
+ ' return import(spec);\n' +
907
+ '};\n';
908
+ return helper + code;
909
+ }
910
+ catch {
911
+ return code;
912
+ }
913
+ }
321
914
  function normalizeNativeScriptCoreSpecifier(spec) {
322
915
  let normalized = spec.replace(/@nativescript[_-]core/gi, '@nativescript/core').replace(/@nativescript\/core\/index\.js$/i, '@nativescript/core/index.js');
323
916
  if (normalized.startsWith('/node_modules/')) {
@@ -331,7 +924,7 @@ function normalizeNativeScriptCoreSpecifier(spec) {
331
924
  }
332
925
  return normalized;
333
926
  }
334
- function normalizeNodeModulesSpecifier(spec) {
927
+ export function normalizeNodeModulesSpecifier(spec) {
335
928
  if (!spec) {
336
929
  return null;
337
930
  }
@@ -354,7 +947,7 @@ function normalizeNodeModulesSpecifier(spec) {
354
947
  }
355
948
  return subPath.startsWith('/') ? subPath.slice(1) : subPath;
356
949
  }
357
- function resolveVendorFromCandidate(specifier) {
950
+ export function resolveVendorFromCandidate(specifier) {
358
951
  if (!specifier) {
359
952
  return null;
360
953
  }
@@ -375,9 +968,27 @@ function resolveVendorFromCandidate(specifier) {
375
968
  return flatMatch;
376
969
  }
377
970
  for (const [flatKey, canonical] of flattenedMap.entries()) {
378
- if (flattenedId === flatKey || flattenedId.startsWith(`${flatKey}_`)) {
971
+ if (flattenedId === flatKey) {
379
972
  return canonical;
380
973
  }
974
+ if (flattenedId.startsWith(`${flatKey}_`)) {
975
+ // The suffix after the flat key represents a subpath.
976
+ // Convert it back: e.g., "_solid" → "solid", "_store_dist_x" → "store/dist/x"
977
+ // Only resolve to the root vendor module if it's a file/dist subpath.
978
+ // Entry-point subpaths (e.g., _solid, _store) have different exports
979
+ // and must NOT be collapsed to the root.
980
+ const flatSuffix = flattenedId.slice(flatKey.length + 1);
981
+ const subpath = flatSuffix.replace(/_/g, '/');
982
+ if (isFileDistSubpath(subpath)) {
983
+ return canonical;
984
+ }
985
+ // Check if there's an alias for this subpath entry
986
+ const aliasKey = `${canonical}/${subpath.split('/')[0]}`;
987
+ if (manifest.aliases?.[aliasKey] && manifest.modules[manifest.aliases[aliasKey]]) {
988
+ return manifest.aliases[aliasKey];
989
+ }
990
+ // Entry-point subpath — don't collapse to root
991
+ }
381
992
  }
382
993
  const guessedId = flattenedId.replace(/__/g, '.').replace(/_/g, '/');
383
994
  if (guessedId && guessedId !== flattenedId) {
@@ -415,16 +1026,279 @@ function resolveVendorFromCandidate(specifier) {
415
1026
  }
416
1027
  return null;
417
1028
  }
1029
+ function resolveCandidateFilePath(candidate, projectRoot) {
1030
+ const cleaned = candidate.replace(PAT.QUERY_PATTERN, '');
1031
+ if (!cleaned)
1032
+ return null;
1033
+ const root = path.resolve(projectRoot);
1034
+ let absPath = null;
1035
+ if (cleaned.startsWith('/@fs/')) {
1036
+ absPath = cleaned.slice('/@fs'.length);
1037
+ }
1038
+ else if (cleaned.includes('/node_modules/')) {
1039
+ absPath = path.resolve(root, `.${cleaned}`);
1040
+ }
1041
+ else if (/^(?:[A-Za-z]:)?\//.test(cleaned)) {
1042
+ absPath = path.resolve(cleaned);
1043
+ }
1044
+ if (!absPath) {
1045
+ return null;
1046
+ }
1047
+ const rel = path.relative(root, absPath);
1048
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
1049
+ return null;
1050
+ }
1051
+ return existsSync(absPath) ? absPath : null;
1052
+ }
1053
+ export function filterExistingNodeModulesTransformCandidates(spec, candidates, projectRoot) {
1054
+ const cleanedSpec = spec.replace(PAT.QUERY_PATTERN, '');
1055
+ if (!cleanedSpec.includes('/node_modules/')) {
1056
+ return candidates;
1057
+ }
1058
+ return candidates.filter((candidate) => !!resolveCandidateFilePath(candidate, projectRoot));
1059
+ }
418
1060
  function findVendorPrefix(specifier, manifest) {
419
- const { modules } = manifest;
1061
+ const { modules, aliases } = manifest;
420
1062
  const keys = Object.keys(modules || {});
421
1063
  for (const key of keys) {
422
- if (specifier === key || specifier.startsWith(`${key}/`)) {
1064
+ if (specifier === key) {
423
1065
  return key;
424
1066
  }
1067
+ if (specifier.startsWith(`${key}/`)) {
1068
+ const subpath = specifier.slice(key.length + 1);
1069
+ // Only match file/dist subpaths (e.g., solid-js/dist/dev.js → solid-js).
1070
+ // Entry-point subpaths (e.g., solid-js/store) are separate packages
1071
+ // and must NOT be collapsed to the root vendor module.
1072
+ if (isFileDistSubpath(subpath)) {
1073
+ return key;
1074
+ }
1075
+ // Check if there's an explicit alias for this subpath
1076
+ const aliasKey = `${key}/${subpath.split('/')[0]}`;
1077
+ if (aliases?.[aliasKey] && modules[aliases[aliasKey]]) {
1078
+ return aliases[aliasKey];
1079
+ }
1080
+ // Entry-point subpath with no alias — don't vendor-resolve
1081
+ continue;
1082
+ }
1083
+ }
1084
+ return null;
1085
+ }
1086
+ /**
1087
+ * Convert a Vite .vite/deps/ filename back to a bare specifier with subpath,
1088
+ * using the vendor manifest to determine where the package name ends and the
1089
+ * subpath begins.
1090
+ *
1091
+ * e.g., "@nativescript_tanstack-router_solid.js" → "@nativescript/tanstack-router/solid"
1092
+ * "solid-js.js" → "solid-js"
1093
+ *
1094
+ * Returns null if no known package prefix matches.
1095
+ */
1096
+ function viteDepsPathToBareSpecifier(depPath) {
1097
+ const manifest = getVendorManifest();
1098
+ if (!manifest)
1099
+ return null;
1100
+ const flatId = extractVitePrebundleId(`.vite/deps/${depPath}`);
1101
+ if (!flatId)
1102
+ return null;
1103
+ const flatMap = getFlattenedManifestMap(manifest);
1104
+ // Try exact match first
1105
+ if (flatMap.has(flatId)) {
1106
+ return flatMap.get(flatId);
1107
+ }
1108
+ // Try prefix match: find the longest matching vendor flat key
1109
+ let bestKey = '';
1110
+ let bestCanonical = '';
1111
+ for (const [flatKey, canonical] of flatMap.entries()) {
1112
+ if (flatId.startsWith(`${flatKey}_`) && flatKey.length > bestKey.length) {
1113
+ bestKey = flatKey;
1114
+ bestCanonical = canonical;
1115
+ }
1116
+ }
1117
+ if (bestKey && bestCanonical) {
1118
+ // Convert the suffix back to a subpath
1119
+ const flatSuffix = flatId.slice(bestKey.length + 1);
1120
+ const subpath = flatSuffix.replace(/_/g, '/');
1121
+ return `${bestCanonical}/${subpath}`;
425
1122
  }
426
1123
  return null;
427
1124
  }
1125
+ function isFileDistSubpath(subpath) {
1126
+ const firstSegment = subpath.split('/')[0];
1127
+ // Starts with a known build/dist directory segment → file path
1128
+ const FILE_DIST_DIRS = new Set(['dist', 'src', 'lib', 'build', 'esm', 'cjs', 'es', 'umd', 'module', 'bundle', 'output', '_esm', '_cjs']);
1129
+ if (FILE_DIST_DIRS.has(firstSegment)) {
1130
+ return true;
1131
+ }
1132
+ // Single segment with file extension → file path (e.g., "index.js")
1133
+ if (!subpath.includes('/') && /\.[a-zA-Z0-9]+$/.test(subpath)) {
1134
+ return true;
1135
+ }
1136
+ return false;
1137
+ }
1138
+ // ── Package exports reverse map ──────────────────────────────────────────────
1139
+ // Resolves Vite's resolved file paths back to original bare specifiers using
1140
+ // the package's own package.json exports field. This eliminates the fragile
1141
+ // heuristic that tried to guess main entry vs. subpath from file paths.
1142
+ const _exportsReverseMapCache = new Map();
1143
+ /**
1144
+ * Resolve the concrete file path from a package.json exports condition value.
1145
+ * Handles nested condition objects: { "esm2022": { "default": "./file.mjs" } }
1146
+ */
1147
+ function resolveExportConditionValue(conditions) {
1148
+ if (typeof conditions === 'string')
1149
+ return conditions;
1150
+ if (typeof conditions !== 'object' || conditions === null)
1151
+ return null;
1152
+ const obj = conditions;
1153
+ for (const key of ['esm2022', 'esm', 'esm2015', 'import', 'module', 'default']) {
1154
+ if (key in obj) {
1155
+ const result = resolveExportConditionValue(obj[key]);
1156
+ if (result)
1157
+ return result;
1158
+ }
1159
+ }
1160
+ for (const val of Object.values(obj)) {
1161
+ if (typeof val === 'string')
1162
+ return val;
1163
+ }
1164
+ return null;
1165
+ }
1166
+ /**
1167
+ * Build a reverse map from file paths → bare specifiers for a package.
1168
+ *
1169
+ * Example for @angular/common:
1170
+ * "fesm2022/common.mjs" → "@angular/common" (exports["."])
1171
+ * "fesm2022/http.mjs" → "@angular/common/http" (exports["./http"])
1172
+ *
1173
+ * For packages without exports field, uses main/module fields.
1174
+ */
1175
+ function getExportsReverseMap(pkgName, projectRoot) {
1176
+ const cached = _exportsReverseMapCache.get(pkgName);
1177
+ if (cached)
1178
+ return cached;
1179
+ const map = new Map();
1180
+ try {
1181
+ const pkgJsonPath = path.join(projectRoot, 'node_modules', pkgName, 'package.json');
1182
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
1183
+ if (pkgJson.exports && typeof pkgJson.exports === 'object') {
1184
+ for (const [entryPoint, conditions] of Object.entries(pkgJson.exports)) {
1185
+ // Skip wildcard patterns (./locales/*, etc.) and non-JS exports
1186
+ if (entryPoint.includes('*') || entryPoint.endsWith('.json'))
1187
+ continue;
1188
+ const resolvedPath = resolveExportConditionValue(conditions);
1189
+ if (resolvedPath && typeof resolvedPath === 'string') {
1190
+ const normalized = resolvedPath.replace(/^\.\//, '');
1191
+ const bareSpec = entryPoint === '.' ? pkgName : `${pkgName}/${entryPoint.replace(/^\.\//, '')}`;
1192
+ map.set(normalized, bareSpec);
1193
+ }
1194
+ }
1195
+ }
1196
+ // Fallback: main/module field for packages without exports
1197
+ if (map.size === 0) {
1198
+ for (const field of ['module', 'main']) {
1199
+ const value = pkgJson[field];
1200
+ if (value && typeof value === 'string') {
1201
+ const normalized = value.replace(/^\.\//, '');
1202
+ map.set(normalized, pkgName);
1203
+ // Also store without extension for NativeScript platform resolution
1204
+ const withoutExt = normalized.replace(/\.[^.]+$/, '');
1205
+ if (withoutExt !== normalized) {
1206
+ map.set(withoutExt, pkgName);
1207
+ }
1208
+ break;
1209
+ }
1210
+ }
1211
+ }
1212
+ }
1213
+ catch {
1214
+ // Package.json not found or unreadable
1215
+ }
1216
+ _exportsReverseMapCache.set(pkgName, map);
1217
+ return map;
1218
+ }
1219
+ /**
1220
+ * Extract the root package name from a node_modules specifier.
1221
+ * "@angular/common/fesm2022/http.mjs" → "@angular/common"
1222
+ * "tslib/tslib.es6.mjs" → "tslib"
1223
+ */
1224
+ function extractRootPackageName(spec) {
1225
+ if (spec.startsWith('@')) {
1226
+ const parts = spec.split('/');
1227
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : spec;
1228
+ }
1229
+ return spec.split('/')[0];
1230
+ }
1231
+ /**
1232
+ * Determine whether a vendor package import should go through the vendor bridge
1233
+ * (bare specifier) or HTTP (full URL), using the package's exports map.
1234
+ *
1235
+ * Returns:
1236
+ * { route: 'vendor', bareSpec: string } — use vendor bridge with this bare specifier
1237
+ * { route: 'http' } — serve via HTTP
1238
+ * null — not a vendor package
1239
+ */
1240
+ function resolveVendorRouting(nodeModulesSpec, projectRoot) {
1241
+ const pkgName = extractRootPackageName(nodeModulesSpec);
1242
+ const subpath = nodeModulesSpec.slice(pkgName.length).replace(/^\//, '');
1243
+ const pkgBaseName = pkgName.split('/').pop() || '';
1244
+ const isRootLevelMainEntry = (() => {
1245
+ if (!subpath || subpath.includes('/')) {
1246
+ return false;
1247
+ }
1248
+ const withoutExt = subpath.replace(/\.[^.]+$/, '');
1249
+ const withoutPlatform = withoutExt.replace(/\.(ios|android|visionos)$/i, '');
1250
+ return withoutPlatform === 'index' || withoutPlatform === pkgBaseName;
1251
+ })();
1252
+ // Runtime NativeScript plugins must preserve require()-style loading even when
1253
+ // they are not part of the vendor manifest. Many community packages rely on
1254
+ // singleton side effects or tolerate CommonJS circular initialization that
1255
+ // would fail under plain HTTP ESM evaluation.
1256
+ if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName) && (!subpath || isRootLevelMainEntry)) {
1257
+ return { route: 'vendor', bareSpec: pkgName };
1258
+ }
1259
+ // Check if this package is in the vendor manifest
1260
+ const manifest = getVendorManifest();
1261
+ if (!manifest?.modules?.[pkgName]) {
1262
+ return null;
1263
+ }
1264
+ // Platform-specific NativeScript plugin main entries should still use the
1265
+ // vendor bridge so device require() preserves singleton side effects.
1266
+ // Their named/default bindings are synthesized later by
1267
+ // ensureNativeScriptModuleBindings, so they do not rely on ns-vendor:// ESM
1268
+ // exports directly.
1269
+ if (/\.(ios|android|visionos)\.(js|ts|mjs|mts)$/i.test(nodeModulesSpec) && isLikelyNativeScriptRuntimePluginSpecifier(pkgName) && isRootLevelMainEntry) {
1270
+ return { route: 'vendor', bareSpec: pkgName };
1271
+ }
1272
+ // Other platform-specific files still go via HTTP.
1273
+ if (/\.(ios|android|visionos)\.(js|ts|mjs|mts)$/.test(nodeModulesSpec)) {
1274
+ return { route: 'http' };
1275
+ }
1276
+ // No subpath → bare package specifier → vendor bridge
1277
+ if (!subpath) {
1278
+ return { route: 'vendor', bareSpec: pkgName };
1279
+ }
1280
+ // Use exports reverse map to detect subpath entries — these MUST go to HTTP
1281
+ // to avoid collapsing e.g. @angular/common/http → @angular/common
1282
+ const reverseMap = getExportsReverseMap(pkgName, projectRoot);
1283
+ const originalSpec = reverseMap.get(subpath);
1284
+ if (originalSpec && originalSpec !== pkgName) {
1285
+ // Subpath entry (e.g., fesm2022/http.mjs → @angular/common/http) → HTTP
1286
+ return { route: 'http' };
1287
+ }
1288
+ // For vendor routing, only use the vendor bridge for root-level main entries
1289
+ // (single-segment paths like "index.js", "tslib.es6.mjs"). Multi-segment
1290
+ // build output paths (fesm2022/core.mjs, dist/index.js) go to HTTP even if
1291
+ // they ARE the main entry — the ns-vendor:// protocol in HMR mode does not
1292
+ // reliably serve all named ES exports for complex packages.
1293
+ if (!subpath.includes('/')) {
1294
+ const lastSegment = subpath.replace(/\.[^.]+$/, '');
1295
+ if (lastSegment === 'index' || lastSegment === pkgBaseName || lastSegment.startsWith(pkgBaseName + '.')) {
1296
+ return { route: 'vendor', bareSpec: pkgName };
1297
+ }
1298
+ }
1299
+ // Default: HTTP — safe for all module types and preserves all named exports
1300
+ return { route: 'http' };
1301
+ }
428
1302
  function stripCoreGlobalsImports(code) {
429
1303
  const pattern = /^\s*(?:import\s+(?:[^'"\n]*from\s+)?|export\s+\*\s+from\s+)["'][^"']*(?:@nativescript(?:[/_-])core(?:[\/_-])globals|@nativescript_core_globals)[^"']*["'];?\s*$/gm;
430
1304
  return code.replace(pattern, '');
@@ -471,6 +1345,109 @@ function ensureGuardPlainDynamicImports(code, origin) {
471
1345
  return code;
472
1346
  }
473
1347
  }
1348
+ /**
1349
+ * Expand `export * from "url"` into explicit named re-exports.
1350
+ *
1351
+ * NativeScript's HTTP ESM loader on iOS/Android may not correctly propagate
1352
+ * `export * from` re-exports across HTTP module boundaries — the importing
1353
+ * module's namespace object gets only direct exports, missing re-exported
1354
+ * names. This function resolves each `export * from` by fetching the target
1355
+ * module, scanning for its named exports, and replacing the star export with
1356
+ * explicit `export { name1, name2, ... } from "url"`.
1357
+ *
1358
+ * Only expands star exports pointing to node_modules HTTP URLs to avoid
1359
+ * unnecessary work for app source files (which are typically not re-exported).
1360
+ */
1361
+ async function expandStarExports(code, server, projectRoot, verbose) {
1362
+ const STAR_RE = /^[ \t]*(export\s+\*\s+from\s+["'])([^"']+)(["'];?)[ \t]*$/gm;
1363
+ let match;
1364
+ const replacements = [];
1365
+ while ((match = STAR_RE.exec(code)) !== null) {
1366
+ const url = match[2];
1367
+ // Only expand node_modules star exports served over HTTP
1368
+ if (!url.includes('/node_modules/'))
1369
+ continue;
1370
+ replacements.push({ full: match[0], url, prefix: match[1], suffix: match[3] });
1371
+ }
1372
+ if (!replacements.length)
1373
+ return code;
1374
+ for (const rep of replacements) {
1375
+ try {
1376
+ // Strip HTTP origin to get a Vite-resolvable path
1377
+ let vitePath = rep.url.replace(/^https?:\/\/[^/]+/, '');
1378
+ // Strip /ns/m/ prefix to get the node_modules path
1379
+ vitePath = vitePath.replace(/^\/ns\/m\//, '/');
1380
+ // Strip boot-path prefix used during initial HTTP boot progress tracking.
1381
+ vitePath = vitePath.replace(/^\/__ns_boot__\/[^/]+/, '');
1382
+ // Strip HMR cache-busting path segments
1383
+ vitePath = vitePath.replace(/\/__ns_hmr__\/[^/]+/, '');
1384
+ const r = await server.transformRequest(vitePath);
1385
+ if (!r?.code)
1386
+ continue;
1387
+ const names = extractExportedNames(r.code);
1388
+ if (!names.length)
1389
+ continue;
1390
+ // Replace `export * from "url"` with explicit named re-exports
1391
+ const explicit = `export { ${names.join(', ')} } from ${JSON.stringify(rep.url)};`;
1392
+ code = code.replace(rep.full, explicit);
1393
+ if (verbose) {
1394
+ console.log(`[ns/m] expanded export* → ${names.length} names from ${vitePath}`);
1395
+ }
1396
+ }
1397
+ catch { }
1398
+ }
1399
+ return code;
1400
+ }
1401
+ /**
1402
+ * Extract named export identifiers from a module's source code.
1403
+ * Handles: export function/class/const/let/var NAME, export { NAME },
1404
+ * export { NAME as ALIAS }, and export * from (recursive marker).
1405
+ * Does NOT follow `export * from` chains — only direct exports.
1406
+ */
1407
+ function extractExportedNames(code) {
1408
+ const names = new Set();
1409
+ // export function NAME / export class NAME / export async function NAME
1410
+ const declRe = /\bexport\s+(?:async\s+)?(?:function|class)\s+([A-Za-z_$][\w$]*)/g;
1411
+ let m;
1412
+ while ((m = declRe.exec(code)) !== null) {
1413
+ names.add(m[1]);
1414
+ }
1415
+ // export const/let/var NAME (handles destructuring and multiple declarators)
1416
+ const varRe = /\bexport\s+(?:const|let|var)\s+([^=;{]+)/g;
1417
+ while ((m = varRe.exec(code)) !== null) {
1418
+ // Simple case: `export const foo = ...`
1419
+ const decl = m[1].trim();
1420
+ // Could be `{ a, b }` (destructuring) or `foo, bar` (multiple) or just `foo`
1421
+ if (decl.startsWith('{')) {
1422
+ const inner = decl.replace(/^\{|\}$/g, '');
1423
+ for (const part of inner.split(',')) {
1424
+ const name = part.split(':')[0].trim(); // handle { orig: alias }
1425
+ if (/^[A-Za-z_$][\w$]*$/.test(name))
1426
+ names.add(name);
1427
+ }
1428
+ }
1429
+ else {
1430
+ const name = decl.split(/[\s,=]/)[0].trim();
1431
+ if (/^[A-Za-z_$][\w$]*$/.test(name))
1432
+ names.add(name);
1433
+ }
1434
+ }
1435
+ // export { NAME, NAME as ALIAS, ... } (without `from`)
1436
+ // and export { NAME, ... } from "..." (re-exports)
1437
+ const braceRe = /\bexport\s*\{([^}]+)\}/g;
1438
+ while ((m = braceRe.exec(code)) !== null) {
1439
+ for (const part of m[1].split(',')) {
1440
+ const trimmed = part.trim();
1441
+ // `name as alias` → use alias; `name` → use name
1442
+ const asMatch = trimmed.match(/\S+\s+as\s+(\S+)/);
1443
+ const name = asMatch ? asMatch[1] : trimmed.split(/\s/)[0];
1444
+ if (name && /^[A-Za-z_$][\w$]*$/.test(name) && name !== 'default') {
1445
+ names.add(name);
1446
+ }
1447
+ }
1448
+ }
1449
+ return Array.from(names);
1450
+ }
474
1451
  // Heal accidental "import ... = expr" assignments produced by upstream transforms.
475
1452
  // These are invalid JS; convert to equivalent const assignments.
476
1453
  function repairImportEqualsAssignments(code) {
@@ -521,6 +1498,11 @@ function ensureVersionedCoreImports(code, origin, ver) {
521
1498
  catch { }
522
1499
  return code;
523
1500
  }
1501
+ export function buildVersionedCoreSubpathAliasModule(sub, ver) {
1502
+ const normalizedSub = (sub || '').replace(/^\/+/, '');
1503
+ const canonicalUrl = `/ns/core/${ver}?p=${normalizedSub}`;
1504
+ return `import * as __ns_core_alias from ${JSON.stringify(canonicalUrl)};\n` + `export default (__ns_core_alias.default || __ns_core_alias);\n` + `export * from ${JSON.stringify(canonicalUrl)};\n`;
1505
+ }
524
1506
  // Hardened removal of Vite's virtual dynamic-import-helper. Some variants (side-effect only
525
1507
  // or minified forms) slipped past earlier regexes causing runtime attempts to resolve
526
1508
  // /@id/__x00__vite/dynamic-import-helper.js which does not exist in the device mirror.
@@ -809,68 +1791,15 @@ function cleanCode(code) {
809
1791
  result = ACTIVE_STRATEGY.preClean(result);
810
1792
  result = ACTIVE_STRATEGY.rewriteFrameworkImports(result);
811
1793
  // Vendor manifest-driven import rewrites
1794
+ // NOTE: Static and side-effect vendor imports are intentionally NOT rewritten here.
1795
+ // They are left as import statements so that ensureNativeScriptModuleBindings()
1796
+ // (called later in processCodeForDevice) can transform them using the robust
1797
+ // __nsVendorRequire + __nsPick pattern that works on device.
1798
+ // Only dynamic imports are handled here since ensureNativeScriptModuleBindings
1799
+ // does not process dynamic import() calls.
812
1800
  try {
813
1801
  const manifest = getVendorManifest();
814
1802
  if (manifest) {
815
- // Pattern: capture full import statement (static) with optional bindings
816
- // import X from 'pkg'; | import {a,b as c} from "pkg"; | import * as ns from 'pkg';
817
- const staticImportRE = /(import\s+([^;]*?)\s+from\s*["'])([^"']+)(["'];?)/g;
818
- result = result.replace(staticImportRE, (full, pre, bindings, spec, post) => {
819
- // Do not vendor-rewrite @nativescript/core — handled by the unified HTTP bridge later
820
- if (isNativeScriptCoreModule(spec))
821
- return full;
822
- const resolved = resolveVendorSpecifier(spec);
823
- if (!resolved || /^@nativescript\/core(\b|\/)/i.test(resolved))
824
- return full; // not vendor or is core
825
- // Determine binding style
826
- const trimmed = (bindings || '').trim();
827
- let injected = '';
828
- if (!trimmed || trimmed === '') {
829
- // Side-effect import: import 'pkg'; -> we drop it (vendor already evaluated)
830
- return `/* vendor side-effect dropped: ${spec} */`;
831
- }
832
- // Default + named or default only
833
- // Examples of trimmed:
834
- // defaultExport
835
- // { a, b as c }
836
- // * as ns
837
- // defaultExport, { a, b }
838
- const globalAccessor = `globalThis.__nsVendor && globalThis.__nsVendor(${JSON.stringify(resolved)})`;
839
- const ensureHelper = `globalThis.__nsVendor=require? (globalThis.__nsVendor|| (globalThis.__nsVendor=(id)=>{const m=(globalThis.__NS_VENDOR_MANIFEST__?globalThis.__NS_VENDOR_MANIFEST__.modules[id]:null);return (globalThis.__nsModules && globalThis.__nsModules.get? (globalThis.__nsModules.get(id)||globalThis.__nsModules.get(m?.id||id)):undefined);})):globalThis.__nsVendor`;
840
- if (trimmed.startsWith('{')) {
841
- // Named only
842
- injected = `${ensureHelper}; const ${trimmed} = ${globalAccessor} || {};`;
843
- }
844
- else if (trimmed.startsWith('*')) {
845
- // Namespace import: * as ns
846
- const m = /\*\s+as\s+(\w+)/.exec(trimmed);
847
- if (m) {
848
- injected = `${ensureHelper}; const ${m[1]} = ${globalAccessor} || {};`;
849
- }
850
- }
851
- else if (trimmed.includes(',')) {
852
- // default plus named
853
- const parts = trimmed.split(',');
854
- const def = parts[0].trim();
855
- const named = parts.slice(1).join(',').trim();
856
- injected = `${ensureHelper}; const __vmod = ${globalAccessor} || {}; const ${def} = __vmod.default || __vmod; const ${named} = __vmod;`;
857
- }
858
- else {
859
- // default only
860
- injected = `${ensureHelper}; const ${trimmed} = (${globalAccessor}||{}).default || ${globalAccessor};`;
861
- }
862
- return injected;
863
- });
864
- // Bare side-effect imports: import 'pkg';
865
- const sideEffectRE = /(import\s*["'])([^"']+)(["'];?)/g;
866
- result = result.replace(sideEffectRE, (full, pre, spec, post) => {
867
- if (isNativeScriptCoreModule(spec))
868
- return full;
869
- const resolved = resolveVendorSpecifier(spec);
870
- if (!resolved || /^@nativescript\/core(\b|\/)/i.test(resolved))
871
- return full;
872
- return `/* vendor side-effect skipped: ${spec} */`;
873
- });
874
1803
  // Dynamic import rewrites: import('pkg') -> Promise.resolve(__nsVendor('id'))
875
1804
  const dynImportRE = /(import\(\s*["'])([^"']+)(["']\s*\))/g;
876
1805
  result = result.replace(dynImportRE, (full, pre, spec, post) => {
@@ -1023,15 +1952,169 @@ function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
1023
1952
  if (!absolute || absolute === spec) {
1024
1953
  return null;
1025
1954
  }
1026
- return absolute;
1955
+ return absolute;
1956
+ }
1957
+ /**
1958
+ * After the Angular linker runs on code that Vite has already resolved (bare
1959
+ * specifiers → full URLs), the linker injects NEW import statements with bare
1960
+ * specifiers (e.g. `import {Component} from '@angular/core'`). These cause:
1961
+ * 1. Duplicate-identifier SyntaxErrors (the name was already imported via URL)
1962
+ * 2. Unresolvable bare specifiers at runtime on device
1963
+ *
1964
+ * This function:
1965
+ * • builds a map packageName → resolvedURL from existing resolved imports
1966
+ * • collects all binding names already imported per package
1967
+ * • for each bare-specifier import, removes duplicate bindings
1968
+ * • rewrites any genuinely-new bindings to use the resolved URL
1969
+ */
1970
+ function deduplicateLinkerImports(code) {
1971
+ if (!code)
1972
+ return code;
1973
+ try {
1974
+ const imports = collectTopLevelImportRecords(code);
1975
+ if (!imports.length) {
1976
+ return code;
1977
+ }
1978
+ // ── Step 1: collect resolved imports already in the file ──────────
1979
+ const pkgUrlMap = new Map();
1980
+ const pkgBindings = new Map();
1981
+ for (const imp of imports) {
1982
+ const url = imp.source;
1983
+ if (!/^https?:\/\//.test(url) && !url.startsWith('/')) {
1984
+ continue;
1985
+ }
1986
+ const nmIdx = url.lastIndexOf('/node_modules/');
1987
+ if (nmIdx === -1)
1988
+ continue;
1989
+ const afterNm = url.substring(nmIdx + '/node_modules/'.length);
1990
+ const parts = afterNm.split('/');
1991
+ const pkg = parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
1992
+ if (!pkgUrlMap.has(pkg))
1993
+ pkgUrlMap.set(pkg, url);
1994
+ if (imp.namedBindings.length) {
1995
+ if (!pkgBindings.has(pkg))
1996
+ pkgBindings.set(pkg, new Set());
1997
+ for (const binding of imp.namedBindings) {
1998
+ if (binding.importedName)
1999
+ pkgBindings.get(pkg).add(binding.importedName);
2000
+ }
2001
+ }
2002
+ }
2003
+ if (pkgUrlMap.size === 0)
2004
+ return code;
2005
+ // ── Step 2: rewrite bare-specifier imports ───────────────────────
2006
+ const edits = [];
2007
+ for (const imp of imports) {
2008
+ if (!imp.hasOnlyNamedSpecifiers) {
2009
+ continue;
2010
+ }
2011
+ const specifier = imp.source;
2012
+ if (specifier.startsWith('/') || specifier.startsWith('.') || specifier.startsWith('http')) {
2013
+ continue;
2014
+ }
2015
+ const parts = specifier.split('/');
2016
+ const pkg = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
2017
+ const url = pkgUrlMap.get(pkg);
2018
+ if (!url) {
2019
+ continue;
2020
+ }
2021
+ const existing = pkgBindings.get(pkg) || new Set();
2022
+ const newBindings = imp.namedBindings.filter((binding) => !existing.has(binding.importedName));
2023
+ if (newBindings.length === 0) {
2024
+ edits.push({ start: imp.start, end: imp.end, text: '' });
2025
+ continue;
2026
+ }
2027
+ if (newBindings.length === imp.namedBindings.length) {
2028
+ continue;
2029
+ }
2030
+ for (const binding of newBindings) {
2031
+ existing.add(binding.importedName);
2032
+ }
2033
+ edits.push({
2034
+ start: imp.start,
2035
+ end: imp.end,
2036
+ text: `import { ${newBindings.map((binding) => binding.text).join(', ')} } from ${JSON.stringify(url)};`,
2037
+ });
2038
+ }
2039
+ if (!edits.length) {
2040
+ return code;
2041
+ }
2042
+ let next = code;
2043
+ for (const edit of edits.sort((left, right) => right.start - left.start)) {
2044
+ next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
2045
+ }
2046
+ return next;
2047
+ }
2048
+ catch {
2049
+ return code;
2050
+ }
2051
+ }
2052
+ export function wrapCommonJsModuleForDevice(code) {
2053
+ if (!code)
2054
+ return code;
2055
+ try {
2056
+ const hasExportDefault = /\bexport\s+default\b/.test(code) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(code);
2057
+ const hasNamedExports = /\bexport\s+(?:const|let|var|function|class|async)\b/.test(code) || /\bexport\s*\{/.test(code);
2058
+ const hasCjsExports = /\bmodule\s*\.\s*exports\b/.test(code) || /\bexports\s*\.\s*\w/.test(code);
2059
+ if (hasExportDefault || hasNamedExports || !hasCjsExports) {
2060
+ return code;
2061
+ }
2062
+ const namedExports = new Set();
2063
+ const exportsRe = /\bexports\s*\.\s*([A-Za-z_$][\w$]*)\s*=/g;
2064
+ let em;
2065
+ while ((em = exportsRe.exec(code)) !== null) {
2066
+ const name = em[1];
2067
+ if (name !== '__esModule' && name !== 'default') {
2068
+ namedExports.add(name);
2069
+ }
2070
+ }
2071
+ const defPropRe = /Object\s*\.\s*defineProperty\s*\(\s*exports\s*,\s*['"]([^'"]+)['"]/g;
2072
+ while ((em = defPropRe.exec(code)) !== null) {
2073
+ const name = em[1];
2074
+ if (name !== '__esModule' && name !== 'default') {
2075
+ namedExports.add(name);
2076
+ }
2077
+ }
2078
+ let suffix = `\nvar __cjs_mod = module.exports;\nexport default __cjs_mod;\n`;
2079
+ if (namedExports.size) {
2080
+ const entries = Array.from(namedExports);
2081
+ const temps = entries.map((name, i) => `var __cjs_e${i} = __cjs_mod[${JSON.stringify(name)}];`);
2082
+ const reExports = entries.map((name, i) => `__cjs_e${i} as ${name}`);
2083
+ suffix += `${temps.join(' ')}\nexport { ${reExports.join(', ')} };\n`;
2084
+ }
2085
+ const prelude = `var module = { exports: {} }; var exports = module.exports;\n` +
2086
+ `var __ns_cjs_require_base = (typeof globalThis.__nsBaseRequire === 'function' ? globalThis.__nsBaseRequire : (typeof globalThis.__nsRequire === 'function' ? globalThis.__nsRequire : (typeof globalThis.require === 'function' ? globalThis.require : undefined)));\n` +
2087
+ `var __ns_cjs_require_kind = (typeof globalThis.__nsBaseRequire === 'function' ? 'base-require' : (typeof globalThis.__nsRequire === 'function' ? 'vendor-require' : 'global-require'));\n` +
2088
+ `var require = function(spec) {\n` +
2089
+ ` if (!__ns_cjs_require_base) { throw new Error('require is not defined'); }\n` +
2090
+ ` try { var __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(String(spec), { kind: __ns_cjs_require_kind, specifier: String(spec), via: 'cjs-wrapper', parent: (typeof import.meta !== 'undefined' && import.meta && import.meta.url) ? import.meta.url : undefined }); } } catch (e) {}\n` +
2091
+ ` var mod = __ns_cjs_require_base(spec);\n` +
2092
+ ` try {\n` +
2093
+ ` if (mod && (typeof mod === 'object' || typeof mod === 'function') && mod.default !== undefined) {\n` +
2094
+ ` var keys = [];\n` +
2095
+ ` try { keys = Object.keys(mod); } catch (e) {}\n` +
2096
+ ` var defaultOnly = keys.length === 1 && keys[0] === 'default';\n` +
2097
+ ` var esModuleOnly = keys.length === 2 && keys.indexOf('default') !== -1 && keys.indexOf('__esModule') !== -1;\n` +
2098
+ ` if (mod.__esModule || defaultOnly || esModuleOnly) { return mod.default; }\n` +
2099
+ ` }\n` +
2100
+ ` } catch (e) {}\n` +
2101
+ ` return mod;\n` +
2102
+ `};\n`;
2103
+ return `${prelude}${code}${suffix}`;
2104
+ }
2105
+ catch {
2106
+ return code;
2107
+ }
1027
2108
  }
1028
2109
  /**
1029
2110
  * Process code for device: inject globals, remove framework imports
1030
2111
  */
1031
- function processCodeForDevice(code, isVitePreBundled) {
2112
+ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = false, isNodeModule = false, sourceId) {
1032
2113
  let result = code;
1033
2114
  // Ensure Angular partial declarations are linked before any sanitizers run so runtime never hits the JIT path.
1034
2115
  result = linkAngularPartialsIfNeeded(result);
2116
+ // Post-linker: deduplicate/resolve imports the Angular linker injected with bare specifiers
2117
+ result = deduplicateLinkerImports(result);
1035
2118
  // First: aggressively strip any lingering virtual dynamic-import-helper before anything else.
1036
2119
  // Doing this up-front prevents downstream dependency collection from seeing the virtual id.
1037
2120
  result = stripViteDynamicImportVirtual(result);
@@ -1042,13 +2125,17 @@ function processCodeForDevice(code, isVitePreBundled) {
1042
2125
  // Inject ALL NativeScript/build globals at the top (matching global-defines.ts)
1043
2126
  // This ensures any code using __DEV__, __ANDROID__, __IOS__, etc. works correctly
1044
2127
  const allGlobals = [
2128
+ // Minimal process shim — populated with CLI --env.* flags at module load time.
2129
+ // In production builds, Vite/Rollup replaces process.env.* statically.
2130
+ // In HMR dev mode the code runs as-is on device, so we need the shim.
2131
+ `if (typeof process === "undefined") { globalThis.process = { env: ${__processEnvJson} }; } else if (!process.env) { process.env = ${__processEnvJson}; }`,
1045
2132
  'const __ANDROID__ = globalThis.__ANDROID__ !== undefined ? globalThis.__ANDROID__ : false;',
1046
2133
  'const __IOS__ = globalThis.__IOS__ !== undefined ? globalThis.__IOS__ : false;',
1047
2134
  'const __VISIONOS__ = globalThis.__VISIONOS__ !== undefined ? globalThis.__VISIONOS__ : false;',
1048
2135
  'const __APPLE__ = globalThis.__APPLE__ !== undefined ? globalThis.__APPLE__ : (__IOS__ || __VISIONOS__);',
1049
2136
  'const __DEV__ = globalThis.__DEV__ !== undefined ? globalThis.__DEV__ : false;',
1050
2137
  'const __COMMONJS__ = globalThis.__COMMONJS__ !== undefined ? globalThis.__COMMONJS__ : false;',
1051
- 'const __NS_WEBPACK__ = globalThis.__NS_WEBPACK__ !== undefined ? globalThis.__NS_WEBPACK__ : true;',
2138
+ 'const __NS_WEBPACK__ = globalThis.__NS_WEBPACK__ !== undefined ? globalThis.__NS_WEBPACK__ : false;',
1052
2139
  'const __NS_ENV_VERBOSE__ = globalThis.__NS_ENV_VERBOSE__ !== undefined ? !!globalThis.__NS_ENV_VERBOSE__ : false;',
1053
2140
  "const __CSS_PARSER__ = globalThis.__CSS_PARSER__ !== undefined ? globalThis.__CSS_PARSER__ : 'css-tree';",
1054
2141
  'const __UI_USE_XML_PARSER__ = globalThis.__UI_USE_XML_PARSER__ !== undefined ? globalThis.__UI_USE_XML_PARSER__ : true;',
@@ -1056,19 +2143,29 @@ function processCodeForDevice(code, isVitePreBundled) {
1056
2143
  'const __TEST__ = globalThis.__TEST__ !== undefined ? globalThis.__TEST__ : false;',
1057
2144
  ];
1058
2145
  result = allGlobals.join('\n') + '\n' + result;
1059
- // Prefer AST-based normalization for imports and helper aliases; fallback regex if parsing fails
1060
- try {
1061
- result = astNormalizeModuleImportsAndHelpers(result);
2146
+ const nodeModuleProvenancePrelude = buildNodeModuleProvenancePrelude(sourceId);
2147
+ if (nodeModuleProvenancePrelude) {
2148
+ result = nodeModuleProvenancePrelude + result;
1062
2149
  }
1063
- catch { }
1064
- // Verify there are no duplicate top-level const/let bindings after AST normalization
1065
- try {
1066
- result = astVerifyAndAnnotateDuplicates(result);
2150
+ // AST normalization: inject /ns/rt helper aliases for underscore-prefixed identifiers.
2151
+ // ONLY for app source files library code in node_modules should be served as-is.
2152
+ // Running the normalizer on libraries like tslib injects harmful destructures
2153
+ // (e.g., `const { SuppressedError } = __ns_rt_ns_1`) that shadow globals.
2154
+ if (!isNodeModule) {
2155
+ try {
2156
+ result = astNormalizeModuleImportsAndHelpers(result);
2157
+ }
2158
+ catch { }
2159
+ // Verify there are no duplicate top-level const/let bindings after AST normalization
2160
+ try {
2161
+ result = astVerifyAndAnnotateDuplicates(result);
2162
+ }
2163
+ catch { }
1067
2164
  }
1068
- catch { }
1069
- // If AST marker present, skip regex-based helper alias injection to avoid duplicates
1070
- // Accept both line and block comment markers emitted by the normalizer
1071
- if (!/^\s*(?:\/\/|\/\*) \[ast-normalized\]/m.test(result)) {
2165
+ // If AST marker present OR this is a node_modules file, skip regex-based helper
2166
+ // alias injection. Library code should NOT get /ns/rt destructures injected
2167
+ // underscore-prefixed identifiers in libraries are internal variables, not NS helpers.
2168
+ if (!isNodeModule && !/^\s*(?:\/\/|\/\*) \[ast-normalized\]/m.test(result)) {
1072
2169
  try {
1073
2170
  const underscored = new Set();
1074
2171
  const re = /(^|[^.\w$])_([A-Za-z]\w*)\b/g;
@@ -1149,7 +2246,11 @@ function processCodeForDevice(code, isVitePreBundled) {
1149
2246
  result = result.replace(/(^|\n)([\t ]*import\s+[^;]*?\s+from)\s*\n\s*("\/?node_modules\/\.vite\/deps\/[^"\n]+"\s*;?\s*)/gm, (_m, p1, p2, p3) => `${p1}${p2} ${p3}`);
1150
2247
  }
1151
2248
  catch { }
1152
- result = ensureNativeScriptModuleBindings(result);
2249
+ // When preserveVendorImports is true (HMR /ns/m/ endpoint), skip the
2250
+ // __nsVendorRequire + __nsPick rewrite. Vendor imports stay as bare
2251
+ // specifiers so the device-side import map resolves them via V8's native
2252
+ // module system, which correctly handles export * re-exports.
2253
+ result = preserveVendorImports ? ensureNativeScriptModuleBindings(result, { preserveNonPluginVendorImports: true }) : ensureNativeScriptModuleBindings(result);
1153
2254
  // Repair any accidental "import ... = expr" assignments that may have slipped in.
1154
2255
  try {
1155
2256
  result = repairImportEqualsAssignments(result);
@@ -1158,10 +2259,7 @@ function processCodeForDevice(code, isVitePreBundled) {
1158
2259
  // Strip Vite prebundle deps imports (both named and side-effect) and any malformed const string artifacts
1159
2260
  // Example problematic line observed: const "/node_modules/.vite/deps/@nativescript_firebase-messaging.js?v=...";
1160
2261
  if (/node_modules\/\.vite\/deps\//.test(result)) {
1161
- // Named imports from prebundle deps
1162
- result = result.replace(/^[\t ]*import\s+[^;]*from\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '');
1163
- // Side-effect only imports from prebundle deps
1164
- result = result.replace(/^[\t ]*import\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '');
2262
+ result = rewriteVitePrebundleImportsForDevice(result, preserveVendorImports);
1165
2263
  // Malformed const string lines accidentally produced by upstream transforms
1166
2264
  result = result.replace(/^[\t ]*const\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '// [hmr-sanitize] stripped malformed const prebundle ref\n');
1167
2265
  // Naked string-only lines pointing at prebundle deps
@@ -1248,7 +2346,7 @@ function processCodeForDevice(code, isVitePreBundled) {
1248
2346
  }
1249
2347
  // Ensure vendor bindings also apply after potential wrapper injections above
1250
2348
  // (idempotent: second pass will be a no-op if imports already consumed).
1251
- result = ensureNativeScriptModuleBindings(result);
2349
+ result = preserveVendorImports ? ensureNativeScriptModuleBindings(result, { preserveNonPluginVendorImports: true }) : ensureNativeScriptModuleBindings(result);
1252
2350
  try {
1253
2351
  result = repairImportEqualsAssignments(result);
1254
2352
  }
@@ -1315,6 +2413,9 @@ function processCodeForDevice(code, isVitePreBundled) {
1315
2413
  .join(', ');
1316
2414
  const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1317
2415
  result = result.replace(reNamed, (_full, pfx, specList, src) => {
2416
+ // Deep subpath URLs serve actual ESM with real named exports — skip.
2417
+ if (isDeepCoreSubpath(src))
2418
+ return _full;
1318
2419
  __core_ns_seq++;
1319
2420
  const tmp = `__ns_core_ns${__core_ns_seq}`;
1320
2421
  const decl = `const { ${toDestructureCore(specList)} } = ${tmp};`;
@@ -1322,6 +2423,8 @@ function processCodeForDevice(code, isVitePreBundled) {
1322
2423
  });
1323
2424
  const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1324
2425
  result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
2426
+ if (isDeepCoreSubpath(src))
2427
+ return _full;
1325
2428
  const decl = `const { ${toDestructureCore(specList)} } = ${defName};`;
1326
2429
  return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
1327
2430
  });
@@ -1335,8 +2438,11 @@ function processCodeForDevice(code, isVitePreBundled) {
1335
2438
  // Keep a single semicolon before the import to avoid generating ';;'
1336
2439
  result = result.replace(/;\s*import\s+/g, ';\nimport ');
1337
2440
  result = result.replace(/}\s*import\s+/g, '}\nimport ');
1338
- // Fallback: ensure any static import that isn't at start of line gets a newline before it
1339
- result = result.replace(/([^\n])\s*(import\s+[^;\n]*\s+from\s*["'][^"']+["'])/g, '$1\n$2');
2441
+ // Fallback: ensure any static import that isn't at start of line gets a newline before it.
2442
+ // Only match after statement-ending characters (;, }, ), ], quotes) — NOT after `*` or
2443
+ // spaces inside JSDoc comment blocks, which would accidentally extract example imports
2444
+ // from documentation comments and hoist them as real code.
2445
+ result = result.replace(/([;}\)\]'"`])\s*(import\s+[^;\n]*\s+from\s*["'][^"']+["'])/g, '$1\n$2');
1340
2446
  }
1341
2447
  catch { }
1342
2448
  // Collapse duplicate destructuring from the same temp namespace var (e.g., multiple const { x } = __ns_rt_ns1)
@@ -1370,22 +2476,13 @@ function processCodeForDevice(code, isVitePreBundled) {
1370
2476
  // always come before any statements that might reference their bindings. This ordering avoids
1371
2477
  // device runtimes that are stricter about imports-first semantics during module instantiation.
1372
2478
  try {
1373
- const importLineRe = /^\s*import\s+[^;]+;?\s*$/gm;
1374
- const lines = [];
1375
- result = result.replace(importLineRe, (imp) => {
1376
- lines.push(imp.trim());
1377
- return '';
1378
- });
1379
- if (lines.length) {
1380
- const hoisted = Array.from(new Set(lines)).join('\n') + '\n';
1381
- result = hoisted + result;
1382
- }
2479
+ result = hoistTopLevelStaticImports(result);
1383
2480
  }
1384
2481
  catch { }
1385
2482
  // Final safety: normalize any lingering named imports from /ns/rt into default+destructure
1386
- // Skip when AST normalization marker present to avoid introducing duplicate temp imports
2483
+ // Skip for node_modules (no /ns/rt helpers needed) and when AST marker present
1387
2484
  try {
1388
- if (!/^\s*\/\* \[ast-normalized\] \*\//m.test(result)) {
2485
+ if (!isNodeModule && !/^\s*\/\* \[ast-normalized\] \*\//m.test(result)) {
1389
2486
  result = ensureDestructureRtImports(result);
1390
2487
  }
1391
2488
  }
@@ -1531,6 +2628,13 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
1531
2628
  }
1532
2629
  }
1533
2630
  if (localCore.test(ln)) {
2631
+ // Comments can never cause split-realm risk at runtime — skip them.
2632
+ // Library authors commonly reference @nativescript/core in comments
2633
+ // (e.g. TSDoc /// <reference> directives, module resolution notes).
2634
+ const trimmed = ln.trimStart();
2635
+ if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
2636
+ continue;
2637
+ }
1534
2638
  offenders.push(`${i + 1}: ${ln.substring(0, 200)} [local-core-path]`);
1535
2639
  }
1536
2640
  if (offenders.length >= 10)
@@ -1555,6 +2659,7 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
1555
2659
  function ensureDestructureCoreImports(code) {
1556
2660
  try {
1557
2661
  let result = code;
2662
+ let coreImportCounter = 0;
1558
2663
  const toDestructure = (specList) => specList
1559
2664
  .split(',')
1560
2665
  .map((s) => s.trim())
@@ -1567,13 +2672,19 @@ function ensureDestructureCoreImports(code) {
1567
2672
  // import { A, B } from '/ns/core[/ver][?p=...]'
1568
2673
  const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1569
2674
  result = result.replace(reNamed, (_full, pfx, specList, src) => {
1570
- const tmp = `__ns_core_ns_re`; // temp binding name is not reused elsewhere after hoist
2675
+ // Deep subpath URLs serve actual ESM with real named exports skip.
2676
+ if (isDeepCoreSubpath(src))
2677
+ return _full;
2678
+ const tmp = `__ns_core_ns_re${coreImportCounter > 0 ? `_${coreImportCounter}` : ''}`;
2679
+ coreImportCounter++;
1571
2680
  const decl = `const { ${toDestructure(specList)} } = ${tmp};`;
1572
2681
  return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
1573
2682
  });
1574
2683
  // import Default, { A, B } from '/ns/core[...]'
1575
2684
  const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1576
2685
  result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
2686
+ if (isDeepCoreSubpath(src))
2687
+ return _full;
1577
2688
  const decl = `const { ${toDestructure(specList)} } = ${defName};`;
1578
2689
  return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
1579
2690
  });
@@ -1685,14 +2796,16 @@ function dedupeRtNamedImportsAgainstDestructures(code) {
1685
2796
  /**
1686
2797
  * THE SINGLE REWRITE FUNCTION - used everywhere for consistency
1687
2798
  */
1688
- function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin) {
2799
+ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
1689
2800
  let result = code;
1690
2801
  const httpOriginSafe = httpOrigin;
2802
+ const isDynamicImportPrefix = (prefix) => /import\(\s*["']?$/.test(prefix.trimStart());
1691
2803
  const importerDir = path.posix.dirname(importerPath);
1692
2804
  // Determine importer output relative path (project-relative .mjs) to compute relative imports consistently
1693
2805
  const importerOutRel = outputDirOverrideRel || getProjectRelativeImportPath(importerPath, projectRoot) || stripToProjectRelative(importerPath, projectRoot).replace(/\.(ts|js|tsx|jsx|mjs|mts|cts)$/i, '.mjs');
1694
2806
  const importerOutDir = importerOutRel ? path.posix.dirname(importerOutRel) : '';
1695
2807
  const ensureRel = (p) => (p.startsWith('.') ? p : `./${p}`);
2808
+ const isNsSfcSpecifier = (spec) => /^(?:https?:\/\/[^/]+)?\/ns\/sfc(?:\/\d+)?(?:\/|$)/.test(spec.replace(PAT.QUERY_PATTERN, ''));
1696
2809
  // Normalize all @nativescript/core imports to the unified HTTP ESM core bridge to guarantee a single realm on device
1697
2810
  try {
1698
2811
  let coreAliasIdx = 0;
@@ -1835,6 +2948,16 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
1835
2948
  return `${prefix}${stub}${suffix}`;
1836
2949
  }
1837
2950
  spec = normalizeNativeScriptCoreSpecifier(spec);
2951
+ // Route Vite virtual modules (/@solid-refresh, etc.) through /ns/m/ so their
2952
+ // internal imports (e.g. solid-js) get vendor-rewritten by our pipeline.
2953
+ // Skip known Vite internals (/@vite/, /@id/, /@fs/) which are handled elsewhere.
2954
+ if (spec.startsWith('/@') && !/^\/@(?:vite|id|fs)\//.test(spec)) {
2955
+ const out = `/ns/m${spec}`;
2956
+ if (httpOriginSafe) {
2957
+ return `${prefix}${httpOriginSafe}${out}${suffix}`;
2958
+ }
2959
+ return `${prefix}${out}${suffix}`;
2960
+ }
1838
2961
  // Route internal NS endpoints to absolute HTTP origin for device
1839
2962
  if (spec.startsWith('/ns/')) {
1840
2963
  if (httpOriginSafe) {
@@ -1847,19 +2970,30 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
1847
2970
  }
1848
2971
  const nodeModulesSpecifier = normalizeNodeModulesSpecifier(spec);
1849
2972
  const candidateNativeScriptSpec = nodeModulesSpecifier ?? spec;
1850
- const vendorCanonical = resolveVendorFromCandidate(nodeModulesSpecifier ?? spec);
1851
- if (vendorCanonical) {
1852
- if (nodeModulesSpecifier) {
1853
- return `${prefix}${nodeModulesSpecifier.replace(PAT.QUERY_PATTERN, '')}${suffix}`;
1854
- }
1855
- return `${prefix}${spec.replace(PAT.QUERY_PATTERN, '')}${suffix}`;
1856
- }
1857
- if (isNativeScriptPluginModule(candidateNativeScriptSpec)) {
1858
- const bareSpecifier = candidateNativeScriptSpec.replace(PAT.QUERY_PATTERN, '');
1859
- return `${prefix}${bareSpecifier}${suffix}`;
1860
- }
2973
+ // ── Node modules routing ──────────────────────────────────────
2974
+ // Uses the package's own package.json exports field to determine
2975
+ // whether an import is the main entry (→ vendor bridge) or a
2976
+ // subpath entry (→ HTTP). This replaces the old heuristic-based
2977
+ // approach that tried to guess from file paths.
1861
2978
  if (nodeModulesSpecifier) {
1862
- return `${prefix}${nodeModulesSpecifier}${suffix}`;
2979
+ const vendorRouting = resolveVendorRouting(nodeModulesSpecifier, projectRoot);
2980
+ if (vendorRouting) {
2981
+ if (vendorRouting.route === 'vendor') {
2982
+ return `${prefix}${vendorRouting.bareSpec}${suffix}`;
2983
+ }
2984
+ // Vendor package but subpath/platform-specific → HTTP
2985
+ const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
2986
+ if (httpOriginSafe) {
2987
+ return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
2988
+ }
2989
+ return `${prefix}${httpSpec}${suffix}`;
2990
+ }
2991
+ // Not a vendor package → serve via HTTP from Vite dev server
2992
+ const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
2993
+ if (httpOriginSafe) {
2994
+ return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
2995
+ }
2996
+ return `${prefix}${httpSpec}${suffix}`;
1863
2997
  }
1864
2998
  // Handle .vue imports
1865
2999
  if (PAT.VUE_FILE_PATTERN.test(spec)) {
@@ -1916,6 +3050,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
1916
3050
  const baseId = absMaybe ? toAppModuleBaseId(absMaybe, projectRoot) : null; // e.g. /src/foo.mjs
1917
3051
  if (baseId) {
1918
3052
  const httpSpec = `/ns/m${baseId}`;
3053
+ if (isDynamicImportPrefix(prefix)) {
3054
+ if (verbose)
3055
+ console.log(`[rewrite][http] dynamic relative app import → ${httpSpec} (from ${spec})`);
3056
+ return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
3057
+ }
1919
3058
  if (verbose)
1920
3059
  console.log(`[rewrite][http] relative app import → ${httpSpec} (from ${spec})`);
1921
3060
  return `${prefix}${httpSpec}${suffix}`;
@@ -1928,6 +3067,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
1928
3067
  const baseId = toAppModuleBaseId(spec, projectRoot);
1929
3068
  if (baseId) {
1930
3069
  const httpSpec = `/ns/m${baseId}`;
3070
+ if (isDynamicImportPrefix(prefix)) {
3071
+ if (verbose)
3072
+ console.log(`[rewrite][http] dynamic app import → ${httpSpec} (from ${spec})`);
3073
+ return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
3074
+ }
1931
3075
  if (verbose)
1932
3076
  console.log(`[rewrite][http] absolute app import → ${httpSpec} (from ${spec})`);
1933
3077
  return `${prefix}${httpSpec}${suffix}`;
@@ -1959,6 +3103,10 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
1959
3103
  result = result.replace(PAT.IMPORT_PATTERN_2, replaceVueImport);
1960
3104
  result = result.replace(PAT.EXPORT_PATTERN, replaceVueImport);
1961
3105
  result = result.replace(PAT.IMPORT_PATTERN_3, replaceVueImport);
3106
+ // Side-effect imports (import "spec") — must run AFTER named-import patterns
3107
+ // since IMPORT_PATTERN_1 already handles `import ... from "spec"`.
3108
+ result = result.replace(PAT.IMPORT_PATTERN_SIDE_EFFECT, replaceVueImport);
3109
+ result = ensureDynamicHmrImportHelper(result);
1962
3110
  // Extra guard: map any lingering dynamic import('@') to a safe stub module path
1963
3111
  // to prevent device runtime normalization errors.
1964
3112
  // Example matched: import('@') or import("@") with optional whitespace before closing paren
@@ -1999,6 +3147,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
1999
3147
  // In HTTP mode, skip legacy local-path rewrite to avoid mixing module origins
2000
3148
  result = result.replace(PAT.VUE_FILE_IMPORT, (_m, p1, spec, p3) => {
2001
3149
  if (httpOrigin) {
3150
+ if (isNsSfcSpecifier(spec)) {
3151
+ if (verbose)
3152
+ console.log(`[rewrite] .vue already routed (VUE_FILE_IMPORT http): ${spec}`);
3153
+ return `${p1}${spec}${p3}`;
3154
+ }
2002
3155
  // Route via /ns/sfc with full query preserved
2003
3156
  try {
2004
3157
  let base = spec;
@@ -2064,6 +3217,8 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
2064
3217
  function createHmrWebSocketPlugin(opts) {
2065
3218
  const verbose = !!opts.verbose;
2066
3219
  let wss = null;
3220
+ let sharedTransformRequest;
3221
+ const pendingAngularReloadSuppressions = new Map();
2067
3222
  const sfcFileMap = new Map();
2068
3223
  const depFileMap = new Map();
2069
3224
  // Generic module manifest (spec -> emitted relative .mjs path)
@@ -2078,6 +3233,22 @@ function createHmrWebSocketPlugin(opts) {
2078
3233
  // Transactional HMR batches: map graphVersion -> ordered list of changed ids for that version
2079
3234
  const txnBatches = new Map();
2080
3235
  const graph = new Map();
3236
+ function rememberAngularReloadSuppression(root, file, ttlMs = 3000) {
3237
+ const absPath = normalizeHotReloadMatchPath(file);
3238
+ const relPath = normalizeHotReloadMatchPath(file, root);
3239
+ pendingAngularReloadSuppressions.set(absPath, {
3240
+ absPath,
3241
+ relPath,
3242
+ expiresAt: Date.now() + ttlMs,
3243
+ });
3244
+ }
3245
+ function pruneAngularReloadSuppressions(now = Date.now()) {
3246
+ for (const [key, entry] of pendingAngularReloadSuppressions) {
3247
+ if (!entry || entry.expiresAt <= now) {
3248
+ pendingAngularReloadSuppressions.delete(key);
3249
+ }
3250
+ }
3251
+ }
2081
3252
  // Compute a dependency-closed, topologically sorted list of modules for a given set of changed ids.
2082
3253
  // Only include application modules we can serve (e.g., under /src and known .vue/.ts/.js entries in the graph).
2083
3254
  function computeTxnOrderForChanged(changedIds) {
@@ -2323,6 +3494,41 @@ function createHmrWebSocketPlugin(opts) {
2323
3494
  const httpServer = server.httpServer;
2324
3495
  if (!httpServer)
2325
3496
  return;
3497
+ const wsAny = server.ws;
3498
+ if (!wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__) {
3499
+ const originalSend = server.ws.send.bind(server.ws);
3500
+ wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__ = true;
3501
+ server.ws.send = ((payload, ...rest) => {
3502
+ pruneAngularReloadSuppressions();
3503
+ if (shouldSuppressViteFullReloadPayload({
3504
+ payload,
3505
+ pendingEntries: pendingAngularReloadSuppressions.values(),
3506
+ root: pluginRoot,
3507
+ })) {
3508
+ if (verbose) {
3509
+ console.log('[hmr-ws][angular] suppressed vite full-reload payload', payload);
3510
+ }
3511
+ return;
3512
+ }
3513
+ return originalSend(payload, ...rest);
3514
+ });
3515
+ }
3516
+ // Default to serialized transform execution for deterministic HTTP HMR startup.
3517
+ // Higher fan-out can be re-enabled explicitly via NS_VITE_HMR_TRANSFORM_CONCURRENCY.
3518
+ const configuredTransformConcurrency = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CONCURRENCY || '1', 10);
3519
+ const transformConcurrency = Number.isFinite(configuredTransformConcurrency) && configuredTransformConcurrency > 0 ? configuredTransformConcurrency : 1;
3520
+ const configuredTransformCacheMs = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CACHE_MS || '15000', 10);
3521
+ const transformCacheMs = Number.isFinite(configuredTransformCacheMs) && configuredTransformCacheMs >= 0 ? configuredTransformCacheMs : 15000;
3522
+ sharedTransformRequest = createSharedTransformRequestRunner((url) => server.transformRequest(url), (url, timeoutMs) => {
3523
+ try {
3524
+ console.warn('[ns:m] slow transformRequest for', url, '(>' + timeoutMs + 'ms)');
3525
+ }
3526
+ catch { }
3527
+ }, {
3528
+ maxConcurrent: transformConcurrency,
3529
+ resultCacheTtlMs: transformCacheMs,
3530
+ getResultCacheKey: (url) => canonicalizeTransformRequestCacheKey(url, pluginRoot),
3531
+ });
2326
3532
  // Attempt early vendor manifest bootstrap once per server.
2327
3533
  if (!vendorBootstrapDone) {
2328
3534
  vendorBootstrapDone = true;
@@ -2381,6 +3587,40 @@ function createHmrWebSocketPlugin(opts) {
2381
3587
  }
2382
3588
  catch { }
2383
3589
  });
3590
+ // Import map endpoint: GET /ns/import-map.json
3591
+ // Returns the import map + runtime config for __nsConfigureRuntime()
3592
+ server.middlewares.use(async (req, res, next) => {
3593
+ try {
3594
+ const urlObj = new URL(req.url || '', 'http://localhost');
3595
+ if (urlObj.pathname !== '/ns/import-map.json')
3596
+ return next();
3597
+ res.setHeader('Access-Control-Allow-Origin', '*');
3598
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
3599
+ if (req.method === 'OPTIONS') {
3600
+ res.statusCode = 204;
3601
+ res.end();
3602
+ return;
3603
+ }
3604
+ // Determine origin from request headers or server config
3605
+ const host = req.headers.host || 'localhost:5173';
3606
+ const protocol = 'http';
3607
+ const origin = `${protocol}://${host}`;
3608
+ const runtimeConfig = buildRuntimeConfig({
3609
+ origin,
3610
+ flavor: ACTIVE_STRATEGY?.flavor || 'typescript',
3611
+ });
3612
+ res.setHeader('Content-Type', 'application/json');
3613
+ res.end(JSON.stringify({
3614
+ importMap: JSON.parse(runtimeConfig.importMap),
3615
+ volatilePatterns: runtimeConfig.volatilePatterns,
3616
+ }, null, 2));
3617
+ }
3618
+ catch (err) {
3619
+ console.error('[import-map] error generating import map:', err?.message || err);
3620
+ res.statusCode = 500;
3621
+ res.end(JSON.stringify({ error: 'Failed to generate import map' }));
3622
+ }
3623
+ });
2384
3624
  // Dev-only HTTP ESM loader endpoint for device clients
2385
3625
  // 1) Legacy JSON module endpoint (kept temporarily): GET /ns-module?path=/abs -> { path, code, additionalFiles }
2386
3626
  server.middlewares.use(async (req, res, next) => {
@@ -2415,13 +3655,15 @@ function createHmrWebSocketPlugin(opts) {
2415
3655
  // Transform via Vite with variant resolution (same as ws ns:fetch-module)
2416
3656
  const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
2417
3657
  const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
3658
+ const transformRoot = server.config?.root || process.cwd();
2418
3659
  const candidates = [];
2419
3660
  if (hasExt)
2420
3661
  candidates.push(spec);
2421
3662
  candidates.push(baseNoExt + '.ts', baseNoExt + '.js', baseNoExt + '.tsx', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', baseNoExt + '.vue', baseNoExt + '/index.ts', baseNoExt + '/index.js', baseNoExt + '/index.tsx', baseNoExt + '/index.jsx', baseNoExt + '/index.mjs');
3663
+ const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, transformRoot);
2422
3664
  let transformed = null;
2423
3665
  let resolvedCandidate = null;
2424
- for (const cand of candidates) {
3666
+ for (const cand of transformCandidates) {
2425
3667
  try {
2426
3668
  const r = await server.transformRequest(cand);
2427
3669
  if (r?.code) {
@@ -2443,7 +3685,10 @@ function createHmrWebSocketPlugin(opts) {
2443
3685
  code = REQUIRE_GUARD_SNIPPET + code;
2444
3686
  // Apply same sanitation/rewrite pipeline used for WS path
2445
3687
  code = cleanCode(code);
2446
- code = processCodeForDevice(code, false);
3688
+ // preserveVendorImports=true: vendor imports stay as bare specifiers
3689
+ // for the device-side import map (ns-vendor://) instead of being
3690
+ // transformed to __nsVendorRequire calls with fragile __nsPick lookups.
3691
+ code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
2447
3692
  code = rewriteImports(code, spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
2448
3693
  code = ensureVariableDynamicImportHelper(code);
2449
3694
  // Enforce upstream guarantee: no optimized deps or virtual ids remain
@@ -2499,7 +3744,7 @@ function createHmrWebSocketPlugin(opts) {
2499
3744
  if (seen.has(depBase))
2500
3745
  continue;
2501
3746
  seen.add(depBase);
2502
- const depCandidates = [depBase + '.ts', depBase + '.js', depBase + '.tsx', depBase + '.jsx', depBase + '.mjs', depBase + '.mts', depBase + '.cts', depBase + '.vue', depBase + '/index.ts', depBase + '/index.js', depBase + '/index.tsx', depBase + '/index.jsx', depBase + '/index.mjs'];
3747
+ const depCandidates = filterExistingNodeModulesTransformCandidates(depBase, [depBase + '.ts', depBase + '.js', depBase + '.tsx', depBase + '.jsx', depBase + '.mjs', depBase + '.mts', depBase + '.cts', depBase + '.vue', depBase + '/index.ts', depBase + '/index.js', depBase + '/index.tsx', depBase + '/index.jsx', depBase + '/index.mjs'], transformRoot);
2503
3748
  let depTrans = null;
2504
3749
  let depResolved = null;
2505
3750
  for (const c of depCandidates) {
@@ -2516,7 +3761,7 @@ function createHmrWebSocketPlugin(opts) {
2516
3761
  if (depTrans?.code && depResolved) {
2517
3762
  let depCode = depTrans.code;
2518
3763
  depCode = cleanCode(depCode);
2519
- depCode = processCodeForDevice(depCode, false);
3764
+ depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
2520
3765
  depCode = rewriteImports(depCode, depResolved, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
2521
3766
  depCode = ensureVariableDynamicImportHelper(depCode);
2522
3767
  try {
@@ -2568,6 +3813,7 @@ function createHmrWebSocketPlugin(opts) {
2568
3813
  let spec = urlObj.searchParams.get('path') || '';
2569
3814
  // Optional graph version pin for deterministic boot
2570
3815
  const forcedVer = urlObj.searchParams.get('v');
3816
+ let bootTaggedRequest = false;
2571
3817
  if (!spec) {
2572
3818
  const base = '/ns/m';
2573
3819
  let rest = urlObj.pathname.slice(base.length);
@@ -2585,22 +3831,36 @@ function createHmrWebSocketPlugin(opts) {
2585
3831
  res.end('export {}\n');
2586
3832
  return;
2587
3833
  }
3834
+ const serverRoot = (server.config?.root || process.cwd());
2588
3835
  spec = spec.replace(/[?#].*$/, '');
2589
- // Accept path-based HMR cache-busting: /ns/m/__ns_hmr__/<tag>/<real-spec>
3836
+ // Accept path-based boot/HMR prefixes:
3837
+ // /ns/m/__ns_boot__/b1/<real-spec>
3838
+ // /ns/m/__ns_hmr__/<tag>/<real-spec>
3839
+ // /ns/m/__ns_boot__/b1/__ns_hmr__/<tag>/<real-spec>
2590
3840
  // The iOS HTTP ESM loader canonicalizes cache keys by stripping query params,
2591
3841
  // so we must carry the cache-buster in the path.
2592
3842
  try {
2593
- const m = spec.match(/^\/?__ns_hmr__\/[^\/]+(\/.*)?$/);
2594
- if (m) {
2595
- spec = m[1] || '/';
3843
+ let changed = true;
3844
+ while (changed) {
3845
+ changed = false;
3846
+ const bootMatch = spec.match(/^\/?__ns_boot__\/[^\/]+(\/.*)?$/);
3847
+ if (bootMatch) {
3848
+ bootTaggedRequest = true;
3849
+ spec = bootMatch[1] || '/';
3850
+ changed = true;
3851
+ }
3852
+ const hmrMatch = spec.match(/^\/?__ns_hmr__\/[^\/]+(\/.*)?$/);
3853
+ if (hmrMatch) {
3854
+ spec = hmrMatch[1] || '/';
3855
+ changed = true;
3856
+ }
2596
3857
  }
2597
3858
  }
2598
3859
  catch { }
2599
3860
  // Normalize absolute filesystem paths back to project-relative ids (e.g. /src/app.ts)
2600
3861
  try {
2601
- const projectRoot = (server.config?.root || process.cwd());
2602
3862
  const toPosix = (p) => p.replace(/\\/g, '/');
2603
- const rootPosix = toPosix(projectRoot);
3863
+ const rootPosix = toPosix(serverRoot);
2604
3864
  const specPosix = toPosix(spec);
2605
3865
  // If spec is an absolute path under the project root, convert to '/'+relative
2606
3866
  const isAbsFs = /^\//.test(specPosix) || /^[A-Za-z]:\//.test(spec); // posix or win drive
@@ -2615,6 +3875,36 @@ function createHmrWebSocketPlugin(opts) {
2615
3875
  }
2616
3876
  }
2617
3877
  catch { }
3878
+ // Serve Vite virtual modules (/@id/ prefix). These are internal
3879
+ // virtual modules (e.g., \0nsvite:nsconfig-json for ~/package.json)
3880
+ // that don't exist on disk. Decode the ID and load via plugin container.
3881
+ if (spec.startsWith('/@id/')) {
3882
+ try {
3883
+ // First try Vite's transform pipeline directly
3884
+ const vr = await sharedTransformRequest(spec);
3885
+ if (vr?.code) {
3886
+ res.statusCode = 200;
3887
+ res.end(vr.code);
3888
+ return;
3889
+ }
3890
+ }
3891
+ catch { }
3892
+ try {
3893
+ // Fallback: decode the virtual module ID (__x00__ → \0) and
3894
+ // load through the plugin container directly
3895
+ const rawId = spec.slice('/@id/'.length).replace(/__x00__/g, '\0');
3896
+ const loadResult = await server.pluginContainer.load(rawId);
3897
+ if (loadResult) {
3898
+ const code = typeof loadResult === 'string' ? loadResult : loadResult.code;
3899
+ if (code) {
3900
+ res.statusCode = 200;
3901
+ res.end(code);
3902
+ return;
3903
+ }
3904
+ }
3905
+ }
3906
+ catch { }
3907
+ }
2618
3908
  if (spec.startsWith('@/'))
2619
3909
  spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
2620
3910
  if (spec.startsWith('./'))
@@ -2624,18 +3914,33 @@ function createHmrWebSocketPlugin(opts) {
2624
3914
  const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
2625
3915
  const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
2626
3916
  const candidates = [...(hasExt ? [spec] : []), baseNoExt + '.ts', baseNoExt + '.js', baseNoExt + '.tsx', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', baseNoExt + '.vue', baseNoExt + '/index.ts', baseNoExt + '/index.js', baseNoExt + '/index.tsx', baseNoExt + '/index.jsx', baseNoExt + '/index.mjs'];
3917
+ const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, serverRoot);
2627
3918
  let transformed = null;
2628
3919
  let resolvedCandidate = null;
2629
- for (const cand of candidates) {
2630
- try {
2631
- const r = await server.transformRequest(cand);
2632
- if (r?.code) {
2633
- transformed = r;
2634
- resolvedCandidate = cand;
2635
- break;
3920
+ const rawExplicitModule = tryReadRawExplicitJavaScriptModule(spec, serverRoot);
3921
+ if (rawExplicitModule) {
3922
+ transformed = { code: rawExplicitModule.code };
3923
+ resolvedCandidate = rawExplicitModule.resolvedId;
3924
+ }
3925
+ // Queue and dedupe transformRequest calls so heavy app graphs do not
3926
+ // overwhelm Vite with concurrent work. Slow-transform warnings start only
3927
+ // when the transform actually begins executing, and requests stay pending
3928
+ // until Vite returns a real result.
3929
+ const transformWithTimeout = (url, timeoutMs = 120000) => {
3930
+ return sharedTransformRequest(url, timeoutMs);
3931
+ };
3932
+ if (!transformed?.code) {
3933
+ for (const cand of transformCandidates) {
3934
+ try {
3935
+ const r = await transformWithTimeout(cand);
3936
+ if (r?.code) {
3937
+ transformed = r;
3938
+ resolvedCandidate = cand;
3939
+ break;
3940
+ }
2636
3941
  }
3942
+ catch { }
2637
3943
  }
2638
- catch { }
2639
3944
  }
2640
3945
  // Fallback 1: ask Vite to resolve the id, then transform the resolved id (handles aliases and virtual ids)
2641
3946
  if (!transformed?.code) {
@@ -2643,7 +3948,7 @@ function createHmrWebSocketPlugin(opts) {
2643
3948
  const rid = await server.pluginContainer?.resolveId?.(spec, undefined);
2644
3949
  const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
2645
3950
  if (ridStr) {
2646
- const r = await server.transformRequest(ridStr);
3951
+ const r = await transformWithTimeout(ridStr);
2647
3952
  if (r?.code) {
2648
3953
  transformed = r;
2649
3954
  resolvedCandidate = ridStr;
@@ -2652,27 +3957,49 @@ function createHmrWebSocketPlugin(opts) {
2652
3957
  }
2653
3958
  catch { }
2654
3959
  }
3960
+ // Fallback 1b: if spec is a /node_modules/ path, extract bare specifier
3961
+ // and try resolveId with that. This handles package.json "exports" field
3962
+ // resolution (e.g., solid-js/jsx-runtime → solid-js/dist/solid.js).
3963
+ if (!transformed?.code && spec.includes('/node_modules/')) {
3964
+ try {
3965
+ const nmIdx = spec.lastIndexOf('/node_modules/');
3966
+ const bare = spec.slice(nmIdx + '/node_modules/'.length);
3967
+ if (bare && !bare.startsWith('.')) {
3968
+ const rid = await server.pluginContainer?.resolveId?.(bare, undefined);
3969
+ const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
3970
+ if (ridStr) {
3971
+ const r = await sharedTransformRequest(ridStr);
3972
+ if (r?.code) {
3973
+ transformed = r;
3974
+ resolvedCandidate = ridStr;
3975
+ }
3976
+ }
3977
+ }
3978
+ }
3979
+ catch { }
3980
+ }
2655
3981
  // Fallback 2: try /@fs absolute path under project root (Vite file system alias)
2656
3982
  if (!transformed?.code) {
2657
3983
  try {
2658
- const projectRoot = (server.config?.root || process.cwd());
2659
3984
  const toPosix = (p) => p.replace(/\\/g, '/');
2660
- const rootPosix = toPosix(projectRoot).replace(/\/$/, '');
3985
+ const rootPosix = toPosix(serverRoot).replace(/\/$/, '');
2661
3986
  const absPosix = `${rootPosix}${spec.startsWith('/') ? '' : '/'}${spec}`;
2662
3987
  const fsId = `/@fs${absPosix}`;
2663
- const r = await server.transformRequest(fsId);
2664
- if (r?.code) {
2665
- transformed = r;
2666
- resolvedCandidate = fsId;
3988
+ if (resolveCandidateFilePath(fsId, serverRoot)) {
3989
+ const r = await transformWithTimeout(fsId);
3990
+ if (r?.code) {
3991
+ transformed = r;
3992
+ resolvedCandidate = fsId;
3993
+ }
2667
3994
  }
2668
3995
  }
2669
3996
  catch { }
2670
3997
  }
2671
3998
  // Fallback 3: try adding ?import to hint Vite's transform pipeline
2672
3999
  if (!transformed?.code) {
2673
- for (const cand of candidates) {
4000
+ for (const cand of transformCandidates) {
2674
4001
  try {
2675
- const r = await server.transformRequest(`${cand}${cand.includes('?') ? '&' : '?'}import`);
4002
+ const r = await transformWithTimeout(`${cand}${cand.includes('?') ? '&' : '?'}import`);
2676
4003
  if (r?.code) {
2677
4004
  transformed = r;
2678
4005
  resolvedCandidate = `${cand}?import`;
@@ -2682,39 +4009,67 @@ function createHmrWebSocketPlugin(opts) {
2682
4009
  catch { }
2683
4010
  }
2684
4011
  }
2685
- // Post-transform: inject cache-busting version for all internal /ns/m/* imports to avoid stale module reuse on device.
2686
- // IMPORTANT: use PATH-based busting (not query) because the iOS HTTP ESM loader strips query params
2687
- // when computing module cache keys.
4012
+ // Solid HMR: patch @@solid-refresh's $$refreshESM to do inline patching
4013
+ // during module re-evaluation instead of deferring to hot.accept() callback.
4014
+ // In NativeScript's HTTP ESM environment, accept callbacks are registered
4015
+ // but not invoked by the HMR client. By adding a direct patchRegistry()
4016
+ // call when hot.data already has a stored registry, component updates
4017
+ // apply immediately when the module re-evaluates.
2688
4018
  try {
2689
- if (transformed?.code) {
2690
- const ver = Number(global.graphVersion || graphVersion || 0);
2691
- let code = transformed.code;
2692
- const prefix = `/ns/m/__ns_hmr__/v${ver}`;
2693
- const rewrite = (p) => {
2694
- try {
2695
- if (!p || typeof p !== 'string')
2696
- return p;
2697
- if (!p.startsWith('/ns/m/'))
2698
- return p;
2699
- if (p.startsWith('/ns/m/__ns_hmr__/'))
2700
- return p;
2701
- return prefix + p.slice('/ns/m'.length);
4019
+ if (transformed?.code && ACTIVE_STRATEGY?.flavor === 'solid' && (resolvedCandidate || spec || '').includes('@solid-refresh')) {
4020
+ const PATCH_SENTINEL = '/* __ns_solid_refresh_patched__ */';
4021
+ const alreadyPatched = transformed.code.includes(PATCH_SENTINEL);
4022
+ console.log('[hmr-ws][solid] @solid-refresh patch check:', { spec: resolvedCandidate || spec, alreadyPatched, codeLen: transformed.code.length });
4023
+ if (!alreadyPatched) {
4024
+ let patchedCode = transformed.code;
4025
+ // Patch 1: Bypass shouldWarnAndDecline() the vendor-bundled solid-js
4026
+ // may not have the 'development' condition active, making DEV empty/undefined.
4027
+ // In NativeScript HMR mode we are always in dev, so force it to return false.
4028
+ const declineCheck = 'function shouldWarnAndDecline() {';
4029
+ if (patchedCode.includes(declineCheck)) {
4030
+ patchedCode = patchedCode.replace(declineCheck, `${PATCH_SENTINEL}\nfunction shouldWarnAndDecline() { return false; /* NS HMR: always allow refresh */ }\nfunction __original_shouldWarnAndDecline() {`);
4031
+ console.log('[hmr-ws][solid] bypassed shouldWarnAndDecline() for NativeScript HMR');
2702
4032
  }
2703
- catch {
2704
- return p;
4033
+ // Patch 2: Force createMemo path in createProxy.
4034
+ // Without the 'development' condition, $DEVCOMP is not set on components,
4035
+ // so createProxy falls through to `return s(props)` — a direct call with
4036
+ // no reactive subscription. When patchComponent fires update() (the signal
4037
+ // setter), nobody is listening. By forcing the createMemo path, HMRComp
4038
+ // subscribes to the signal and re-renders when the component changes.
4039
+ const proxyCondition = 'if (!s || $DEVCOMP in s) {';
4040
+ if (patchedCode.includes(proxyCondition)) {
4041
+ patchedCode = patchedCode.replace(proxyCondition, 'if (true) { /* NS HMR: always use createMemo for reactive HMR updates */');
4042
+ console.log('[hmr-ws][solid] forced createMemo path in createProxy for NativeScript HMR');
2705
4043
  }
2706
- };
2707
- // 1) Static imports: import ... from "/ns/m/..."
2708
- code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewrite(p)}${b}`);
2709
- // 2) Side-effect imports: import "/ns/m/..."
2710
- code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewrite(p)}${b}`);
2711
- // 3) Dynamic imports: import("/ns/m/...")
2712
- code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, (_m, a, p, b) => `${a}${rewrite(p)}${b}`);
2713
- // 4) new URL("/ns/m/...", import.meta.url)
2714
- code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\))/g, (_m, a, p, b) => `${a}${rewrite(p)}${b}`);
2715
- // 5) __ns_import(new URL('/ns/m/...', import.meta.url).href)
2716
- code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\)\.href)/g, (_m, a, p, b) => `${a}${rewrite(p)}${b}`);
2717
- transformed.code = code;
4044
+ // Patch 3: Inline patchRegistry call so updates apply immediately
4045
+ // on module re-evaluation (accept callbacks are not invoked by the HMR client).
4046
+ const marker = 'hot.data[SOLID_REFRESH] = hot.data[SOLID_REFRESH] || registry;';
4047
+ if (patchedCode.includes(marker)) {
4048
+ const patchCode = [
4049
+ `console.log('[solid-refresh][$$refreshESM] hot.data keys=', hot.data ? Object.keys(hot.data) : 'no-data', 'has=', !!(hot.data && hot.data[SOLID_REFRESH]));`,
4050
+ `if (hot.data[SOLID_REFRESH]) {`,
4051
+ ` console.log('[solid-refresh][$$refreshESM] patching: oldComponents=', hot.data[SOLID_REFRESH].components ? hot.data[SOLID_REFRESH].components.size : 0, 'newComponents=', registry.components ? registry.components.size : 0);`,
4052
+ ` var _shouldInvalidate = patchRegistry(hot.data[SOLID_REFRESH], registry);`,
4053
+ ` console.log('[solid-refresh][$$refreshESM] patchRegistry result: shouldInvalidate=', _shouldInvalidate);`,
4054
+ `} else {`,
4055
+ ` console.log('[solid-refresh][$$refreshESM] first load — creating registry, components=', registry.components ? registry.components.size : 0);`,
4056
+ `}`,
4057
+ ].join('\n ');
4058
+ patchedCode = patchedCode.replace(marker, `${patchCode}\n ${marker}`);
4059
+ console.log('[hmr-ws][solid] added inline patchRegistry for NativeScript HMR');
4060
+ }
4061
+ // Work on a copy to avoid mutating Vite's cached TransformResult
4062
+ transformed = { ...transformed, code: patchedCode };
4063
+ }
4064
+ }
4065
+ }
4066
+ catch { }
4067
+ // NOTE: Path-based cache busting for /ns/m/* imports is applied in the
4068
+ // finalize step below (after rewriteImports adds the /ns/m/ prefix).
4069
+ // The block here only handles TypeScript-specific graph population.
4070
+ try {
4071
+ if (transformed?.code) {
4072
+ const code = transformed.code;
2718
4073
  // TypeScript-specific graph population: when TS flavor is active
2719
4074
  // and this is an application module under the virtual app root,
2720
4075
  // upsert it into the HMR graph so ns:hmr-full-graph is non-empty.
@@ -2843,7 +4198,7 @@ export const piniaSymbol = p.piniaSymbol;
2843
4198
  if (!transformed?.code) {
2844
4199
  // Emit a module that throws with context for easier on-device debugging
2845
4200
  try {
2846
- const tried = Array.from(new Set(candidates)).slice(0, 12);
4201
+ const tried = Array.from(new Set(transformCandidates.length > 0 ? transformCandidates : candidates)).slice(0, 12);
2847
4202
  const out = `// [ns:m] transform miss path=${spec} tried=${tried.length}\n` + `throw new Error(${JSON.stringify(`[ns/m] transform failed for ${spec} (tried ${tried.length} candidates).`)});\nexport {};\n`;
2848
4203
  res.statusCode = 404;
2849
4204
  res.end(out);
@@ -2860,8 +4215,36 @@ export const piniaSymbol = p.piniaSymbol;
2860
4215
  // Prepend guard to capture any URL-based require attempts
2861
4216
  code = REQUIRE_GUARD_SNIPPET + code;
2862
4217
  code = cleanCode(code);
2863
- code = processCodeForDevice(code, false);
2864
- code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
4218
+ const isNodeMod = /(?:^|\/)node_modules\//.test(resolvedCandidate || spec || '');
4219
+ code = processCodeForDevice(code, false, true, isNodeMod, resolvedCandidate || spec);
4220
+ // Solid HMR: The NativeScript iOS/Android runtime provides import.meta.hot
4221
+ // natively (via InitializeImportMetaHot in HMRSupport.mm) with C++-backed
4222
+ // persistent hot.data that survives across module re-evaluations.
4223
+ // cleanCode() strips Vite's __vite__createHotContext assignment, which is
4224
+ // correct — the runtime's native hot context is better.
4225
+ // We inject a diagnostic log to trace hot.data state during development.
4226
+ try {
4227
+ if (ACTIVE_STRATEGY?.flavor === 'solid' && /\.(tsx|jsx)$/i.test(resolvedCandidate || spec)) {
4228
+ const moduleId = (resolvedCandidate || spec).replace(/[?#].*$/, '');
4229
+ // Diagnostic: log import.meta.hot state on device to trace solid-refresh flow
4230
+ code = `try{if(typeof import.meta!=='undefined'&&import.meta.hot){var _hd=import.meta.hot.data;var _sr=_hd&&_hd['solid-refresh'];console.log('[solid-hmr][native-hot]',${JSON.stringify(moduleId)},'hasHot=true','hasData=',!!_hd,'hasSolidRefresh=',!!_sr,'dataKeys=',_hd?Object.keys(_hd):[]);}else{console.log('[solid-hmr][native-hot]',${JSON.stringify(moduleId)},'hasHot=',!!(typeof import.meta!=='undefined'&&import.meta.hot));}}catch(e){console.log('[solid-hmr][native-hot] error',e);}\n` + code;
4231
+ console.log('[hmr-ws][solid] diagnostic injected for', moduleId, '(using runtime native import.meta.hot)');
4232
+ }
4233
+ }
4234
+ catch { }
4235
+ code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server), true);
4236
+ // Expand `export * from "url"` into explicit named re-exports.
4237
+ // NativeScript's HTTP ESM loader may not propagate star-re-exports across
4238
+ // HTTP module boundaries (the namespace object gets direct exports but
4239
+ // misses re-exported names). By expanding to `export { a, b } from "url"`,
4240
+ // the engine sees explicit named exports and resolves them correctly.
4241
+ try {
4242
+ code = await expandStarExports(code, server, server.config?.root || process.cwd(), verbose);
4243
+ }
4244
+ catch (e) {
4245
+ if (verbose)
4246
+ console.warn('[ns/m] export* expansion failed:', e?.message);
4247
+ }
2865
4248
  // Dedupe any /ns/rt named imports that duplicate destructured bindings off default /ns/rt
2866
4249
  try {
2867
4250
  code = dedupeRtNamedImportsAgainstDestructures(code);
@@ -2888,6 +4271,28 @@ export const piniaSymbol = p.piniaSymbol;
2888
4271
  }
2889
4272
  }
2890
4273
  catch { }
4274
+ // Final pass: deduplicate/resolve any bare-specifier imports that slipped
4275
+ // through the pipeline (e.g., extracted from JSDoc comments by import-splitting
4276
+ // regexes, or injected by the Angular linker on already-resolved code).
4277
+ try {
4278
+ code = deduplicateLinkerImports(code);
4279
+ }
4280
+ catch { }
4281
+ // CJS/UMD wrapping: if a module uses module.exports but has no ESM export default,
4282
+ // wrap it with CJS shims so the device HTTP ESM loader can consume it.
4283
+ // This handles npm packages that use CommonJS but aren't pre-bundled by Vite.
4284
+ //
4285
+ // Key constraints this must handle:
4286
+ // - CJS modules often declare local vars with the same names as their exports
4287
+ // (e.g. `function createLTTB() {...}; exports.createLTTB = createLTTB;`)
4288
+ // so `export var { createLTTB }` would cause a duplicate declaration.
4289
+ // - UMD modules reference `this` at top level (undefined in ESM) but
4290
+ // typically fall back to `self` or `globalThis`.
4291
+ // - `module`, `exports` must be shims since they don't exist in ESM.
4292
+ try {
4293
+ code = wrapCommonJsModuleForDevice(code);
4294
+ }
4295
+ catch { }
2891
4296
  try {
2892
4297
  assertNoOptimizedArtifacts(code, `NS M ${resolvedCandidate || spec}`);
2893
4298
  }
@@ -2914,23 +4319,37 @@ export const piniaSymbol = p.piniaSymbol;
2914
4319
  code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
2915
4320
  }
2916
4321
  catch { }
2917
- // Finalize: also stamp all internal /ns/m imports with ?v=<ver> after all rewrites
4322
+ // Finalize: stamp all internal /ns/m imports with PATH-based cache busting.
4323
+ // IMPORTANT: use path prefix (not ?v= query) because the iOS HTTP ESM loader
4324
+ // strips query params when computing module cache keys, so ?v= doesn't bust the V8 cache.
2918
4325
  try {
2919
4326
  const ver = String(forcedVer || graphVersion || 0);
2920
4327
  const origin = getServerOrigin(server);
4328
+ const hmrPrefix = `/ns/m/__ns_hmr__/v${ver}`;
4329
+ const bootHmrPrefix = `/ns/m/__ns_boot__/b1/__ns_hmr__/v${ver}`;
4330
+ const rewritePath = (p) => {
4331
+ if (!p || !p.startsWith('/ns/m/'))
4332
+ return p;
4333
+ if (p.startsWith('/ns/m/__ns_boot__/'))
4334
+ return p;
4335
+ if (p.startsWith('/ns/m/__ns_hmr__/')) {
4336
+ return bootTaggedRequest ? `/ns/m/__ns_boot__/b1${p.slice('/ns/m'.length)}` : p;
4337
+ }
4338
+ return (bootTaggedRequest ? bootHmrPrefix : hmrPrefix) + p.slice('/ns/m'.length);
4339
+ };
2921
4340
  // 1) Static imports: import ... from "/ns/m/..."
2922
- code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$1$2?v=${ver}$3`);
4341
+ code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
2923
4342
  // 2) Side-effect imports: import "/ns/m/..."
2924
- code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$1$2?v=${ver}$3`);
4343
+ code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
2925
4344
  // 3) Dynamic imports: import("/ns/m/...")
2926
- code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, `$1$2?v=${ver}$3`);
4345
+ code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
2927
4346
  // 4) new URL("/ns/m/...", import.meta.url)
2928
- code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\))/g, `$1$2?v=${ver}$3`);
4347
+ code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
2929
4348
  // 5) __ns_import(new URL('/ns/m/...', import.meta.url).href)
2930
- code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\)\.href)/g, `$1$2?v=${ver}$3`);
2931
- // 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href → "${origin}/ns/m/..."
4349
+ code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\)\.href)/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
4350
+ // 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href → "${origin}/ns/m/__ns_hmr__/..."
2932
4351
  try {
2933
- code = code.replace(/new\s+URL\(\s*["'](\/ns\/m\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}${p1}?v=${ver}`)}`);
4352
+ code = code.replace(/new\s+URL\(\s*["'](\/ns\/m\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}${rewritePath(p1)}`)}`);
2934
4353
  }
2935
4354
  catch { }
2936
4355
  // 7) Also fix SFC new URL('/ns/sfc/...', import.meta.url).href → "${origin}/ns/sfc/<ver>/..."
@@ -2946,13 +4365,25 @@ export const piniaSymbol = p.piniaSymbol;
2946
4365
  code = ensureDestructureCoreImports(code);
2947
4366
  }
2948
4367
  catch { }
4368
+ // Boot-time module graph progress: while the app is still replacing the
4369
+ // placeholder, emit lightweight progress updates as /ns/m modules begin
4370
+ // evaluating. This keeps the overlay moving during large initial graphs.
4371
+ try {
4372
+ if (bootTaggedRequest) {
4373
+ const bootModuleLabel = String(spec || '').replace(/\\/g, '/');
4374
+ const bootProgressSnippet = buildBootProgressSnippet(bootModuleLabel);
4375
+ code = bootProgressSnippet + code;
4376
+ code = hoistTopLevelStaticImports(code);
4377
+ }
4378
+ }
4379
+ catch { }
2949
4380
  // Dev-only: link-check static imports to surface missing bindings early
2950
4381
  try {
2951
4382
  const devCheck = process.env.NODE_ENV !== 'production';
2952
4383
  if (devCheck) {
2953
4384
  const ast = babelParse(code, {
2954
4385
  sourceType: 'module',
2955
- plugins: ['typescript', 'importMeta'],
4386
+ plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
2956
4387
  });
2957
4388
  const imports = [];
2958
4389
  babelTraverse(ast, {
@@ -3036,6 +4467,16 @@ export const piniaSymbol = p.piniaSymbol;
3036
4467
  continue;
3037
4468
  const hasDefault = /\bexport\s+default\b/.test(targetCode) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(targetCode);
3038
4469
  if (!hasDefault) {
4470
+ // CJS/UMD modules won't have `export default` — they get CJS-wrapped
4471
+ // by the serving pipeline. Only warn, don't fatally block the importer.
4472
+ const hasCjsPattern = /\bmodule\s*\.\s*exports\b/.test(targetCode) || /\bexports\s*\.\s*\w/.test(targetCode);
4473
+ if (hasCjsPattern) {
4474
+ try {
4475
+ console.warn(`[ns:m][link-check] CJS module without export default: ${u.pathname} (will be CJS-wrapped at serve time)`);
4476
+ }
4477
+ catch { }
4478
+ continue;
4479
+ }
3039
4480
  const msg = `[link-check] Missing default export in ${u.pathname}${u.search} (imported by ${resolvedCandidate || spec})`;
3040
4481
  // Emit a module that throws to surface the exact offender
3041
4482
  res.statusCode = 200;
@@ -3052,6 +4493,16 @@ export const piniaSymbol = p.piniaSymbol;
3052
4493
  }
3053
4494
  catch { }
3054
4495
  }
4496
+ // Diagnostic: dump served code to terminal when ?__diag=1 is in the original URL
4497
+ try {
4498
+ if (urlObj?.searchParams?.get('__diag') === '1') {
4499
+ const specId = resolvedCandidate || spec;
4500
+ console.log(`\n${'='.repeat(80)}\n[ns:m][DIAG] ${specId}\n${'='.repeat(80)}`);
4501
+ console.log(code);
4502
+ console.log(`${'='.repeat(80)}\n[ns:m][DIAG] END ${specId}\n${'='.repeat(80)}\n`);
4503
+ }
4504
+ }
4505
+ catch { }
3055
4506
  res.statusCode = 200;
3056
4507
  res.end(code);
3057
4508
  }
@@ -3251,17 +4702,59 @@ export const piniaSymbol = p.piniaSymbol;
3251
4702
  server.middlewares.use(async (req, res, next) => {
3252
4703
  try {
3253
4704
  const urlObj = new URL(req.url || '', 'http://localhost');
3254
- if (!(urlObj.pathname === '/ns/core' || /^\/ns\/core\/[\d]+$/.test(urlObj.pathname)))
4705
+ // Match /ns/core, /ns/core/<ver>, and /ns/core/<subpath> (path-based deep imports)
4706
+ if (!urlObj.pathname.startsWith('/ns/core'))
3255
4707
  return next();
3256
4708
  res.setHeader('Access-Control-Allow-Origin', '*');
3257
4709
  res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
3258
4710
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3259
4711
  res.setHeader('Pragma', 'no-cache');
3260
4712
  res.setHeader('Expires', '0');
3261
- const verSeg = urlObj.pathname.replace(/^\/ns\/core\/?/, '');
3262
- const ver = /^[0-9]+$/.test(verSeg) ? verSeg : String(graphVersion || 0);
3263
- const sub = urlObj.searchParams.get('p') || '';
4713
+ const afterCore = urlObj.pathname.replace(/^\/ns\/core\/?/, '');
4714
+ const hasExplicitVersion = /^[0-9]+$/.test(afterCore);
4715
+ const ver = /^[0-9]+$/.test(afterCore) ? afterCore : String(graphVersion || 0);
4716
+ // Support both query-based (?p=data/observable/index.js) and
4717
+ // path-based (/ns/core/data/observable/index.js) subpath formats.
4718
+ // The device's HTTP ESM loader may use either depending on the import map.
4719
+ const sub = urlObj.searchParams.get('p') || (afterCore && !/^[0-9]+$/.test(afterCore) ? afterCore : '');
3264
4720
  const key = sub ? `@nativescript/core/${sub}` : `@nativescript/core`;
4721
+ // Any @nativescript/core subpath import (including shallow ones like
4722
+ // `utils`) may expose exports that are not available from the root
4723
+ // vendor bundle namespace. Serve the actual transformed module content
4724
+ // instead of the lightweight proxy bridge.
4725
+ if (sub) {
4726
+ try {
4727
+ const normalizedSub = sub.replace(/^\/+/, '');
4728
+ if (!hasExplicitVersion) {
4729
+ res.statusCode = 200;
4730
+ res.end(buildVersionedCoreSubpathAliasModule(normalizedSub, ver));
4731
+ return;
4732
+ }
4733
+ const coreSpecifier = `@nativescript/core/${normalizedSub}`;
4734
+ const resolved = await server.pluginContainer?.resolveId?.(coreSpecifier, undefined);
4735
+ const resolvedId = typeof resolved === 'string' ? resolved : resolved?.id || null;
4736
+ const modulePath = resolvedId || `/node_modules/@nativescript/core/${normalizedSub}`;
4737
+ const transformed = await sharedTransformRequest(modulePath);
4738
+ if (transformed?.code) {
4739
+ // Minimal pipeline: Vite already produces correct ESM.
4740
+ // ONLY rewrite specifier strings to device-fetchable URLs.
4741
+ // Do NOT run processCodeForDevice, rewriteImports, or any
4742
+ // other heavy transform — those mangle newlines, eat exports,
4743
+ // and cause cascading "does not provide an export" failures.
4744
+ const moduleCode = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
4745
+ res.statusCode = 200;
4746
+ res.end(moduleCode);
4747
+ return;
4748
+ }
4749
+ }
4750
+ catch (e) {
4751
+ try {
4752
+ console.warn('[ns-core-bridge] deep subpath serve failed:', sub, e?.message);
4753
+ }
4754
+ catch { }
4755
+ }
4756
+ }
4757
+ // Main entry or shallow subpath: use proxy bridge
3265
4758
  // HTTP-only core bridge: do NOT use require/createRequire. Export a proxy that maps
3266
4759
  // property access to globalThis first, then to any available vendor registry module.
3267
4760
  let code = REQUIRE_GUARD_SNIPPET +
@@ -3269,7 +4762,7 @@ export const piniaSymbol = p.piniaSymbol;
3269
4762
  `const g = globalThis;\n` +
3270
4763
  `const reg = (g.__nsVendorRegistry ||= new Map());\n` +
3271
4764
  `const __getVendorCore = () => { try { const m = reg && reg.get ? (reg.get(${JSON.stringify(key)}) || reg.get('@nativescript/core')) : null; return (m && (m.__esModule && m.default ? m.default : (m.default || m))) || m || null; } catch { return null; } };\n` +
3272
- `const __core = new Proxy({}, { get(_t, p){ if (p === 'default') return __core; if (p === Symbol.toStringTag) return 'Module'; try { const v = g[p]; if (v !== undefined) return v; } catch {} try { const vc = __getVendorCore(); return vc ? vc[p] : undefined; } catch {} return undefined; } });\n` +
4765
+ `const __core = new Proxy({}, { get(_t, p){ if (p === 'default') return __core; if (p === Symbol.toStringTag) return 'Module'; try { const vc = __getVendorCore(); if (vc) { const vv = vc[p]; if (vv !== undefined) return vv; } } catch {} try { const v = g[p]; if (v !== undefined) return v; } catch {} return undefined; } });\n` +
3273
4766
  `// Default export: namespace-like proxy\n` +
3274
4767
  `export default __core;\n`;
3275
4768
  res.statusCode = 200;
@@ -3300,18 +4793,40 @@ export const piniaSymbol = p.piniaSymbol;
3300
4793
  res.setHeader('Expires', '0');
3301
4794
  let content = '';
3302
4795
  try {
3303
- const req = createRequire(import.meta.url);
3304
- const entryRtPath = req.resolve('@nativescript/vite/hmr/entry-runtime.js');
3305
- const fs = req('fs');
3306
- content = fs.readFileSync(entryRtPath, 'utf-8');
4796
+ const _req = createRequire(import.meta.url);
4797
+ const entryRtPath = _req.resolve('@nativescript/vite/hmr/entry-runtime.js');
4798
+ content = readFileSync(entryRtPath, 'utf-8');
3307
4799
  }
3308
4800
  catch (e) {
3309
- content = 'export default async function start(){ console.error("[/ns/entry-rt] not found"); }\n';
4801
+ // .js not found (source tree without build) — transform .ts on the fly
4802
+ try {
4803
+ const tsPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'entry-runtime.ts');
4804
+ if (existsSync(tsPath)) {
4805
+ const tsSource = readFileSync(tsPath, 'utf-8');
4806
+ const result = babelCore.transformSync(tsSource, {
4807
+ filename: tsPath,
4808
+ plugins: [[pluginTransformTypescript, { isTSX: false, allowDeclareFields: true }]],
4809
+ sourceType: 'module',
4810
+ });
4811
+ if (result?.code) {
4812
+ content = result.code;
4813
+ }
4814
+ }
4815
+ }
4816
+ catch (e2) {
4817
+ if (verbose)
4818
+ console.warn('[hmr-http] entry-runtime.ts transform failed', e2);
4819
+ }
4820
+ if (!content) {
4821
+ content = 'export default async function start(){ console.error("[/ns/entry-rt] not found"); }\n';
4822
+ }
3310
4823
  }
4824
+ console.log('[hmr-http] /ns/entry-rt serving', content.length, 'bytes');
3311
4825
  res.statusCode = 200;
3312
4826
  res.end(content);
3313
4827
  }
3314
4828
  catch (e) {
4829
+ console.warn('[hmr-http] /ns/entry-rt error', e);
3315
4830
  next();
3316
4831
  }
3317
4832
  });
@@ -3741,7 +5256,7 @@ export const piniaSymbol = p.piniaSymbol;
3741
5256
  code = outCode;
3742
5257
  }
3743
5258
  catch { }
3744
- code = processCodeForDevice(code, false);
5259
+ code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(fullSpec), fullSpec);
3745
5260
  // Transform static .vue imports into static imports from the assembler (no TLA) via AST
3746
5261
  try {
3747
5262
  const importerPath = fullSpec.replace(/[?#].*$/, '');
@@ -4297,7 +5812,7 @@ export const piniaSymbol = p.piniaSymbol;
4297
5812
  parts.push(`export function render(){ const f = (typeof __ns_getRender==='function' ? __ns_getRender() : (__ns_sfc__ && __ns_sfc__.render)); return typeof f==='function' ? f.apply(this, arguments) : undefined; }`);
4298
5813
  parts.push(`export default __ns_sfc__`);
4299
5814
  let inlineCode = parts.filter(Boolean).join('\n');
4300
- inlineCode = processCodeForDevice(inlineCode, false);
5815
+ inlineCode = processCodeForDevice(inlineCode, false, true);
4301
5816
  try {
4302
5817
  inlineCode = ensureVersionedCoreImports(inlineCode, getServerOrigin(server), Number(ver));
4303
5818
  }
@@ -4377,7 +5892,7 @@ export const piniaSymbol = p.piniaSymbol;
4377
5892
  outParts.push('export function render(){ const f = (typeof __ns_getRender==="function" ? __ns_getRender() : (typeof __ns_render==="function" ? __ns_render : (__ns_sfc__ && __ns_sfc__.render))); return typeof f === "function" ? f.apply(this, arguments) : undefined; }');
4378
5893
  outParts.push('export default __ns_sfc__');
4379
5894
  let inlineCode2 = outParts.filter(Boolean).join('\n');
4380
- inlineCode2 = processCodeForDevice(inlineCode2, false);
5895
+ inlineCode2 = processCodeForDevice(inlineCode2, false, true);
4381
5896
  try {
4382
5897
  inlineCode2 = ensureVersionedCoreImports(inlineCode2, getServerOrigin(server), Number(ver));
4383
5898
  }
@@ -4689,7 +6204,7 @@ export const piniaSymbol = p.piniaSymbol;
4689
6204
  }
4690
6205
  // Run full device processing so helper aliasing and globals are consistent in this path too
4691
6206
  let code = REQUIRE_GUARD_SNIPPET + asm;
4692
- code = processCodeForDevice(code, false);
6207
+ code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(base), base);
4693
6208
  try {
4694
6209
  code = ensureVersionedCoreImports(code, getServerOrigin(server), Number(ver));
4695
6210
  }
@@ -4862,7 +6377,7 @@ export const piniaSymbol = p.piniaSymbol;
4862
6377
  let code = transformed.code;
4863
6378
  // Reuse existing sanitation chain (lightweight)
4864
6379
  code = cleanCode(code);
4865
- code = processCodeForDevice(code, false);
6380
+ code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
4866
6381
  try {
4867
6382
  code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
4868
6383
  }
@@ -4917,7 +6432,7 @@ export const piniaSymbol = p.piniaSymbol;
4917
6432
  if (depTrans?.code && depResolved) {
4918
6433
  let depCode = depTrans.code;
4919
6434
  depCode = cleanCode(depCode);
4920
- depCode = processCodeForDevice(depCode, false);
6435
+ depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
4921
6436
  try {
4922
6437
  depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
4923
6438
  }
@@ -4990,32 +6505,33 @@ export const piniaSymbol = p.piniaSymbol;
4990
6505
  if (verbose)
4991
6506
  console.warn('[hmr-ws][graph] initial population failed', e);
4992
6507
  }
4993
- // Send SFC registry on first connection
4994
- if (!registrySent) {
4995
- try {
4996
- await ACTIVE_STRATEGY.buildRegistry({
4997
- server,
4998
- sfcFileMap,
4999
- depFileMap,
5000
- wss: wss,
5001
- verbose,
5002
- helpers: {
5003
- cleanCode,
5004
- collectImportDependencies,
5005
- isCoreGlobalsReference,
5006
- isNativeScriptCoreModule,
5007
- isNativeScriptPluginModule,
5008
- resolveVendorFromCandidate,
5009
- createHash: (value) => createHash('md5').update(value).digest('hex'),
5010
- rewriteImports,
5011
- processSfcCode,
5012
- },
5013
- });
5014
- registrySent = true;
5015
- }
5016
- catch (error) {
5017
- console.warn('[hmr-ws] Failed to send registry:', error);
5018
- }
6508
+ // Send SFC registry on every connection (not just the first).
6509
+ // When the NativeScript app restarts (e.g. CLI auto-reload), the new
6510
+ // JS context has an empty sfcArtifactMap. Without the registry the
6511
+ // rescue-mount cannot find the root .vue component.
6512
+ try {
6513
+ await ACTIVE_STRATEGY.buildRegistry({
6514
+ server,
6515
+ sfcFileMap,
6516
+ depFileMap,
6517
+ wss: wss,
6518
+ verbose,
6519
+ helpers: {
6520
+ cleanCode,
6521
+ collectImportDependencies,
6522
+ isCoreGlobalsReference,
6523
+ isNativeScriptCoreModule,
6524
+ isNativeScriptPluginModule,
6525
+ resolveVendorFromCandidate,
6526
+ createHash: (value) => createHash('md5').update(value).digest('hex'),
6527
+ rewriteImports,
6528
+ processSfcCode,
6529
+ },
6530
+ });
6531
+ registrySent = true;
6532
+ }
6533
+ catch (error) {
6534
+ console.warn('[hmr-ws] Failed to send registry:', error);
5019
6535
  }
5020
6536
  emitFullGraph(ws);
5021
6537
  // After sending registry & graph also send current module manifest if any
@@ -5040,14 +6556,30 @@ export const piniaSymbol = p.piniaSymbol;
5040
6556
  }
5041
6557
  // Graph update for this file change (wrapped to avoid aborting rest of handler)
5042
6558
  try {
5043
- const mod = server.moduleGraph.getModuleById(file) || server.moduleGraph.getModuleById(file + '?vue');
5044
- if (mod) {
5045
- const deps = Array.from(mod.importedModules)
5046
- .map((m) => (m.id || '').replace(/\?.*$/, ''))
5047
- .filter(Boolean);
5048
- const transformed = await server.transformRequest(mod.id);
5049
- const code = transformed?.code || '';
5050
- upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps);
6559
+ const skipAngularHtmlGraphUpdate = ACTIVE_STRATEGY.flavor === 'angular' && /\.(html|htm)$/i.test(file);
6560
+ if (!skipAngularHtmlGraphUpdate) {
6561
+ const graphTargets = collectGraphUpdateModulesForHotUpdate({
6562
+ file,
6563
+ flavor: ACTIVE_STRATEGY.flavor,
6564
+ modules: ctx.modules,
6565
+ getModuleById: (id) => server.moduleGraph.getModuleById(id),
6566
+ });
6567
+ for (const mod of graphTargets) {
6568
+ if (!mod?.id)
6569
+ continue;
6570
+ try {
6571
+ const deps = Array.from(mod.importedModules || [])
6572
+ .map((m) => (m.id || '').replace(/\?.*$/, ''))
6573
+ .filter(Boolean);
6574
+ const transformed = await server.transformRequest(mod.id);
6575
+ const code = transformed?.code || '';
6576
+ upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps);
6577
+ }
6578
+ catch (error) {
6579
+ if (verbose)
6580
+ console.warn('[hmr-ws][v2] failed graph update target', mod.id, error);
6581
+ }
6582
+ }
5051
6583
  }
5052
6584
  }
5053
6585
  catch (e) {
@@ -5099,11 +6631,93 @@ export const piniaSymbol = p.piniaSymbol;
5099
6631
  // For Angular, react to component TS or external template HTML changes under /src
5100
6632
  const isHtml = file.endsWith('.html');
5101
6633
  const isTs = file.endsWith('.ts');
6634
+ const angularHotUpdateRoots = collectAngularHotUpdateRoots({
6635
+ file,
6636
+ modules: ctx.modules,
6637
+ getModuleById: (id) => server.moduleGraph.getModuleById(id),
6638
+ getModulesByFile: (targetFile) => server.moduleGraph.getModulesByFile?.(targetFile),
6639
+ });
5102
6640
  if (!(isHtml || isTs))
5103
6641
  return;
6642
+ if (angularHotUpdateRoots.length) {
6643
+ for (const mod of angularHotUpdateRoots) {
6644
+ try {
6645
+ server.moduleGraph.invalidateModule(mod);
6646
+ }
6647
+ catch (invalidationError) {
6648
+ if (verbose) {
6649
+ console.warn('[hmr-ws][angular] hot-update root invalidation failed', mod?.id, invalidationError);
6650
+ }
6651
+ }
6652
+ }
6653
+ if (verbose) {
6654
+ console.log('[hmr-ws][angular] invalidated hot-update root modules:', angularHotUpdateRoots.length);
6655
+ }
6656
+ }
6657
+ const angularTransitiveInvalidationRoots = (angularHotUpdateRoots.length ? angularHotUpdateRoots : ctx.modules);
6658
+ if (shouldInvalidateAngularTransitiveImporters({ flavor: ACTIVE_STRATEGY.flavor, file })) {
6659
+ try {
6660
+ const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
6661
+ modules: angularTransitiveInvalidationRoots,
6662
+ isExcluded: (id) => id.includes('/node_modules/'),
6663
+ maxDepth: 16,
6664
+ });
6665
+ for (const mod of transitiveImporters) {
6666
+ try {
6667
+ server.moduleGraph.invalidateModule(mod);
6668
+ }
6669
+ catch (invalidationError) {
6670
+ if (verbose) {
6671
+ console.warn('[hmr-ws][angular] transitive importer invalidation failed', mod?.id, invalidationError);
6672
+ }
6673
+ }
6674
+ }
6675
+ if (verbose && transitiveImporters.length) {
6676
+ console.log('[hmr-ws][angular] invalidated transitive importers:', transitiveImporters.length);
6677
+ }
6678
+ }
6679
+ catch (error) {
6680
+ if (verbose)
6681
+ console.warn('[hmr-ws][angular] transitive importer collection failed', error);
6682
+ }
6683
+ }
6684
+ try {
6685
+ const transformCacheInvalidationUrls = new Set();
6686
+ if (isTs) {
6687
+ transformCacheInvalidationUrls.add(file);
6688
+ }
6689
+ for (const mod of angularHotUpdateRoots) {
6690
+ if (mod?.id) {
6691
+ transformCacheInvalidationUrls.add(mod.id);
6692
+ }
6693
+ }
6694
+ if (shouldInvalidateAngularTransitiveImporters({ flavor: ACTIVE_STRATEGY.flavor, file })) {
6695
+ const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
6696
+ modules: angularTransitiveInvalidationRoots,
6697
+ isExcluded: (id) => id.includes('/node_modules/'),
6698
+ maxDepth: 16,
6699
+ });
6700
+ for (const mod of transitiveImporters) {
6701
+ if (mod?.id) {
6702
+ transformCacheInvalidationUrls.add(mod.id);
6703
+ }
6704
+ }
6705
+ }
6706
+ if (transformCacheInvalidationUrls.size) {
6707
+ sharedTransformRequest.invalidateMany(transformCacheInvalidationUrls);
6708
+ if (verbose) {
6709
+ console.log('[hmr-ws][angular] purged shared transform cache entries:', transformCacheInvalidationUrls.size);
6710
+ }
6711
+ }
6712
+ }
6713
+ catch (error) {
6714
+ if (verbose)
6715
+ console.warn('[hmr-ws][angular] shared transform cache purge failed', error);
6716
+ }
5104
6717
  try {
5105
6718
  const root = server.config.root || process.cwd();
5106
6719
  const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
6720
+ rememberAngularReloadSuppression(root, file);
5107
6721
  const origin = getServerOrigin(server);
5108
6722
  const msg = {
5109
6723
  type: 'ns:angular-update',
@@ -5120,6 +6734,9 @@ export const piniaSymbol = p.piniaSymbol;
5120
6734
  catch (error) {
5121
6735
  console.warn('[hmr-ws][angular] update failed:', error);
5122
6736
  }
6737
+ if (shouldSuppressDefaultViteHotUpdate({ flavor: ACTIVE_STRATEGY.flavor, file })) {
6738
+ return [];
6739
+ }
5123
6740
  return;
5124
6741
  }
5125
6742
  // TypeScript flavor: emit generic graph delta for app XML/TS/style changes
@@ -5142,6 +6759,43 @@ export const piniaSymbol = p.piniaSymbol;
5142
6759
  }
5143
6760
  return;
5144
6761
  }
6762
+ // Solid flavor: emit graph delta for app TSX/TS/JSX file changes.
6763
+ // The common graph-update block above (moduleGraph lookup) may have
6764
+ // already emitted a delta if the file was in Vite's module graph.
6765
+ // This handler ensures a delta is emitted even if the module wasn't
6766
+ // found (e.g. new file, or moduleGraph mismatch), and provides
6767
+ // Solid-specific logging. The client-side processQueue handles
6768
+ // propagation from non-component .ts files to .tsx component boundaries.
6769
+ if (ACTIVE_STRATEGY.flavor === 'solid') {
6770
+ const isSolidFile = /\.(tsx?|jsx?)$/i.test(file);
6771
+ if (!isSolidFile)
6772
+ return;
6773
+ try {
6774
+ const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
6775
+ if (verbose)
6776
+ console.log('[hmr-ws][solid] app file hot update', { file, rel });
6777
+ // If the common block already upserted (hash changed), this will
6778
+ // detect unchanged hash and no-op. If the common block missed it
6779
+ // (module not in Vite's graph), this forces the delta emission.
6780
+ const normalizedId = normalizeGraphId(rel);
6781
+ const existing = graph.get(normalizedId);
6782
+ if (!existing) {
6783
+ // Module not in graph yet — force upsert with timestamp-based
6784
+ // hash so the client sees a change.
6785
+ upsertGraphModule(rel, `/* solid-hmr ${Date.now()} */`, []);
6786
+ }
6787
+ // Log what we're sending so devs can trace the flow on the server side.
6788
+ if (verbose) {
6789
+ const gm = graph.get(normalizedId);
6790
+ console.log('[hmr-ws][solid] delta module', { id: gm?.id, hash: gm?.hash });
6791
+ }
6792
+ }
6793
+ catch (e) {
6794
+ if (verbose)
6795
+ console.warn('[hmr-ws][solid] failed to handle hot update for', file, e);
6796
+ }
6797
+ return;
6798
+ }
5145
6799
  // Handle .vue file updates
5146
6800
  if (!file.endsWith('.vue')) {
5147
6801
  if (verbose)
@@ -5249,6 +6903,7 @@ export const piniaSymbol = p.piniaSymbol;
5249
6903
  // Rewrite ONLY .vue imports (everything else is now inlined)
5250
6904
  const projectRoot = server.config.root || process.cwd();
5251
6905
  code = rewriteImports(code, rel, sfcFileMap, depFileMap, projectRoot, opts.verbose, undefined);
6906
+ upsertGraphModule(rel, code, [...deps, ...vueDeps]);
5252
6907
  // Add HMR runtime prelude (CRITICAL for runtime)
5253
6908
  const hmrPrelude = `
5254
6909
  // Embedded HMR Runtime for NativeScript runtime