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

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 (81) hide show
  1. package/configuration/angular.js +45 -8
  2. package/configuration/angular.js.map +1 -1
  3. package/configuration/base.js +1 -1
  4. package/configuration/base.js.map +1 -1
  5. package/helpers/commonjs-plugins.d.ts +5 -2
  6. package/helpers/commonjs-plugins.js +126 -0
  7. package/helpers/commonjs-plugins.js.map +1 -1
  8. package/helpers/main-entry.d.ts +1 -0
  9. package/helpers/main-entry.js +26 -85
  10. package/helpers/main-entry.js.map +1 -1
  11. package/hmr/client/index.js +72 -19
  12. package/hmr/client/index.js.map +1 -1
  13. package/hmr/client/utils.js +57 -6
  14. package/hmr/client/utils.js.map +1 -1
  15. package/hmr/entry-runtime.js +1 -1
  16. package/hmr/entry-runtime.js.map +1 -1
  17. package/hmr/frameworks/angular/client/index.d.ts +2 -1
  18. package/hmr/frameworks/angular/client/index.js +51 -2
  19. package/hmr/frameworks/angular/client/index.js.map +1 -1
  20. package/hmr/frameworks/angular/server/strategy.js +20 -5
  21. package/hmr/frameworks/angular/server/strategy.js.map +1 -1
  22. package/hmr/frameworks/typescript/server/strategy.js +8 -2
  23. package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
  24. package/hmr/server/core-sanitize.d.ts +3 -2
  25. package/hmr/server/core-sanitize.js +34 -17
  26. package/hmr/server/core-sanitize.js.map +1 -1
  27. package/hmr/server/import-map.js +20 -14
  28. package/hmr/server/import-map.js.map +1 -1
  29. package/hmr/server/index.d.ts +2 -1
  30. package/hmr/server/index.js.map +1 -1
  31. package/hmr/server/runtime-graph-filter.d.ts +5 -0
  32. package/hmr/server/runtime-graph-filter.js +21 -0
  33. package/hmr/server/runtime-graph-filter.js.map +1 -0
  34. package/hmr/server/shared-transform-request.d.ts +12 -0
  35. package/hmr/server/shared-transform-request.js +137 -0
  36. package/hmr/server/shared-transform-request.js.map +1 -0
  37. package/hmr/server/vite-plugin.d.ts +21 -1
  38. package/hmr/server/vite-plugin.js +443 -22
  39. package/hmr/server/vite-plugin.js.map +1 -1
  40. package/hmr/server/websocket-angular-entry.d.ts +2 -0
  41. package/hmr/server/websocket-angular-entry.js +68 -0
  42. package/hmr/server/websocket-angular-entry.js.map +1 -0
  43. package/hmr/server/websocket-angular-hot-update.d.ts +61 -0
  44. package/hmr/server/websocket-angular-hot-update.js +239 -0
  45. package/hmr/server/websocket-angular-hot-update.js.map +1 -0
  46. package/hmr/server/websocket-core-bridge.d.ts +23 -0
  47. package/hmr/server/websocket-core-bridge.js +360 -0
  48. package/hmr/server/websocket-core-bridge.js.map +1 -0
  49. package/hmr/server/websocket-graph-upsert.d.ts +6 -0
  50. package/hmr/server/websocket-graph-upsert.js +13 -0
  51. package/hmr/server/websocket-graph-upsert.js.map +1 -0
  52. package/hmr/server/websocket-module-bindings.d.ts +6 -0
  53. package/hmr/server/websocket-module-bindings.js +471 -0
  54. package/hmr/server/websocket-module-bindings.js.map +1 -0
  55. package/hmr/server/websocket-module-specifiers.d.ts +37 -0
  56. package/hmr/server/websocket-module-specifiers.js +637 -0
  57. package/hmr/server/websocket-module-specifiers.js.map +1 -0
  58. package/hmr/server/websocket.d.ts +21 -74
  59. package/hmr/server/websocket.js +455 -1386
  60. package/hmr/server/websocket.js.map +1 -1
  61. package/hmr/shared/package-classifier.d.ts +9 -0
  62. package/hmr/shared/package-classifier.js +58 -0
  63. package/hmr/shared/package-classifier.js.map +1 -0
  64. package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
  65. package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
  66. package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
  67. package/hmr/shared/runtime/dev-overlay.d.ts +1 -1
  68. package/hmr/shared/runtime/dev-overlay.js +23 -12
  69. package/hmr/shared/runtime/dev-overlay.js.map +1 -1
  70. package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
  71. package/hmr/shared/runtime/root-placeholder.js +430 -52
  72. package/hmr/shared/runtime/root-placeholder.js.map +1 -1
  73. package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
  74. package/hmr/shared/runtime/session-bootstrap.js +146 -0
  75. package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
  76. package/hmr/shared/vendor/manifest.d.ts +2 -0
  77. package/hmr/shared/vendor/manifest.js +24 -10
  78. package/hmr/shared/vendor/manifest.js.map +1 -1
  79. package/package.json +7 -1
  80. package/runtime/core-aliases-early.js +83 -32
  81. package/runtime/core-aliases-early.js.map +1 -1
@@ -4,12 +4,11 @@ import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCore
4
4
  import { parse as babelParse } from '@babel/parser';
5
5
  import { genCode } from '../helpers/babel.js';
6
6
  import babelCore from '@babel/core';
7
- import pluginTransformTypescript from '@babel/plugin-transform-typescript';
8
7
  import traverse from '@babel/traverse';
9
8
  // Ensure traverse callable across CJS/ESM builds
10
9
  const babelTraverse = traverse?.default || traverse;
11
10
  import * as t from '@babel/types';
12
- import { existsSync, readFileSync } from 'fs';
11
+ import { existsSync, readFileSync, statSync } from 'fs';
13
12
  import { astNormalizeModuleImportsAndHelpers, astVerifyAndAnnotateDuplicates } from '../helpers/ast-normalizer.js';
14
13
  import { stripRtCoreSentinel, stripDanglingViteCjsImports } from '../helpers/sanitize.js';
15
14
  import { WebSocketServer } from 'ws';
@@ -17,7 +16,7 @@ import * as path from 'path';
17
16
  import { createHash } from 'crypto';
18
17
  import * as PAT from './constants.js';
19
18
  import { getVendorManifest, resolveVendorSpecifier } from '../shared/vendor/registry.js';
20
- import { getPackageJson, getProjectFilePath } from '../../helpers/project.js';
19
+ import { getPackageJson, getProjectFilePath, getProjectRootPath } from '../../helpers/project.js';
21
20
  import { loadPrebuiltVendorManifest } from '../shared/vendor/manifest-loader.js';
22
21
  import '../vendor-bootstrap.js';
23
22
  import { NS_NATIVE_TAGS } from './compiler.js';
@@ -32,13 +31,31 @@ import { astExtractImportsAndStripTypes } from '../helpers/ast-extract.js';
32
31
  import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
33
32
  import { buildRuntimeConfig, generateImportMap } from './import-map.js';
34
33
  import { getCliFlags } from '../../helpers/cli-flags.js';
34
+ import { isRuntimeGraphExcludedPath, matchesRuntimeGraphModuleId, normalizeRuntimeGraphPath, shouldIncludeRuntimeGraphFile, shouldSkipRuntimeGraphDirectoryName } from './runtime-graph-filter.js';
35
+ import { resolveAngularCoreHmrImportSource, rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
36
+ import { canonicalizeTransformRequestCacheKey, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload } from './websocket-angular-hot-update.js';
37
+ import { classifyGraphUpsert, shouldBroadcastGraphUpsertDelta } from './websocket-graph-upsert.js';
38
+ import { extractVitePrebundleId, filterExistingNodeModulesTransformCandidates, getBlockedDeviceNodeModulesReason, getFlattenedManifestMap, isCoreGlobalsReference, isEsmFrameworkPackageSpecifier, isLikelyNativeScriptPluginSpecifier, isLikelyNativeScriptRuntimePluginSpecifier, isNativeScriptCoreModule, isNativeScriptPluginModule, normalizeNativeScriptCoreSpecifier, normalizeNodeModulesSpecifier, resolveCandidateFilePath, resolveInternalRuntimePluginBareSpecifier, resolveNodeModulesPackageBoundary, resolveVendorFromCandidate, resolveVendorRouting, shouldPreserveBareRuntimePluginSubpathImport, stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule, viteDepsPathToBareSpecifier, } from './websocket-module-specifiers.js';
39
+ import { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
40
+ import { buildVersionedCoreMainBridgeModule, buildVersionedCoreSubpathAliasModule, collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, extractDirectExportedNames, hasModuleDefaultExport, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest, resolveRuntimeCoreModulePath } from './websocket-core-bridge.js';
41
+ import { createSharedTransformRequestRunner } from './shared-transform-request.js';
42
+ export { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
43
+ export { stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
44
+ export { buildVersionedCoreMainBridgeModule, buildVersionedCoreSubpathAliasModule, collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest } from './websocket-core-bridge.js';
45
+ export { rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
46
+ export { canonicalizeTransformRequestCacheKey, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, createSharedTransformRequestRunner, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload, classifyGraphUpsert, shouldBroadcastGraphUpsertDelta };
47
+ const pluginTransformTypescript = (() => {
48
+ const requireFromHere = createRequire(import.meta.url);
49
+ const loaded = requireFromHere('@babel/plugin-transform-typescript');
50
+ return loaded?.default || loaded;
51
+ })();
35
52
  // Build a serialized process.env object from CLI --env.* flags.
36
53
  // This is injected into every HTTP-served module so app code referencing
37
54
  // process.env.TEST_ENV (etc.) works on device in HMR dev mode.
38
55
  const __processEnvEntries = { NODE_ENV: 'development' };
39
56
  try {
40
57
  const flags = getCliFlags();
41
- for (const [k, v] of Object.entries(flags)) {
58
+ for (const [k, v] of Object.entries(flags || {})) {
42
59
  // Skip internal NativeScript build flags
43
60
  if (['ios', 'android', 'visionos', 'platform', 'hmr', 'verbose'].includes(k))
44
61
  continue;
@@ -60,182 +77,46 @@ const STRATEGY_REGISTRY = new Map([
60
77
  ['typescript', typescriptServerStrategy],
61
78
  ]);
62
79
  function resolveFrameworkStrategy(flavor) {
63
- return STRATEGY_REGISTRY.get(flavor);
64
- }
65
- let ACTIVE_STRATEGY;
66
- const processSfcCode = createProcessSfcCode(processCodeForDevice);
67
- // Bare specifiers and special skip patterns (virtual, data:, etc.)
68
- const VENDOR_PACKAGES = /^[A-Za-z@][^:\/\s]*$/;
69
- const SKIP_PATTERNS = /^(?:data:|blob:|node:|virtual:|vite:|\0|\/@@?id|\/__vite|__vite|__x00__)/;
70
- // Minimal helpers to support vendor pre-bundle detection
71
- function extractVitePrebundleId(spec) {
72
- const m = spec.match(/\.vite\/deps\/([^?]+?)\.[mc]?js/);
73
- if (m)
74
- return m[1];
75
- const m2 = spec.match(/__x00__([^?]+?)\.[mc]?js/);
76
- if (m2)
77
- return m2[1];
78
- return null;
79
- }
80
- function getFlattenedManifestMap(manifest) {
81
- const map = new Map();
82
- const mods = Object.keys(manifest.modules || {});
83
- for (const canonical of mods) {
84
- const flat = canonical.replace(/\./g, '__').replace(/\//g, '_');
85
- map.set(flat, canonical);
86
- const alias = manifest.aliases?.[canonical];
87
- if (alias) {
88
- const aliasFlat = String(alias).replace(/\./g, '__').replace(/\//g, '_');
89
- map.set(aliasFlat, canonical);
90
- }
80
+ const strategy = STRATEGY_REGISTRY.get(flavor);
81
+ if (!strategy) {
82
+ throw new Error(`[ns-hmr] Unsupported framework strategy: ${flavor}`);
91
83
  }
92
- return map;
93
- }
94
- // NativeScript module detectors
95
- function isCoreGlobalsReference(spec) {
96
- return /@nativescript(?:[\/_-])core(?:[\/_-])globals/.test(spec || '');
97
- }
98
- function isNativeScriptCoreModule(spec) {
99
- return /^(?:@nativescript[\/_-]core|@nativescript\/core)(?:\b|\/)/i.test(spec || '');
100
- }
101
- function isNativeScriptPluginModule(spec) {
102
- return /^@nativescript\//i.test(spec || '') && !isNativeScriptCoreModule(spec || '');
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);
84
+ return strategy;
130
85
  }
131
- // Looser detector for NativeScript plugin-style specifiers that should be resolved
132
- // via device require() rather than HTTP during HMR. This includes popular community
133
- // scopes in addition to @nativescript/* (excluding core).
134
- function isLikelyNativeScriptPluginSpecifier(spec) {
135
- if (!spec)
136
- return false;
137
- const s = spec.replace(PAT.QUERY_PATTERN, '');
138
- // Absolute or relative paths are not bare packages
139
- if (/^(?:\.|\/|https?:\/\/)/i.test(s))
140
- return false;
141
- // App alias paths like '@/...' are not vendor packages
142
- if (s.startsWith('@@/'))
143
- return false; // extremely rare double '@' alias
144
- if (s.startsWith('~/'))
145
- return false; // NativeScript tilde alias (app root)
146
- if (s.startsWith('@/'))
147
- return false; // Common Vite alias for src
148
- // .vue SFCs are not vendor packages
149
- if (/\.vue(?:\?|$)/i.test(s))
150
- return false;
151
- // Exclude core and vue runtime which are handled by dedicated bridges
152
- if (/^@nativescript\/core(\b|\/)/i.test(s))
153
- return false;
154
- if (/^(?:vue|nativescript-vue)(?:\b|\/)/i.test(s))
86
+ let ACTIVE_STRATEGY;
87
+ function isSocketClientOpen(client) {
88
+ if (!client) {
155
89
  return false;
156
- // Treat any other bare package id as device-resolved (require) during HMR
157
- return true;
158
- }
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
90
  }
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;
91
+ const openState = typeof client.OPEN === 'number' ? client.OPEN : 1;
92
+ return client.readyState === openState;
93
+ }
94
+ function getHmrSocketRoleFromRequestUrl(requestUrl) {
187
95
  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
- };
96
+ const url = new URL(requestUrl || '/ns-hmr', 'http://localhost');
97
+ return url.searchParams.get('ns_hmr_role') || 'unknown';
196
98
  }
197
99
  catch {
198
- return null;
100
+ return 'unknown';
199
101
  }
200
102
  }
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}`;
103
+ function getHmrSocketRole(client) {
104
+ if (!client) {
105
+ return 'unknown';
217
106
  }
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()}`;
107
+ return typeof client.__nsHmrClientRole === 'string' && client.__nsHmrClientRole ? client.__nsHmrClientRole : 'unknown';
108
+ }
109
+ function shouldAllowLocalCoreSanitizerPaths(contextLabel) {
110
+ return /\bnode_modules\/@nativescript\/vite\/hmr\/(?:client|frameworks)\//.test(contextLabel);
238
111
  }
112
+ export function prepareAngularEntryForDevice(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
113
+ const rewrittenCode = rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp);
114
+ return rewriteAngularEntryRegisterOnly(rewrittenCode, resolveAngularCoreHmrImportSource(rewrittenCode, httpOrigin));
115
+ }
116
+ const processSfcCode = createProcessSfcCode(processCodeForDevice);
117
+ // Bare specifiers and special skip patterns (virtual, data:, etc.)
118
+ const VENDOR_PACKAGES = /^[A-Za-z@][^:\/\s]*$/;
119
+ const SKIP_PATTERNS = /^(?:data:|blob:|node:|virtual:|vite:|\0|\/@@?id|\/__vite|__vite|__x00__)/;
239
120
  const MODULE_IMPORT_ANALYSIS_PLUGINS = ['typescript', 'jsx', 'importMeta', 'topLevelAwait', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'decorators-legacy'];
240
121
  function collectTopLevelImportRecords(code) {
241
122
  if (!code || typeof code !== 'string' || !/\bimport\b/.test(code)) {
@@ -351,953 +232,47 @@ function buildNodeModuleProvenancePrelude(sourceId) {
351
232
  const viteDepsMatch = cleaned.match(/(?:^|\/)node_modules\/\.vite\/deps\/([^?#]+)/);
352
233
  if (viteDepsMatch?.[1]) {
353
234
  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) {
671
- // Proceed even if a vendor manifest isn't available; we'll still vendor-bind
672
- // likely NativeScript plugin-style specifiers (e.g., 'pinia', '@scope/pkg')
673
- // via require() so device can resolve them from the app bundle.
674
- const importRegex = /(^|\n)\s*import\s+([\s\S]*?)\s+from\s+["']([^"']+)["'];?/gm;
675
- const sideEffectRegex = /(^|\n)\s*import\s+["']([^"']+)["'];?/gm;
676
- // Collect non-vendor imports so we can hoist them above the injected vendor prelude.
677
- // This ensures any residual ESM imports (like SFCs) remain at the true top-level for parsers
678
- // that require imports to precede other statements.
679
- const preservedImports = [];
680
- const modules = new Map();
681
- const getModuleBinding = (canonical) => {
682
- let entry = modules.get(canonical);
683
- if (!entry) {
684
- entry = {
685
- default: new Set(),
686
- namespace: new Set(),
687
- named: [],
688
- sideEffectOnly: false,
689
- };
690
- modules.set(canonical, entry);
691
- }
692
- return entry;
693
- };
694
- const parseNamedImports = (clause, binding) => {
695
- const inner = clause.replace(/^\{/, '').replace(/\}$/, '');
696
- inner
697
- .split(',')
698
- .map((segment) => segment.trim())
699
- .filter(Boolean)
700
- .forEach((segment) => {
701
- const [imported, local] = segment.split(/\s+as\s+/i).map((s) => s.trim());
702
- const resolvedImported = imported;
703
- const resolvedLocal = local || imported;
704
- if (resolvedImported) {
705
- binding.named.push({
706
- imported: resolvedImported,
707
- local: resolvedLocal,
708
- });
709
- }
710
- });
711
- };
712
- // Handle "import ... from 'x'" forms
713
- code = code.replace(importRegex, (full, pfx, clause, rawSpec) => {
714
- // Capture original for potential preservation (strip the leading newline to avoid double spacing when hoisted)
715
- const original = full.replace(/^\n/, '');
716
- // Do not touch type-only imports other than hoisting
717
- if (full.trimStart().startsWith('import type')) {
718
- preservedImports.push(original);
719
- return pfx || '';
720
- }
721
- const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
722
- let canonical = resolveVendorFromCandidate(specifier);
723
- const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier);
724
- if (options?.preserveNonPluginVendorImports && !runtimePluginSpecifier) {
725
- preservedImports.push(original);
726
- return pfx || '';
727
- }
728
- // If not found in vendor manifest, treat well-known NativeScript plugin-style packages
729
- // as require() based modules so the device can resolve them from the app bundle or vendor.
730
- if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
731
- canonical = specifier;
732
- }
733
- // CRITICAL: never vendor-inject @nativescript/core here — preserve for the later core-bridge pass.
734
- if (canonical && /^@nativescript\/core(\b|\/)/i.test(canonical)) {
735
- preservedImports.push(original);
736
- return pfx || '';
737
- }
738
- if (!canonical) {
739
- preservedImports.push(original);
740
- return pfx || '';
741
- }
742
- const binding = getModuleBinding(canonical);
743
- const trimmed = String(clause).trim();
744
- if (!trimmed) {
745
- binding.sideEffectOnly = true;
746
- return pfx || ''; // erase the import line
747
- }
748
- // namespace: import * as ns from 'x'
749
- if (trimmed.startsWith('*')) {
750
- const m = trimmed.match(/\*\s+as\s+(\w+)/i);
751
- if (m?.[1])
752
- binding.namespace.add(m[1]);
753
- return pfx || '';
754
- }
755
- // named: import { a, b as c } from 'x'
756
- if (trimmed.startsWith('{')) {
757
- parseNamedImports(trimmed, binding);
758
- return pfx || '';
759
- }
760
- // default + named: import Default, { a as A } from 'x'
761
- if (trimmed.includes(',') && trimmed.includes('{')) {
762
- const [defaultPart, namedPart] = trimmed.split(/,(.+)/, 2);
763
- const def = defaultPart.trim();
764
- if (def)
765
- binding.default.add(def);
766
- if (namedPart)
767
- parseNamedImports(namedPart.trim(), binding);
768
- return pfx || '';
769
- }
770
- // default only
771
- binding.default.add(trimmed);
772
- return pfx || '';
773
- });
774
- // Handle side-effect only imports: import 'x'
775
- code = code.replace(sideEffectRegex, (full, _pfx, rawSpec) => {
776
- const original = full.replace(/^\n/, '');
777
- const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
778
- let canonical = resolveVendorFromCandidate(specifier);
779
- const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier);
780
- if (options?.preserveNonPluginVendorImports && !runtimePluginSpecifier) {
781
- preservedImports.push(original);
782
- return _pfx || '';
783
- }
784
- if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
785
- canonical = specifier;
786
- }
787
- if (canonical && /^@nativescript\/core(\b|\/)/i.test(canonical)) {
788
- preservedImports.push(original);
789
- return _pfx || '';
790
- }
791
- if (!canonical) {
792
- preservedImports.push(original);
793
- return _pfx || '';
794
- }
795
- const binding = getModuleBinding(canonical);
796
- binding.sideEffectOnly = true;
797
- return _pfx || '';
798
- });
799
- // If there are no vendor modules to bind, still hoist preserved imports if any were collected.
800
- if (!modules.size) {
801
- if (preservedImports.length) {
802
- const preserved = preservedImports.join('') + '\n';
803
- return preserved + code;
804
- }
805
- return code;
806
- }
807
- let injection = 'const __nsVendorRegistry = (globalThis.__nsVendorRegistry ||= new Map());\n';
808
- // Soft vendor fallback mode: when a plugin module is not available during HMR, provide a stub so the module can instantiate.
809
- // Toggle with globalThis.__NS_VENDOR_SOFT__ (default true)
810
- // Use JS-safe global access (no TS casts) to avoid syntax errors on device
811
- injection += "const __NS_VENDOR_SOFT__ = (typeof globalThis.__NS_VENDOR_SOFT__ !== 'undefined' ? !!globalThis.__NS_VENDOR_SOFT__ : true);\n";
812
- // Provide a require fallback that throws lazily so callers can soft-stub in the catch block.
813
- injection += "const __nsVendorRequire = (typeof globalThis.__nsRequire === 'function' ? globalThis.__nsRequire : (typeof globalThis.require === 'function' ? globalThis.require : (spec => { throw new Error('__nsVendorRequire unavailable'); })));\n";
814
- // One-time diagnostic if require is missing; avoid spewing on every module
815
- injection += "try { (globalThis.__NS_VENDOR_ONCE__ ||= { loggedRequireMissing: false }); if (!globalThis.__NS_VENDOR_ONCE__.loggedRequireMissing && typeof __nsVendorRequire !== 'function') { console.warn('[ns-hmr][vendor][require-missing] using soft stubs=', __NS_VENDOR_SOFT__); globalThis.__NS_VENDOR_ONCE__.loggedRequireMissing = true; } } catch {}\n";
816
- injection += "function __nsMissing(name){ try { const fn = function(){ try { console.warn('[ns-hmr][vendor][stub]', name); } catch {} }; return new Proxy(fn, { get: (_t, p) => __nsMissing(name + '.' + String(p)) }); } catch { return {}; } }\n";
817
- // Helper utils to simplify robust property/default selection without using optional chaining/nullish
818
- injection += "function __nsHasInstall(x){ try { return (typeof x === 'function') || (typeof x === 'object' && x && typeof x.install === 'function'); } catch { return false; } }\n";
819
- injection += "function __nsDefault(mod){ try { return (mod && mod['default'] !== undefined) ? mod['default'] : mod; } catch { return mod; } }\n";
820
- injection += "function __nsNestedDefault(mod){ try { return (mod && mod.default && (typeof mod.default.default === 'function' || (typeof mod.default.default === 'object' && mod.default.default && typeof mod.default.default.install === 'function'))) ? mod.default.default : undefined; } catch { return undefined; } }\n";
821
- injection += "function __nsPick(mod, name){ try { if (mod && mod['default'] && mod['default'][name] !== undefined) return mod['default'][name]; } catch {} try { if (mod && mod[name] !== undefined) return mod[name]; } catch {} try { if (mod && typeof mod['default'] === 'function' && mod['default'].name === name) return mod['default']; } catch {} return undefined; }\n";
822
- let index = 0;
823
- for (const [canonical, binding] of modules) {
824
- const cacheKey = JSON.stringify(canonical);
825
- const moduleVar = `__nsVendorModule_${index++}`;
826
- injection += `const ${moduleVar} = __nsVendorRegistry.has(${cacheKey}) ? __nsVendorRegistry.get(${cacheKey}) : (() => { try { const mod = __nsVendorRequire(${cacheKey}); __nsVendorRegistry.set(${cacheKey}, mod); return mod; } catch (e) { try { console.error('[ns-hmr][vendor][require-failed]', ${cacheKey}, (e && (e.message||e)) ); } catch {} try { if (__NS_VENDOR_SOFT__) { const stub = __nsMissing(${cacheKey}); __nsVendorRegistry.set(${cacheKey}, stub); return stub; } } catch {} throw e; } })();\n`;
827
- binding.namespace.forEach((alias) => {
828
- // For namespace imports, expose both the raw module and a default fallback for interop consumers
829
- injection += `const ${alias} = ${moduleVar};\n`;
830
- injection += `(${alias} && typeof ${alias} === 'object' && !('default' in ${alias})) && (${alias}.default = ${alias});\n`;
831
- });
832
- if (binding.named.length) {
833
- // Bind each named import robustly from either default or namespace using helper.
834
- for (const { imported, local } of binding.named) {
835
- const localName = local;
836
- const importedName = imported;
837
- injection += `const ${localName} = __nsPick(${moduleVar}, ${JSON.stringify(importedName)});\n`;
838
- }
839
- }
840
- if (binding.default.size) {
841
- // Create one stable default candidate per module and reuse for all default locals
842
- const defVar = `${moduleVar}__def`;
843
- injection += `const ${defVar} = __nsDefault(${moduleVar});\n`;
844
- binding.default.forEach((localName) => {
845
- injection += `const ${localName} = (__nsHasInstall(${defVar})
846
- ? ${defVar}
847
- : (__nsHasInstall(${moduleVar})
848
- ? ${moduleVar}
849
- : (function(){ const _n = __nsNestedDefault(${moduleVar}); return _n !== undefined ? _n : ${defVar}; })()));\n`;
850
- });
851
- }
852
- if (binding.sideEffectOnly && !binding.namespace.size && !binding.named.length && !binding.default.size) {
853
- injection += `void ${moduleVar};\n`;
854
- }
855
- }
856
- injection += '\n';
857
- // Hoist preserved non-vendor imports to the very top for maximum ESM compatibility
858
- const preserved = preservedImports.length ? preservedImports.join('') + '\n' : '';
859
- return preserved + injection + code;
860
- }
861
- // Guard any bare dynamic import(spec) occurring in assembled module code.
862
- // We cannot override native dynamic import globally; for SFC assembler outputs we inline
863
- // a tiny helper and rewrite "import(" to "__nsDynImport(" to prevent anomalous specs like '@'.
864
- function guardBareDynamicImports(code) {
865
- try {
866
- if (!code || typeof code !== 'string')
867
- return code;
868
- const NEEDLE = /(^|\n)\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*/;
869
- const hasImportCall = /\bimport\s*\(/.test(code);
870
- if (!hasImportCall)
871
- return code;
872
- const helper = "const __nsDynImport = (spec) => { try { if (!spec || spec === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {} try { return import(spec); } catch (e) { return Promise.reject(e); } };\n";
873
- // Avoid double injection
874
- const inject = code.includes('const __nsDynImport =') ? '' : helper;
875
- // Replace bare import( ... ) that are not part of 'import.meta' or type-only contexts
876
- // Heuristic: replace 'import(' occurrences; skip 'import.meta'
877
- const rewritten = code.replace(/\bimport\s*\(/g, '__nsDynImport(');
878
- if (rewritten === code && !inject)
879
- return code;
880
- return inject + rewritten;
881
- }
882
- catch {
883
- return code;
884
- }
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
- }
914
- function normalizeNativeScriptCoreSpecifier(spec) {
915
- let normalized = spec.replace(/@nativescript[_-]core/gi, '@nativescript/core').replace(/@nativescript\/core\/index\.js$/i, '@nativescript/core/index.js');
916
- if (normalized.startsWith('/node_modules/')) {
917
- const idx = normalized.toLowerCase().indexOf('@nativescript/core');
918
- if (idx !== -1) {
919
- normalized = normalized.slice(idx);
920
- }
921
- }
922
- if (normalized.toLowerCase().startsWith('@nativescript/core')) {
923
- normalized = normalized.replace(/\?[^"'`]*$/, '');
924
- }
925
- return normalized;
926
- }
927
- export function normalizeNodeModulesSpecifier(spec) {
928
- if (!spec) {
929
- return null;
930
- }
931
- let normalized = spec.replace(/\\/g, '/');
932
- const idx = normalized.lastIndexOf('/node_modules/');
933
- if (idx === -1) {
934
- return null;
935
- }
936
- let subPath = normalized.slice(idx + '/node_modules/'.length);
937
- if (!subPath) {
938
- return null;
939
- }
940
- subPath = subPath.replace(PAT.QUERY_PATTERN, '');
941
- if (!subPath) {
942
- return null;
943
- }
944
- // Skip Vite pre-bundled deps that we already map to vendor
945
- if (subPath.startsWith('.vite/')) {
946
- return null;
947
- }
948
- return subPath.startsWith('/') ? subPath.slice(1) : subPath;
949
- }
950
- export function resolveVendorFromCandidate(specifier) {
951
- if (!specifier) {
952
- return null;
953
- }
954
- const manifest = getVendorManifest();
955
- if (!manifest) {
956
- return null;
957
- }
958
- const cleaned = specifier.replace(PAT.QUERY_PATTERN, '');
959
- const direct = resolveVendorSpecifier(cleaned);
960
- if (direct) {
961
- return direct;
962
- }
963
- const flattenedId = extractVitePrebundleId(cleaned);
964
- if (flattenedId) {
965
- const flattenedMap = getFlattenedManifestMap(manifest);
966
- const flatMatch = flattenedMap.get(flattenedId);
967
- if (flatMatch) {
968
- return flatMatch;
969
- }
970
- for (const [flatKey, canonical] of flattenedMap.entries()) {
971
- if (flattenedId === flatKey) {
972
- return canonical;
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
- }
992
- }
993
- const guessedId = flattenedId.replace(/__/g, '.').replace(/_/g, '/');
994
- if (guessedId && guessedId !== flattenedId) {
995
- const guessedCanonical = resolveVendorSpecifier(guessedId);
996
- if (guessedCanonical) {
997
- return guessedCanonical;
998
- }
999
- const prefix = findVendorPrefix(guessedId, manifest);
1000
- if (prefix) {
1001
- return prefix;
1002
- }
1003
- }
1004
- }
1005
- const normalizedCore = normalizeNativeScriptCoreSpecifier(cleaned);
1006
- if (normalizedCore !== cleaned) {
1007
- const nsCanonical = resolveVendorSpecifier(normalizedCore);
1008
- if (nsCanonical) {
1009
- return nsCanonical;
1010
- }
1011
- }
1012
- const nodeModulesSpecifier = normalizeNodeModulesSpecifier(cleaned);
1013
- if (nodeModulesSpecifier) {
1014
- const canonical = resolveVendorSpecifier(nodeModulesSpecifier);
1015
- if (canonical) {
1016
- return canonical;
1017
- }
1018
- const prefix = findVendorPrefix(nodeModulesSpecifier, manifest);
1019
- if (prefix) {
1020
- return prefix;
1021
- }
1022
- }
1023
- const prefix = findVendorPrefix(cleaned, manifest);
1024
- if (prefix) {
1025
- return prefix;
1026
- }
1027
- return null;
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
- }
1060
- function findVendorPrefix(specifier, manifest) {
1061
- const { modules, aliases } = manifest;
1062
- const keys = Object.keys(modules || {});
1063
- for (const key of keys) {
1064
- if (specifier === key) {
1065
- return key;
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}`;
1122
- }
1123
- return null;
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
235
+ }
1215
236
  }
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;
237
+ if (!normalized) {
238
+ return '';
1228
239
  }
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;
240
+ let packageSpecifier = normalized;
241
+ let via = 'node_modules';
242
+ if (normalized.startsWith('.vite/deps/')) {
243
+ via = 'vite-deps';
244
+ packageSpecifier = viteDepsPathToBareSpecifier(normalized.slice('.vite/deps/'.length)) || normalized;
1263
245
  }
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 };
246
+ const rootPackage = resolveNodeModulesPackageBoundary(packageSpecifier, getProjectRootPath()).packageName;
247
+ if (!rootPackage) {
248
+ return '';
1271
249
  }
1272
- // Other platform-specific files still go via HTTP.
1273
- if (/\.(ios|android|visionos)\.(js|ts|mjs|mts)$/.test(nodeModulesSpec)) {
1274
- return { route: 'http' };
250
+ 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`;
251
+ }
252
+ // Guard any bare dynamic import(spec) occurring in assembled module code.
253
+ // We cannot override native dynamic import globally; for SFC assembler outputs we inline
254
+ // a tiny helper and rewrite "import(" to "__nsDynImport(" to prevent anomalous specs like '@'.
255
+ function guardBareDynamicImports(code) {
256
+ try {
257
+ if (!code || typeof code !== 'string')
258
+ return code;
259
+ const NEEDLE = /(^|\n)\s*(?:\/\/[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*/;
260
+ const hasImportCall = /\bimport\s*\(/.test(code);
261
+ if (!hasImportCall)
262
+ return code;
263
+ const helper = "const __nsDynImport = (spec) => { try { if (!spec || spec === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {} try { return import(spec); } catch (e) { return Promise.reject(e); } };\n";
264
+ // Avoid double injection
265
+ const inject = code.includes('const __nsDynImport =') ? '' : helper;
266
+ // Replace bare import( ... ) that are not part of 'import.meta' or type-only contexts
267
+ // Heuristic: replace 'import(' occurrences; skip 'import.meta'
268
+ const rewritten = code.replace(/\bimport\s*\(/g, '__nsDynImport(');
269
+ if (rewritten === code && !inject)
270
+ return code;
271
+ return inject + rewritten;
1275
272
  }
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
- }
273
+ catch {
274
+ return code;
1298
275
  }
1299
- // Default: HTTP — safe for all module types and preserves all named exports
1300
- return { route: 'http' };
1301
276
  }
1302
277
  function stripCoreGlobalsImports(code) {
1303
278
  const pattern = /^\s*(?:import\s+(?:[^'"\n]*from\s+)?|export\s+\*\s+from\s+)["'][^"']*(?:@nativescript(?:[/_-])core(?:[\/_-])globals|@nativescript_core_globals)[^"']*["'];?\s*$/gm;
@@ -1326,18 +301,14 @@ function ensureVariableDynamicImportHelper(code) {
1326
301
  `};\n`;
1327
302
  return `${helper}${code}`;
1328
303
  }
1329
- // Final safety net for plain dynamic import(expressions) that might slip through
1330
- // Vite's helper transformation. We rewrite occurrences of `import(` to `__ns_import(`
1331
- // and inject a small wrapper that maps the anomalous request '@' to a harmless stub.
1332
304
  function ensureGuardPlainDynamicImports(code, origin) {
1333
305
  try {
1334
306
  if (!code || !/\bimport\s*\(/.test(code))
1335
307
  return code;
1336
- const w = `const __ns_import = (s) => { try { if (s === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {} return import(s); }\n`;
1337
- // Replace only when `import(` is not part of an identifier or property (no preceding "." or word char)
308
+ const wrapper = `const __ns_import = (s) => { try { if (s === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {} return import(s); }\n`;
1338
309
  const replaced = code.replace(/(^|[^\.\w$])import\s*\(/g, (_m, p1) => `${p1}__ns_import(`);
1339
310
  if (replaced !== code) {
1340
- return w + replaced;
311
+ return wrapper + replaced;
1341
312
  }
1342
313
  return code;
1343
314
  }
@@ -1345,26 +316,49 @@ function ensureGuardPlainDynamicImports(code, origin) {
1345
316
  return code;
1346
317
  }
1347
318
  }
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
- */
319
+ function ensureDynamicHmrImportHelper(code) {
320
+ try {
321
+ if (!code.includes('__nsDynamicHmrImport('))
322
+ return code;
323
+ if (code.includes('const __nsDynamicHmrImport ='))
324
+ return code;
325
+ const helper = 'const __nsDynamicHmrImport = (spec) => {\n' +
326
+ " const __nsm = '/ns' + '/m';\n" +
327
+ " const __nsBootPrefix = typeof import.meta !== 'undefined' && import.meta && typeof import.meta.url === 'string' && import.meta.url.includes('/__ns_boot__/b1/') ? '/__ns_boot__/b1' : '';\n" +
328
+ " const __nsImporterTagMatch = typeof import.meta !== 'undefined' && import.meta && typeof import.meta.url === 'string' ? import.meta.url.match(/\\/__ns_hmr__\\/([^/]+)\\//) : null;\n" +
329
+ " const __nsImporterTag = __nsImporterTagMatch && __nsImporterTagMatch[1] ? decodeURIComponent(__nsImporterTagMatch[1]) : '';\n" +
330
+ " try { if (!spec || spec === '@') { return import(new URL(__nsm + '/__invalid_at__.mjs', import.meta.url).href); } } catch {}\n" +
331
+ ' try {\n' +
332
+ " if (typeof spec === 'string' && spec.startsWith(__nsm + '/')) {\n" +
333
+ ' const g = globalThis;\n' +
334
+ " const graphVersion = typeof g.__NS_HMR_GRAPH_VERSION__ === 'number' ? g.__NS_HMR_GRAPH_VERSION__ : 0;\n" +
335
+ " const nonce = typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0;\n" +
336
+ " const __nsActiveBootPrefix = graphVersion || nonce ? '' : __nsBootPrefix;\n" +
337
+ " if (spec.includes('/__ns_hmr__/')) {\n" +
338
+ " const __preservedSpec = !nonce && __nsBootPrefix && spec.startsWith(__nsm + '/__ns_hmr__/') && !spec.includes('/node_modules/') ? __nsm + __nsBootPrefix + spec.slice(__nsm.length) : spec;\n" +
339
+ ' return import(new URL(__preservedSpec, import.meta.url).href);\n' +
340
+ ' }\n' +
341
+ " if (spec.startsWith(__nsm + '/node_modules/')) { return import(new URL(spec, import.meta.url).href); }\n" +
342
+ " const tag = nonce ? `n${nonce}` : (graphVersion ? `v${graphVersion}` : (__nsImporterTag || 'live'));\n" +
343
+ " const nextPath = __nsm + __nsActiveBootPrefix + '/__ns_hmr__/' + encodeURIComponent(tag) + spec.slice(__nsm.length);\n" +
344
+ " const origin = typeof g.__NS_HTTP_ORIGIN__ === 'string' && /^https?:\\/\\//.test(g.__NS_HTTP_ORIGIN__) ? g.__NS_HTTP_ORIGIN__ : '';\n" +
345
+ ' return import(origin ? origin + nextPath : new URL(nextPath, import.meta.url).href);\n' +
346
+ ' }\n' +
347
+ ' } catch {}\n' +
348
+ ' return import(spec);\n' +
349
+ '};\n';
350
+ return helper + code;
351
+ }
352
+ catch {
353
+ return code;
354
+ }
355
+ }
1361
356
  async function expandStarExports(code, server, projectRoot, verbose) {
1362
357
  const STAR_RE = /^[ \t]*(export\s+\*\s+from\s+["'])([^"']+)(["'];?)[ \t]*$/gm;
1363
358
  let match;
1364
359
  const replacements = [];
1365
360
  while ((match = STAR_RE.exec(code)) !== null) {
1366
361
  const url = match[2];
1367
- // Only expand node_modules star exports served over HTTP
1368
362
  if (!url.includes('/node_modules/'))
1369
363
  continue;
1370
364
  replacements.push({ full: match[0], url, prefix: match[1], suffix: match[3] });
@@ -1373,88 +367,33 @@ async function expandStarExports(code, server, projectRoot, verbose) {
1373
367
  return code;
1374
368
  for (const rep of replacements) {
1375
369
  try {
1376
- // Strip HTTP origin to get a Vite-resolvable path
1377
370
  let vitePath = rep.url.replace(/^https?:\/\/[^/]+/, '');
1378
- // Strip /ns/m/ prefix to get the node_modules path
1379
371
  vitePath = vitePath.replace(/^\/ns\/m\//, '/');
1380
- // Strip boot-path prefix used during initial HTTP boot progress tracking.
1381
372
  vitePath = vitePath.replace(/^\/__ns_boot__\/[^/]+/, '');
1382
- // Strip HMR cache-busting path segments
1383
373
  vitePath = vitePath.replace(/\/__ns_hmr__\/[^/]+/, '');
1384
- const r = await server.transformRequest(vitePath);
1385
- if (!r?.code)
374
+ const result = await server.transformRequest(vitePath);
375
+ if (!result?.code)
1386
376
  continue;
1387
- const names = extractExportedNames(r.code);
377
+ const names = extractExportedNames(result.code);
1388
378
  if (!names.length)
1389
379
  continue;
1390
- // Replace `export * from "url"` with explicit named re-exports
1391
380
  const explicit = `export { ${names.join(', ')} } from ${JSON.stringify(rep.url)};`;
1392
381
  code = code.replace(rep.full, explicit);
1393
382
  if (verbose) {
1394
- console.log(`[ns/m] expanded export* ${names.length} names from ${vitePath}`);
383
+ console.log(`[ns/m] expanded export* -> ${names.length} names from ${vitePath}`);
1395
384
  }
1396
385
  }
1397
386
  catch { }
1398
387
  }
1399
388
  return code;
1400
389
  }
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
390
  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);
391
+ return extractDirectExportedNames(code);
1450
392
  }
1451
- // Heal accidental "import ... = expr" assignments produced by upstream transforms.
1452
- // These are invalid JS; convert to equivalent const assignments.
1453
393
  function repairImportEqualsAssignments(code) {
1454
394
  try {
1455
395
  if (!code || typeof code !== 'string')
1456
396
  return code;
1457
- // import { a, b as c } = expr; -> const { a, b: c } = expr;
1458
397
  code = code.replace(/(^|\n)\s*import\s*\{([^}]+)\}\s*=\s*([^;]+);?/g, (_m, p1, specList, rhs) => {
1459
398
  const cleaned = String(specList)
1460
399
  .split(',')
@@ -1464,61 +403,28 @@ function repairImportEqualsAssignments(code) {
1464
403
  .join(', ');
1465
404
  return `${p1}const { ${cleaned} } = ${rhs};`;
1466
405
  });
1467
- // import * as ns = expr; -> const ns = (expr);
1468
- code = code.replace(/(^|\n)\s*import\s*\*\s*as\s*([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, ns, rhs) => {
1469
- return `${p1}const ${ns} = (${rhs});`;
1470
- });
1471
- // import name = expr; -> const name = expr;
1472
- code = code.replace(/(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, id, rhs) => {
1473
- return `${p1}const ${id} = ${rhs};`;
1474
- });
406
+ code = code.replace(/(^|\n)\s*import\s*\*\s*as\s*([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, ns, rhs) => `${p1}const ${ns} = (${rhs});`);
407
+ code = code.replace(/(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, id, rhs) => `${p1}const ${id} = ${rhs};`);
1475
408
  }
1476
409
  catch { }
1477
410
  return code;
1478
411
  }
1479
- // Ensure imports of the NativeScript-Vue runtime bridge '/ns/rt' are versioned to
1480
- // bust the device HTTP loader cache whenever the HMR graph version increments.
1481
412
  function ensureVersionedRtImports(code, origin, ver) {
1482
413
  if (!code || !origin || !Number.isFinite(ver))
1483
414
  return code;
1484
- // Static imports: import { ... } from ".../ns/rt" (plus optional version)
1485
415
  code = code.replace(/(from\s+["'])(?:https?:\/\/[^"']+)?\/(?:\ns|ns)\/rt(?:\/[\d]+)?(["'])/g, (_m, p1, p3) => `${p1}/ns/rt/${ver}${p3}`);
1486
- // Dynamic imports: import(".../ns/rt") (plus optional version)
1487
416
  code = code.replace(/(import\(\s*["'])(?:https?:\/\/[^"']+)?\/(?:\@ns|ns)\/rt(?:\/[\d]+)?(["']\s*\))/g, (_m, p1, p3) => `${p1}/ns/rt/${ver}${p3}`);
1488
417
  return code;
1489
418
  }
1490
- // Ensure imports of @nativescript/core resolve via the unified /ns/core bridge to keep a single realm
1491
- function ensureVersionedCoreImports(code, origin, ver) {
1492
- try {
1493
- // Static imports already handled in rewriteImports; just ensure absolute origin prefix and version
1494
- code = code.replace(/(["'])\/ns\/core(\?p=[^"']+)?\1/g, (_m, q, qp) => `${q}/ns/core/${ver}${qp || ''}${q}`);
1495
- // Dynamic imports already handled in rewriteImports; ensure origin and version
1496
- code = code.replace(/import\(\s*(["'])\/ns\/core(\?p=[^"']+)?\1\s*\)/g, (_m, q, qp) => `import(${q}/ns/core/${ver}${qp || ''}${q})`);
1497
- }
1498
- catch { }
1499
- return code;
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
- }
1506
- // Hardened removal of Vite's virtual dynamic-import-helper. Some variants (side-effect only
1507
- // or minified forms) slipped past earlier regexes causing runtime attempts to resolve
1508
- // /@id/__x00__vite/dynamic-import-helper.js which does not exist in the device mirror.
1509
- // We aggressively strip any reference and inline a helper if necessary.
1510
419
  function stripViteDynamicImportVirtual(code) {
1511
420
  if (!/\/@id\/__x00__vite\/dynamic-import-helper/.test(code)) {
1512
421
  return code;
1513
422
  }
1514
423
  const original = code;
1515
- // Remove any import lines referencing the virtual helper (with or without bindings)
1516
424
  code = code.replace(/^[\t ]*import[^\n]*\/@id\/__x00__vite\/dynamic-import-helper[^\n]*$/gm, '');
1517
- // If any raw spec strings remain (e.g. concatenated), neutralize them
1518
425
  if (/\/@id\/__x00__vite\/dynamic-import-helper/.test(code)) {
1519
426
  code = code.replace(/\/@id\/__x00__vite\/dynamic-import-helper[^"'`)]*/g, '/__NS_UNUSED_DYNAMIC_IMPORT_HELPER__');
1520
427
  }
1521
- // Ensure helper present
1522
428
  if (!/__variableDynamicImportRuntimeHelper/.test(code)) {
1523
429
  const inline = `const __variableDynamicImportRuntimeHelper = (map, request, importMode) => {\n try { if (request === '@') { return import('/ns/m/__invalid_at__.mjs'); } } catch {}\n const loader = map && (map[request] || map[request?.replace(/\\\\/g, '/')]);\n if (!loader) { const e = new Error('Cannot dynamically import: ' + request); /*@ts-ignore*/ e.code = 'ERR_MODULE_NOT_FOUND'; return Promise.reject(e); }\n try { return loader(importMode); } catch (e) { return Promise.reject(e); }\n};\n`;
1524
430
  code = inline + code;
@@ -1528,17 +434,7 @@ function stripViteDynamicImportVirtual(code) {
1528
434
  }
1529
435
  return code;
1530
436
  }
1531
- // Small snippet injected into device-delivered modules to capture any require('http(s)://') calls
1532
437
  const REQUIRE_GUARD_SNIPPET = `// [guard] install require('http(s)://') detector\n(()=>{try{var g=globalThis;if(g.__NS_REQUIRE_GUARD_INSTALLED__){}else{var mk=function(o,l){return function(){try{var s=arguments[0];if(typeof s==='string'&&/^(?:https?:)\\/\\//.test(s)){var e=new Error('[ns-hmr][require-guard] require of URL: '+s+' via '+l);try{console.error(e.message+'\\n'+(e.stack||''));}catch(e2){}try{g.__NS_REQUIRE_GUARD_LAST__={spec:s,stack:e.stack,label:l,ts:Date.now()};}catch(e3){}}}catch(e1){}return o.apply(this, arguments);};};if(typeof g.require==='function'&&!g.require.__NS_REQ_GUARDED__){var o1=g.require;g.require=mk(o1,'require');g.require.__NS_REQ_GUARDED__=true;}if(typeof g.__nsRequire==='function'&&!g.__nsRequire.__NS_REQ_GUARDED__){var o2=g.__nsRequire;g.__nsRequire=mk(o2,'__nsRequire');g.__nsRequire.__NS_REQ_GUARDED__=true;}g.__NS_REQUIRE_GUARD_INSTALLED__=true;}}catch(e){}})();\n`;
1533
- // ============================================================================
1534
- // HELPER FUNCTIONS
1535
- // ============================================================================
1536
- // Origin invariant: we own both client and server. All URLs must use an explicit
1537
- // http(s)://host:port origin with no trailing slash. Build it deterministically
1538
- // where needed; do not post-sanitize.
1539
- /**
1540
- * Check if an import spec should be remapped to dep-*.mjs
1541
- */
1542
438
  function shouldRemapImport(spec) {
1543
439
  if (!spec || typeof spec !== 'string')
1544
440
  return false;
@@ -1559,12 +455,9 @@ function shouldRemapImport(spec) {
1559
455
  }
1560
456
  return true;
1561
457
  }
1562
- // (legacy wrapSfcWithStableDefault removed; full SFCs now delegate to /ns/asm)
1563
458
  function removeNamedImports(code, names) {
1564
459
  const regex = /^(\s*import\s*\{)([^}]*)(\}\s*from\s*['"][^'"]+['"];?)/gm;
1565
460
  return code.replace(regex, (_m, p1, specList, p3) => {
1566
- // Only strip for known globalized framework sources (Vue/Nativescript-Vue).
1567
- // Keep imports from all other packages (Pinia, third-party libs, app modules) intact.
1568
461
  const srcMatch = /from\s*['"]\s*([^'"\s]+)\s*['"]/i.exec(_m);
1569
462
  const src = (srcMatch?.[1] || '').toLowerCase();
1570
463
  const isVueSource = /^(?:vue|nativescript-vue)(?:\b|\/)/i.test(src);
@@ -1663,7 +556,7 @@ function normalizeImportPath(spec, importerDir) {
1663
556
  else if (spec.startsWith('./') || spec.startsWith('../')) {
1664
557
  key = path.posix.normalize(path.posix.join(importerDir, spec));
1665
558
  if (!key.startsWith('/')) {
1666
- key = '/' + key;
559
+ key = `/${key}`;
1667
560
  }
1668
561
  }
1669
562
  else {
@@ -1730,6 +623,70 @@ function findDependencyFileName(depFileMap, key) {
1730
623
  }
1731
624
  return undefined;
1732
625
  }
626
+ function isRuntimePluginRootEntrySpecifier(specifier, projectRoot) {
627
+ if (!specifier) {
628
+ return false;
629
+ }
630
+ const cleaned = specifier.replace(PAT.QUERY_PATTERN, '');
631
+ const normalized = normalizeNodeModulesSpecifier(cleaned) || cleaned.replace(/^\/+/, '');
632
+ if (!normalized) {
633
+ return false;
634
+ }
635
+ const { packageName, subpath } = resolveNodeModulesPackageBoundary(normalized, projectRoot);
636
+ if (!packageName || !isLikelyNativeScriptRuntimePluginSpecifier(packageName, projectRoot)) {
637
+ return false;
638
+ }
639
+ if (!subpath) {
640
+ return true;
641
+ }
642
+ if (subpath.includes('/')) {
643
+ return false;
644
+ }
645
+ const pkgBaseName = packageName.split('/').pop() || '';
646
+ const withoutExt = /(?:\.(?:ios|android|visionos))?\.(?:ts|tsx|js|jsx|mjs|mts|cts)$/i.test(subpath) ? subpath.replace(/\.[^.]+$/, '') : subpath;
647
+ const withoutPlatform = withoutExt.replace(/\.(ios|android|visionos)$/i, '');
648
+ return withoutPlatform === 'index' || withoutPlatform === pkgBaseName;
649
+ }
650
+ function collectMixedRuntimePluginHttpRootPackages(code, projectRoot) {
651
+ const nonRootSubpathPackages = new Set();
652
+ const rootEntryPackages = new Set();
653
+ const visitSpecifier = (rawSpecifier) => {
654
+ if (!rawSpecifier) {
655
+ return;
656
+ }
657
+ const specifier = normalizeNativeScriptCoreSpecifier(rawSpecifier).replace(PAT.QUERY_PATTERN, '');
658
+ if (!specifier) {
659
+ return;
660
+ }
661
+ if (/^https?:\/\//.test(specifier) || specifier.startsWith('/ns/')) {
662
+ return;
663
+ }
664
+ if (/^(?:\.|\/)/.test(specifier) && !specifier.includes('/node_modules/')) {
665
+ return;
666
+ }
667
+ const normalized = normalizeNodeModulesSpecifier(specifier) || specifier.replace(/^\/+/, '');
668
+ if (!normalized) {
669
+ return;
670
+ }
671
+ const { packageName } = resolveNodeModulesPackageBoundary(normalized, projectRoot);
672
+ if (!packageName || !isLikelyNativeScriptRuntimePluginSpecifier(packageName, projectRoot)) {
673
+ return;
674
+ }
675
+ if (isRuntimePluginRootEntrySpecifier(normalized, projectRoot)) {
676
+ rootEntryPackages.add(packageName);
677
+ return;
678
+ }
679
+ nonRootSubpathPackages.add(packageName);
680
+ };
681
+ for (const pattern of [PAT.IMPORT_PATTERN_1, PAT.IMPORT_PATTERN_2, PAT.IMPORT_PATTERN_3, PAT.IMPORT_PATTERN_SIDE_EFFECT]) {
682
+ pattern.lastIndex = 0;
683
+ let match;
684
+ while ((match = pattern.exec(code)) !== null) {
685
+ visitSpecifier(match[2]);
686
+ }
687
+ }
688
+ return new Set(Array.from(nonRootSubpathPackages).filter((packageName) => rootEntryPackages.has(packageName)));
689
+ }
1733
690
  function collectImportDependencies(code, importerPath) {
1734
691
  const importerDir = path.posix.dirname(importerPath);
1735
692
  const deps = new Set();
@@ -1928,6 +885,59 @@ function toAppModuleBaseId(importPath, projectRoot) {
1928
885
  const base = projectRelative.replace(/\.mjs$/i, '');
1929
886
  return `/${base}`;
1930
887
  }
888
+ function toNodeModulesHttpModuleId(importPath) {
889
+ const nodeModulesSpecifier = normalizeNodeModulesSpecifier(importPath);
890
+ if (!nodeModulesSpecifier) {
891
+ return null;
892
+ }
893
+ return `/ns/m/node_modules/${nodeModulesSpecifier}`;
894
+ }
895
+ export function rewriteNsMImportPathForHmr(p, ver, bootTaggedRequest) {
896
+ const toHmrServeTag = (value) => {
897
+ const raw = String(value ?? '').trim();
898
+ if (!raw) {
899
+ return 'v0';
900
+ }
901
+ if (raw === 'live' || /^n\d+$/i.test(raw) || /^v[^/]+$/i.test(raw)) {
902
+ return raw;
903
+ }
904
+ if (/^\d+$/.test(raw)) {
905
+ return `v${raw}`;
906
+ }
907
+ return raw;
908
+ };
909
+ if (!p || !p.startsWith('/ns/m/')) {
910
+ return p;
911
+ }
912
+ const canonicalNodeModulesPath = p.replace(/^\/ns\/m\/__ns_boot__\/b1\/__ns_hmr__\/[^/]+\/node_modules\//, '/ns/m/node_modules/').replace(/^\/ns\/m\/__ns_hmr__\/[^/]+\/node_modules\//, '/ns/m/node_modules/');
913
+ if (canonicalNodeModulesPath.startsWith('/ns/m/node_modules/')) {
914
+ return canonicalNodeModulesPath;
915
+ }
916
+ if (canonicalNodeModulesPath.startsWith('/ns/m/__ns_boot__/')) {
917
+ return canonicalNodeModulesPath;
918
+ }
919
+ if (canonicalNodeModulesPath.startsWith('/ns/m/__ns_hmr__/')) {
920
+ return bootTaggedRequest ? `/ns/m/__ns_boot__/b1${canonicalNodeModulesPath.slice('/ns/m'.length)}` : canonicalNodeModulesPath;
921
+ }
922
+ const tag = toHmrServeTag(ver);
923
+ const hmrPrefix = `/ns/m/__ns_hmr__/${tag}`;
924
+ const bootHmrPrefix = `/ns/m/__ns_boot__/b1/__ns_hmr__/${tag}`;
925
+ return (bootTaggedRequest ? bootHmrPrefix : hmrPrefix) + canonicalNodeModulesPath.slice('/ns/m'.length);
926
+ }
927
+ function getNumericServeVersionTag(tag, fallback) {
928
+ const raw = String(tag || '').trim();
929
+ if (!raw) {
930
+ return fallback;
931
+ }
932
+ const versionMatch = raw.match(/^v(\d+)$/);
933
+ if (versionMatch?.[1]) {
934
+ return Number(versionMatch[1]);
935
+ }
936
+ if (/^\d+$/.test(raw)) {
937
+ return Number(raw);
938
+ }
939
+ return fallback;
940
+ }
1931
941
  function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
1932
942
  if (!spec || typeof spec !== 'string') {
1933
943
  return null;
@@ -2109,8 +1119,13 @@ export function wrapCommonJsModuleForDevice(code) {
2109
1119
  /**
2110
1120
  * Process code for device: inject globals, remove framework imports
2111
1121
  */
2112
- function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = false, isNodeModule = false, sourceId) {
1122
+ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = false, isNodeModule = false, sourceId, options) {
2113
1123
  let result = code;
1124
+ const resolvedSpecifierOverrides = options?.resolvedSpecifierOverrides || getProcessCodeResolvedSpecifierOverrides(sourceId, getProjectRootPath());
1125
+ const bindingOptions = {
1126
+ preserveNonPluginVendorImports: preserveVendorImports,
1127
+ resolvedSpecifierOverrides,
1128
+ };
2114
1129
  // Ensure Angular partial declarations are linked before any sanitizers run so runtime never hits the JIT path.
2115
1130
  result = linkAngularPartialsIfNeeded(result);
2116
1131
  // Post-linker: deduplicate/resolve imports the Angular linker injected with bare specifiers
@@ -2250,7 +1265,7 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
2250
1265
  // __nsVendorRequire + __nsPick rewrite. Vendor imports stay as bare
2251
1266
  // specifiers so the device-side import map resolves them via V8's native
2252
1267
  // module system, which correctly handles export * re-exports.
2253
- result = preserveVendorImports ? ensureNativeScriptModuleBindings(result, { preserveNonPluginVendorImports: true }) : ensureNativeScriptModuleBindings(result);
1268
+ result = ensureNativeScriptModuleBindings(result, bindingOptions);
2254
1269
  // Repair any accidental "import ... = expr" assignments that may have slipped in.
2255
1270
  try {
2256
1271
  result = repairImportEqualsAssignments(result);
@@ -2346,7 +1361,7 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
2346
1361
  }
2347
1362
  // Ensure vendor bindings also apply after potential wrapper injections above
2348
1363
  // (idempotent: second pass will be a no-op if imports already consumed).
2349
- result = preserveVendorImports ? ensureNativeScriptModuleBindings(result, { preserveNonPluginVendorImports: true }) : ensureNativeScriptModuleBindings(result);
1364
+ result = ensureNativeScriptModuleBindings(result, bindingOptions);
2350
1365
  try {
2351
1366
  result = repairImportEqualsAssignments(result);
2352
1367
  }
@@ -2387,17 +1402,17 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
2387
1402
  result = normalizeStrayCoreStringLiterals(result);
2388
1403
  }
2389
1404
  catch { }
1405
+ try {
1406
+ result = fixDanglingCoreFrom(result);
1407
+ }
1408
+ catch { }
1409
+ try {
1410
+ result = normalizeAnyCoreSpecToBridge(result);
1411
+ }
1412
+ catch { }
2390
1413
  result = ensureVariableDynamicImportHelper(result);
2391
1414
  // Normalize any lingering @nativescript/core imports to the /ns/core bridge (non-destructive best-effort)
2392
1415
  try {
2393
- result = result.replace(/from\s+["']@nativescript\/core([^"'\n]*)["']/g, (_m, sub) => {
2394
- const qp = (sub || '').trim().replace(/^\//, '');
2395
- return `from "/ns/core${qp ? `?p=${qp}` : ''}"`;
2396
- });
2397
- result = result.replace(/import\(\s*["']@nativescript\/core([^"'\n]*)["']\s*\)/g, (_m, sub) => {
2398
- const qp = (sub || '').trim().replace(/^\//, '');
2399
- return `import("/ns/core${qp ? `?p=${qp}` : ''}")`;
2400
- });
2401
1416
  // Rewrite named imports from the /ns/core bridge into default import + destructuring.
2402
1417
  // This makes `import { Frame } from '@nativescript/core'` work even if the bridge provides only a default export.
2403
1418
  {
@@ -2635,6 +1650,9 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
2635
1650
  if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
2636
1651
  continue;
2637
1652
  }
1653
+ if (shouldAllowLocalCoreSanitizerPaths(contextLabel)) {
1654
+ continue;
1655
+ }
2638
1656
  offenders.push(`${i + 1}: ${ln.substring(0, 200)} [local-core-path]`);
2639
1657
  }
2640
1658
  if (offenders.length >= 10)
@@ -2799,6 +1817,7 @@ function dedupeRtNamedImportsAgainstDestructures(code) {
2799
1817
  export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
2800
1818
  let result = code;
2801
1819
  const httpOriginSafe = httpOrigin;
1820
+ const mixedRuntimePluginHttpRootPackages = collectMixedRuntimePluginHttpRootPackages(result, projectRoot);
2802
1821
  const isDynamicImportPrefix = (prefix) => /import\(\s*["']?$/.test(prefix.trimStart());
2803
1822
  const importerDir = path.posix.dirname(importerPath);
2804
1823
  // Determine importer output relative path (project-relative .mjs) to compute relative imports consistently
@@ -2969,6 +1988,25 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
2969
1988
  return `${prefix}${spec}${suffix}`;
2970
1989
  }
2971
1990
  const nodeModulesSpecifier = normalizeNodeModulesSpecifier(spec);
1991
+ const normalizedRuntimePluginSpec = nodeModulesSpecifier || spec.replace(PAT.QUERY_PATTERN, '').replace(/^\/+/, '');
1992
+ if (normalizedRuntimePluginSpec && mixedRuntimePluginHttpRootPackages.size > 0) {
1993
+ const { packageName } = resolveNodeModulesPackageBoundary(normalizedRuntimePluginSpec, projectRoot);
1994
+ if (packageName && mixedRuntimePluginHttpRootPackages.has(packageName)) {
1995
+ const httpNodeModulesSpecifier = nodeModulesSpecifier || normalizedRuntimePluginSpec;
1996
+ const httpSpec = `/ns/m/node_modules/${httpNodeModulesSpecifier}`;
1997
+ if (httpOriginSafe) {
1998
+ return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
1999
+ }
2000
+ return `${prefix}${httpSpec}${suffix}`;
2001
+ }
2002
+ }
2003
+ if (shouldPreserveBareRuntimePluginSubpathImport(spec, projectRoot)) {
2004
+ const httpSpec = `/ns/m/node_modules/${spec.replace(PAT.QUERY_PATTERN, '').replace(/^\/+/, '')}`;
2005
+ if (httpOriginSafe) {
2006
+ return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
2007
+ }
2008
+ return `${prefix}${httpSpec}${suffix}`;
2009
+ }
2972
2010
  const candidateNativeScriptSpec = nodeModulesSpecifier ?? spec;
2973
2011
  // ── Node modules routing ──────────────────────────────────────
2974
2012
  // Uses the package's own package.json exports field to determine
@@ -3017,7 +2055,7 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
3017
2055
  return `${prefix}${out}${suffix}`;
3018
2056
  }
3019
2057
  // Case B: plain .vue module → rewrite to SFC endpoint or local artifact
3020
- const vueKey = resolveVueKey(spec.replace(PAT.QUERY_PATTERN, ''));
2058
+ const vueKey = resolveVueKey(spec.replace(PAT.QUERY_PATTERN, '')) || '';
3021
2059
  if (vueKey) {
3022
2060
  if (true) {
3023
2061
  const absVue = vueKey.startsWith('/') ? vueKey : '/' + vueKey;
@@ -3047,6 +2085,17 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
3047
2085
  // Rewrite relative application imports to HTTP for served modules
3048
2086
  if (spec.startsWith('./') || spec.startsWith('../')) {
3049
2087
  const absMaybe = normalizeImportPath(spec, importerDir);
2088
+ const nodeModulesHttpSpec = absMaybe ? toNodeModulesHttpModuleId(absMaybe) : null;
2089
+ if (nodeModulesHttpSpec) {
2090
+ if (isDynamicImportPrefix(prefix)) {
2091
+ if (verbose)
2092
+ console.log(`[rewrite][http] dynamic relative node_modules import → ${nodeModulesHttpSpec} (from ${spec})`);
2093
+ return `__nsDynamicHmrImport(${JSON.stringify(nodeModulesHttpSpec)})`;
2094
+ }
2095
+ if (verbose)
2096
+ console.log(`[rewrite][http] relative node_modules import → ${nodeModulesHttpSpec} (from ${spec})`);
2097
+ return `${prefix}${nodeModulesHttpSpec}${suffix}`;
2098
+ }
3050
2099
  const baseId = absMaybe ? toAppModuleBaseId(absMaybe, projectRoot) : null; // e.g. /src/foo.mjs
3051
2100
  if (baseId) {
3052
2101
  const httpSpec = `/ns/m${baseId}`;
@@ -3201,6 +2250,13 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
3201
2250
  console.log(`[rewrite][http] internal ns import (dynamic) → ${spec} via import.meta.url`);
3202
2251
  return expr;
3203
2252
  }
2253
+ const nodeModulesHttpSpec = toNodeModulesHttpModuleId(spec);
2254
+ if (nodeModulesHttpSpec) {
2255
+ const expr = `import(new URL('${nodeModulesHttpSpec}', import.meta.url).href)`;
2256
+ if (verbose)
2257
+ console.log(`[rewrite][http] absolute dynamic node_modules import → ${nodeModulesHttpSpec} via import.meta.url (from ${spec})`);
2258
+ return expr;
2259
+ }
3204
2260
  const baseId = toAppModuleBaseId(spec, projectRoot);
3205
2261
  if (!baseId)
3206
2262
  return match;
@@ -3252,7 +2308,8 @@ function createHmrWebSocketPlugin(opts) {
3252
2308
  // Compute a dependency-closed, topologically sorted list of modules for a given set of changed ids.
3253
2309
  // Only include application modules we can serve (e.g., under /src and known .vue/.ts/.js entries in the graph).
3254
2310
  function computeTxnOrderForChanged(changedIds) {
3255
- const includeExt = (id) => ACTIVE_STRATEGY.matchesFile(id) || /\.(ts|js|mjs|tsx|jsx)$/i.test(id);
2311
+ const includeAppModule = (id) => matchesRuntimeGraphModuleId(id, APP_VIRTUAL_WITH_SLASH, /\.(ts|js|mjs|tsx|jsx)$/i);
2312
+ const includeExt = (id) => ACTIVE_STRATEGY.matchesFile(id) || includeAppModule(id);
3256
2313
  const isApp = (id) => id.startsWith(APP_VIRTUAL_WITH_SLASH);
3257
2314
  const roots = changedIds.map(normalizeGraphId).filter((id) => graph.has(id) && (isApp(id) || ACTIVE_STRATEGY.matchesFile(id)) && includeExt(id));
3258
2315
  const toVisit = new Set();
@@ -3391,7 +2448,7 @@ function createHmrWebSocketPlugin(opts) {
3391
2448
  catch { }
3392
2449
  });
3393
2450
  }
3394
- function upsertGraphModule(rawId, code, deps) {
2451
+ function upsertGraphModule(rawId, code, deps, options) {
3395
2452
  const id = normalizeGraphId(rawId);
3396
2453
  const normDeps = deps
3397
2454
  .map((d) => normalizeGraphId(d))
@@ -3400,19 +2457,23 @@ function createHmrWebSocketPlugin(opts) {
3400
2457
  .sort();
3401
2458
  const hash = computeHash(code);
3402
2459
  const existing = graph.get(id);
3403
- if (existing && existing.hash === hash && existing.deps.length === normDeps.length && existing.deps.every((d, i) => d === normDeps[i]))
3404
- return; // unchanged
2460
+ const classification = classifyGraphUpsert(existing, hash, normDeps);
2461
+ if (classification === 'unchanged')
2462
+ return existing;
3405
2463
  graphVersion++;
3406
2464
  const gm = { id, deps: normDeps, hash };
3407
2465
  graph.set(id, gm);
3408
2466
  if (verbose) {
3409
2467
  try {
3410
- console.log('[hmr-ws][graph] upsert', { id, deps: normDeps, hash, graphVersion });
2468
+ console.log('[hmr-ws][graph] upsert', { id, deps: normDeps, hash, graphVersion, classification });
3411
2469
  console.log('[hmr-ws][graph] size', graph.size);
3412
2470
  }
3413
2471
  catch { }
3414
2472
  }
3415
- emitDelta([gm], []);
2473
+ if (shouldBroadcastGraphUpsertDelta(classification, options?.emitDeltaOnInsert === true, options?.broadcastDelta !== false)) {
2474
+ emitDelta([gm], []);
2475
+ }
2476
+ return gm;
3416
2477
  }
3417
2478
  function isTypescriptFlavor() {
3418
2479
  try {
@@ -3449,15 +2510,15 @@ function createHmrWebSocketPlugin(opts) {
3449
2510
  }
3450
2511
  async function walk(dir) {
3451
2512
  for (const name of fs.readdirSync(dir)) {
3452
- const full = pathMod.join(dir, name);
3453
- if (name === 'node_modules' || name.startsWith('.'))
2513
+ if (name === 'node_modules' || name.startsWith('.') || shouldSkipRuntimeGraphDirectoryName(name))
3454
2514
  continue;
2515
+ const full = pathMod.join(dir, name);
3455
2516
  try {
3456
2517
  const stat = fs.statSync(full);
3457
2518
  if (stat.isDirectory())
3458
2519
  await walk(full);
3459
2520
  else if (stat.isFile()) {
3460
- if (/\.(vue|ts|js|mjs|tsx|jsx)$/.test(name)) {
2521
+ if (shouldIncludeRuntimeGraphFile(full, /\.(vue|ts|js|mjs|tsx|jsx)$/i)) {
3461
2522
  const rel = '/' + pathMod.relative(root, full).split(pathMod.sep).join('/');
3462
2523
  // Transform via Vite to gather deps (ignore failures)
3463
2524
  try {
@@ -3527,7 +2588,7 @@ function createHmrWebSocketPlugin(opts) {
3527
2588
  }, {
3528
2589
  maxConcurrent: transformConcurrency,
3529
2590
  resultCacheTtlMs: transformCacheMs,
3530
- getResultCacheKey: (url) => canonicalizeTransformRequestCacheKey(url, pluginRoot),
2591
+ getResultCacheKey: (url) => canonicalizeTransformRequestCacheKey(url, pluginRoot || process.cwd()),
3531
2592
  });
3532
2593
  // Attempt early vendor manifest bootstrap once per server.
3533
2594
  if (!vendorBootstrapDone) {
@@ -3572,14 +2633,26 @@ function createHmrWebSocketPlugin(opts) {
3572
2633
  });
3573
2634
  // Additional connection diagnostics
3574
2635
  wss.on('connection', (ws, req) => {
2636
+ const role = getHmrSocketRoleFromRequestUrl(req.url);
2637
+ ws.__nsHmrClientRole = role;
3575
2638
  try {
3576
2639
  if (verbose) {
3577
2640
  const ra = req.socket?.remoteAddress;
3578
2641
  const rp = req.socket?.remotePort;
3579
- console.log('[hmr-ws] Client connected', ra + (rp ? ':' + rp : ''));
2642
+ console.log('[hmr-ws] Client connected', { role, remote: ra + (rp ? ':' + rp : '') });
3580
2643
  }
3581
2644
  }
3582
2645
  catch { }
2646
+ ws.on('close', () => {
2647
+ try {
2648
+ if (verbose) {
2649
+ const ra = req.socket?.remoteAddress;
2650
+ const rp = req.socket?.remotePort;
2651
+ console.log('[hmr-ws] Client disconnected', { role, remote: ra + (rp ? ':' + rp : '') });
2652
+ }
2653
+ }
2654
+ catch { }
2655
+ });
3583
2656
  });
3584
2657
  wss.on('error', (err) => {
3585
2658
  try {
@@ -3700,18 +2773,6 @@ function createHmrWebSocketPlugin(opts) {
3700
2773
  res.setHeader('Content-Type', 'application/json');
3701
2774
  return void res.end(JSON.stringify({ error: e?.message || String(e) }));
3702
2775
  }
3703
- // Optional diagnostics: when ?diag=1, inject simple entry/exit logs to help isolate
3704
- // execution-time failures on device without changing semantics.
3705
- try {
3706
- const wantDiag = urlObj.searchParams.get('diag') === '1';
3707
- if (wantDiag) {
3708
- const importerPath = spec.replace(/[?#].*$/, '');
3709
- const enter = `try { console.log('[sfc][enter]', ${JSON.stringify(importerPath)}, 'hasReq=', (typeof globalThis.__nsRequire==='function'||typeof globalThis.require==='function')); } catch {}`;
3710
- const exit = `\n;try { console.log('[sfc][loaded]', ${JSON.stringify(importerPath)}); } catch {}`;
3711
- code = `${enter}\n${code}${exit}`;
3712
- }
3713
- }
3714
- catch { }
3715
2776
  try {
3716
2777
  const origin = getServerOrigin(server);
3717
2778
  code = ensureVersionedRtImports(code, origin, graphVersion);
@@ -3812,7 +2873,7 @@ function createHmrWebSocketPlugin(opts) {
3812
2873
  // Support both query (?path=/abs) and path-style (/ns/m/abs)
3813
2874
  let spec = urlObj.searchParams.get('path') || '';
3814
2875
  // Optional graph version pin for deterministic boot
3815
- const forcedVer = urlObj.searchParams.get('v');
2876
+ let forcedVer = urlObj.searchParams.get('v');
3816
2877
  let bootTaggedRequest = false;
3817
2878
  if (!spec) {
3818
2879
  const base = '/ns/m';
@@ -3840,21 +2901,10 @@ function createHmrWebSocketPlugin(opts) {
3840
2901
  // The iOS HTTP ESM loader canonicalizes cache keys by stripping query params,
3841
2902
  // so we must carry the cache-buster in the path.
3842
2903
  try {
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
- }
3857
- }
2904
+ const decorated = stripDecoratedServePrefixes(spec);
2905
+ spec = decorated.cleanedSpec;
2906
+ bootTaggedRequest = decorated.bootTaggedRequest;
2907
+ forcedVer || (forcedVer = decorated.forcedVer);
3858
2908
  }
3859
2909
  catch { }
3860
2910
  // Normalize absolute filesystem paths back to project-relative ids (e.g. /src/app.ts)
@@ -3909,6 +2959,12 @@ function createHmrWebSocketPlugin(opts) {
3909
2959
  spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
3910
2960
  if (spec.startsWith('./'))
3911
2961
  spec = spec.slice(1);
2962
+ const blockedNodeModulesReason = getBlockedDeviceNodeModulesReason(spec);
2963
+ if (blockedNodeModulesReason) {
2964
+ res.statusCode = 404;
2965
+ res.end(`// [ns:m] blocked device import\nthrow new Error(${JSON.stringify(`[ns/m] ${blockedNodeModulesReason}`)});\nexport {};\n`);
2966
+ return;
2967
+ }
3912
2968
  if (!spec.startsWith('/'))
3913
2969
  spec = '/' + spec;
3914
2970
  const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
@@ -4078,7 +3134,7 @@ function createHmrWebSocketPlugin(opts) {
4078
3134
  const id = (resolvedCandidate || spec).replace(/[?#].*$/, '');
4079
3135
  // Only track app modules (under APP_VIRTUAL_WITH_SLASH) and ts/js/tsx/jsx/mjs.
4080
3136
  const isApp = id.startsWith(APP_VIRTUAL_WITH_SLASH) || id.startsWith('/app/');
4081
- if (isApp && /\.(ts|tsx|js|jsx|mjs|mts|cts)$/i.test(id)) {
3137
+ if (isApp && /\.(ts|tsx|js|jsx|mjs|mts|cts)$/i.test(id) && !isRuntimeGraphExcludedPath(id)) {
4082
3138
  const deps = Array.from(collectImportDependencies(code, id));
4083
3139
  if (verbose) {
4084
3140
  try {
@@ -4222,17 +3278,14 @@ export const piniaSymbol = p.piniaSymbol;
4222
3278
  // persistent hot.data that survives across module re-evaluations.
4223
3279
  // cleanCode() strips Vite's __vite__createHotContext assignment, which is
4224
3280
  // 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
- }
3281
+ const projectRoot = server.config?.root || process.cwd();
3282
+ const serverOrigin = getServerOrigin(server);
3283
+ if (ACTIVE_STRATEGY?.flavor === 'angular') {
3284
+ code = prepareAngularEntryForDevice(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
3285
+ }
3286
+ else {
3287
+ code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
4233
3288
  }
4234
- catch { }
4235
- code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server), true);
4236
3289
  // Expand `export * from "url"` into explicit named re-exports.
4237
3290
  // NativeScript's HTTP ESM loader may not propagate star-re-exports across
4238
3291
  // HTTP module boundaries (the namespace object gets direct exports but
@@ -4313,7 +3366,7 @@ export const piniaSymbol = p.piniaSymbol;
4313
3366
  }
4314
3367
  catch { }
4315
3368
  try {
4316
- const verNum = Number(forcedVer || graphVersion || 0);
3369
+ const verNum = getNumericServeVersionTag(forcedVer, Number(graphVersion || 0));
4317
3370
  code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
4318
3371
  code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
4319
3372
  code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
@@ -4323,20 +3376,20 @@ export const piniaSymbol = p.piniaSymbol;
4323
3376
  // IMPORTANT: use path prefix (not ?v= query) because the iOS HTTP ESM loader
4324
3377
  // strips query params when computing module cache keys, so ?v= doesn't bust the V8 cache.
4325
3378
  try {
4326
- const ver = String(forcedVer || graphVersion || 0);
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;
3379
+ const ver = (() => {
3380
+ const raw = String(forcedVer || '').trim();
3381
+ if (raw) {
3382
+ if (raw === 'live' || /^n\d+$/i.test(raw) || /^v[^/]+$/i.test(raw)) {
3383
+ return raw;
3384
+ }
3385
+ if (/^\d+$/.test(raw)) {
3386
+ return `v${raw}`;
3387
+ }
4337
3388
  }
4338
- return (bootTaggedRequest ? bootHmrPrefix : hmrPrefix) + p.slice('/ns/m'.length);
4339
- };
3389
+ return `v${String(graphVersion || 0)}`;
3390
+ })();
3391
+ const origin = getServerOrigin(server);
3392
+ const rewritePath = (p) => rewriteNsMImportPathForHmr(p, ver, bootTaggedRequest);
4340
3393
  // 1) Static imports: import ... from "/ns/m/..."
4341
3394
  code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
4342
3395
  // 2) Side-effect imports: import "/ns/m/..."
@@ -4493,16 +3546,6 @@ export const piniaSymbol = p.piniaSymbol;
4493
3546
  }
4494
3547
  catch { }
4495
3548
  }
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 { }
4506
3549
  res.statusCode = 200;
4507
3550
  res.end(code);
4508
3551
  }
@@ -4702,39 +3745,41 @@ export const piniaSymbol = p.piniaSymbol;
4702
3745
  server.middlewares.use(async (req, res, next) => {
4703
3746
  try {
4704
3747
  const urlObj = new URL(req.url || '', 'http://localhost');
4705
- // Match /ns/core, /ns/core/<ver>, and /ns/core/<subpath> (path-based deep imports)
4706
- if (!urlObj.pathname.startsWith('/ns/core'))
3748
+ const coreRequest = parseCoreBridgeRequest(urlObj.pathname, urlObj.searchParams, Number(graphVersion || 0));
3749
+ if (!coreRequest)
4707
3750
  return next();
4708
3751
  res.setHeader('Access-Control-Allow-Origin', '*');
4709
3752
  res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4710
3753
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4711
3754
  res.setHeader('Pragma', 'no-cache');
4712
3755
  res.setHeader('Expires', '0');
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 : '');
4720
- const key = sub ? `@nativescript/core/${sub}` : `@nativescript/core`;
3756
+ const { hasExplicitVersion, key, normalizedSub, sub, ver } = coreRequest;
4721
3757
  // Any @nativescript/core subpath import (including shallow ones like
4722
3758
  // `utils`) may expose exports that are not available from the root
4723
3759
  // vendor bundle namespace. Serve the actual transformed module content
4724
3760
  // instead of the lightweight proxy bridge.
4725
3761
  if (sub) {
4726
3762
  try {
4727
- const normalizedSub = sub.replace(/^\/+/, '');
3763
+ const resolvedSubpath = normalizedSub || sub;
3764
+ const projectRoot = server.config?.root || process.cwd();
3765
+ const resolveModuleId = async (moduleId) => {
3766
+ const resolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
3767
+ return typeof resolved === 'string' ? resolved : resolved?.id || null;
3768
+ };
3769
+ const resolvedId = await resolveRuntimeCoreModulePath(resolvedSubpath, resolveModuleId);
3770
+ const modulePath = resolvedId || `/node_modules/@nativescript/core/${resolvedSubpath}`;
3771
+ const transformed = await sharedTransformRequest(modulePath);
4728
3772
  if (!hasExplicitVersion) {
3773
+ if (transformed?.code) {
3774
+ const expandedModuleCode = await expandStarExports(transformed.code, server, projectRoot, verbose);
3775
+ res.statusCode = 200;
3776
+ res.end(buildVersionedCoreSubpathAliasModule(resolvedSubpath, ver, extractExportedNames(expandedModuleCode), hasModuleDefaultExport(expandedModuleCode)));
3777
+ return;
3778
+ }
4729
3779
  res.statusCode = 200;
4730
- res.end(buildVersionedCoreSubpathAliasModule(normalizedSub, ver));
3780
+ res.end(buildVersionedCoreSubpathAliasModule(resolvedSubpath, ver));
4731
3781
  return;
4732
3782
  }
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
3783
  if (transformed?.code) {
4739
3784
  // Minimal pipeline: Vite already produces correct ESM.
4740
3785
  // ONLY rewrite specifier strings to device-fetchable URLs.
@@ -4755,16 +3800,37 @@ export const piniaSymbol = p.piniaSymbol;
4755
3800
  }
4756
3801
  }
4757
3802
  // Main entry or shallow subpath: use proxy bridge
4758
- // HTTP-only core bridge: do NOT use require/createRequire. Export a proxy that maps
4759
- // property access to globalThis first, then to any available vendor registry module.
4760
- let code = REQUIRE_GUARD_SNIPPET +
4761
- `// [ns-core-bridge][v${ver}] HTTP-only ESM bridge (default proxy only)\n` +
4762
- `const g = globalThis;\n` +
4763
- `const reg = (g.__nsVendorRegistry ||= new Map());\n` +
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` +
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` +
4766
- `// Default export: namespace-like proxy\n` +
4767
- `export default __core;\n`;
3803
+ let code = buildVersionedCoreMainBridgeModule(key, ver);
3804
+ if (!sub) {
3805
+ try {
3806
+ const projectRoot = server.config?.root || process.cwd();
3807
+ const coreSpecifier = '@nativescript/core';
3808
+ const resolved = await server.pluginContainer?.resolveId?.(coreSpecifier, undefined);
3809
+ const resolvedId = typeof resolved === 'string' ? resolved : resolved?.id || null;
3810
+ const modulePath = resolvedId || '/node_modules/@nativescript/core/index.js';
3811
+ const staticExportNames = collectStaticExportNamesFromFile(modulePath);
3812
+ const staticExportOrigins = await normalizeCoreExportOriginsForRuntime(collectStaticExportOriginsFromFile(modulePath), async (moduleId) => {
3813
+ const nextResolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
3814
+ return typeof nextResolved === 'string' ? nextResolved : nextResolved?.id || null;
3815
+ }, modulePath);
3816
+ if (staticExportNames.length) {
3817
+ code = buildVersionedCoreMainBridgeModule(key, ver, staticExportNames, staticExportOrigins);
3818
+ }
3819
+ else {
3820
+ const transformed = await sharedTransformRequest(modulePath);
3821
+ if (transformed?.code) {
3822
+ const expandedModuleCode = await expandStarExports(transformed.code, server, projectRoot, verbose);
3823
+ code = buildVersionedCoreMainBridgeModule(key, ver, extractExportedNames(expandedModuleCode));
3824
+ }
3825
+ }
3826
+ }
3827
+ catch (e) {
3828
+ try {
3829
+ console.warn('[ns-core-bridge] main bridge export discovery failed:', e?.message);
3830
+ }
3831
+ catch { }
3832
+ }
3833
+ }
4768
3834
  res.statusCode = 200;
4769
3835
  res.end(code);
4770
3836
  }
@@ -5808,7 +4874,6 @@ export const piniaSymbol = p.piniaSymbol;
5808
4874
  parts.push(scriptTransformed);
5809
4875
  parts.push(renderDecl);
5810
4876
  parts.push(`try { if (!__ns_sfc__.render) Object.defineProperty(__ns_sfc__, 'render', { configurable: true, enumerable: true, get(){ const r = (typeof __ns_getRender==='function' ? __ns_getRender() : undefined); Object.defineProperty(__ns_sfc__, 'render', { value: r, writable: true, configurable: true, enumerable: true }); return r; }, set(v){ Object.defineProperty(__ns_sfc__, 'render', { value: v, writable: true, configurable: true, enumerable: true }); } }); } catch(_e){}`);
5811
- parts.push(`// diagnostic: hadScriptDefaultPre=${hadScriptDefaultPre} triedInlineTemplate=${triedInlineTemplate} renderOk=${renderOk} tplBytes=${compiledTplCode.length} scriptBytes=${(compiledScript || '').length} templateErr=${templateErr ? templateErr?.message : ''}`);
5812
4877
  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; }`);
5813
4878
  parts.push(`export default __ns_sfc__`);
5814
4879
  let inlineCode = parts.filter(Boolean).join('\n');
@@ -5887,7 +4952,6 @@ export const piniaSymbol = p.piniaSymbol;
5887
4952
  outParts.push(renderDecl);
5888
4953
  }
5889
4954
  outParts.push(`try { if (!__ns_sfc__.render) Object.defineProperty(__ns_sfc__, 'render', { configurable: true, enumerable: true, get(){ const r = (typeof __ns_getRender==='function' ? __ns_getRender() : (typeof __ns_render==='function' ? __ns_render : undefined)); Object.defineProperty(__ns_sfc__, 'render', { value: r, writable: true, configurable: true, enumerable: true }); return r; }, set(v){ Object.defineProperty(__ns_sfc__, 'render', { value: v, writable: true, configurable: true, enumerable: true }); } }); } catch(_e){}`);
5890
- outParts.push(`// diagnostic: hadScriptDefaultPre=${hadScriptDefaultPre} triedInlineTemplate=${triedInlineTemplate} renderOk=${renderOk} tplBytes=${compiledTplCode.length} scriptBytes=${(compiledScript || '').length} templateErr=${templateErr ? templateErr?.message : ''}`);
5891
4955
  // Export named render as a function that resolves lazily
5892
4956
  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; }');
5893
4957
  outParts.push('export default __ns_sfc__');
@@ -6188,12 +5252,11 @@ export const piniaSymbol = p.piniaSymbol;
6188
5252
  }
6189
5253
  let asm;
6190
5254
  if (inlineOk) {
6191
- const diagLine = `// diagnostic:inlineOk ver=${ver} inlineBlock=${!!(inlineBlock && inlineBlock.trim())} helperBindingsLen=${helperBindings.length} renderDeclLen=${renderDecl.length}`;
6192
5255
  if (inlineBlock && inlineBlock.trim()) {
6193
- asm = [`// [sfc-asm] ${base} (inlined template body)`, `export * from ${JSON.stringify(scriptUrl)};`, `import * as __script from ${JSON.stringify(scriptUrl)};`, inlineBlock, `const __ns_sfc__ = (__script && __script.default) ? __script.default : {};`, `try { if (typeof __ns_render === 'function' && !__ns_sfc__.render) __ns_sfc__.render = __ns_render; } catch {}`, `export default __ns_sfc__;`, diagLine].join('\n');
5256
+ asm = [`// [sfc-asm] ${base} (inlined template body)`, `export * from ${JSON.stringify(scriptUrl)};`, `import * as __script from ${JSON.stringify(scriptUrl)};`, inlineBlock, `const __ns_sfc__ = (__script && __script.default) ? __script.default : {};`, `try { if (typeof __ns_render === 'function' && !__ns_sfc__.render) __ns_sfc__.render = __ns_render; } catch {}`, `export default __ns_sfc__;`].join('\n');
6194
5257
  }
6195
5258
  else {
6196
- asm = [`// [sfc-asm] ${base} (inlined template)`, `export * from ${JSON.stringify(scriptUrl)};`, `import * as __script from ${JSON.stringify(scriptUrl)};`, helperBindings, renderDecl, `const __ns_sfc__ = (__script && __script.default) ? __script.default : {};`, `try { if (typeof __ns_render === 'function' && !__ns_sfc__.render) __ns_sfc__.render = __ns_render; } catch {}`, `export default __ns_sfc__;`, diagLine].filter(Boolean).join('\n');
5259
+ asm = [`// [sfc-asm] ${base} (inlined template)`, `export * from ${JSON.stringify(scriptUrl)};`, `import * as __script from ${JSON.stringify(scriptUrl)};`, helperBindings, renderDecl, `const __ns_sfc__ = (__script && __script.default) ? __script.default : {};`, `try { if (typeof __ns_render === 'function' && !__ns_sfc__.render) __ns_sfc__.render = __ns_render; } catch {}`, `export default __ns_sfc__;`].filter(Boolean).join('\n');
6197
5260
  }
6198
5261
  }
6199
5262
  else {
@@ -6465,8 +5528,8 @@ export const piniaSymbol = p.piniaSymbol;
6465
5528
  ts: Date.now(),
6466
5529
  delta: true,
6467
5530
  };
6468
- wss.clients.forEach((c) => {
6469
- if (c.readyState === c.OPEN) {
5531
+ wss?.clients.forEach((c) => {
5532
+ if (isSocketClientOpen(c)) {
6470
5533
  try {
6471
5534
  c.send(JSON.stringify(single));
6472
5535
  }
@@ -6554,6 +5617,9 @@ export const piniaSymbol = p.piniaSymbol;
6554
5617
  if (!wss) {
6555
5618
  return;
6556
5619
  }
5620
+ if (isRuntimeGraphExcludedPath(file)) {
5621
+ return;
5622
+ }
6557
5623
  // Graph update for this file change (wrapped to avoid aborting rest of handler)
6558
5624
  try {
6559
5625
  const skipAngularHtmlGraphUpdate = ACTIVE_STRATEGY.flavor === 'angular' && /\.(html|htm)$/i.test(file);
@@ -6573,7 +5639,10 @@ export const piniaSymbol = p.piniaSymbol;
6573
5639
  .filter(Boolean);
6574
5640
  const transformed = await server.transformRequest(mod.id);
6575
5641
  const code = transformed?.code || '';
6576
- upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps);
5642
+ upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps, {
5643
+ emitDeltaOnInsert: true,
5644
+ broadcastDelta: ACTIVE_STRATEGY.flavor !== 'angular',
5645
+ });
6577
5646
  }
6578
5647
  catch (error) {
6579
5648
  if (verbose)
@@ -6616,7 +5685,7 @@ export const piniaSymbol = p.piniaSymbol;
6616
5685
  ],
6617
5686
  };
6618
5687
  wss.clients.forEach((client) => {
6619
- if (client.readyState === client.OPEN) {
5688
+ if (isSocketClientOpen(client)) {
6620
5689
  client.send(JSON.stringify(msg));
6621
5690
  }
6622
5691
  });
@@ -6682,27 +5751,20 @@ export const piniaSymbol = p.piniaSymbol;
6682
5751
  }
6683
5752
  }
6684
5753
  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({
5754
+ const transitiveImporters = shouldInvalidateAngularTransitiveImporters({ flavor: ACTIVE_STRATEGY.flavor, file })
5755
+ ? collectAngularTransitiveImportersForInvalidation({
6696
5756
  modules: angularTransitiveInvalidationRoots,
6697
5757
  isExcluded: (id) => id.includes('/node_modules/'),
6698
5758
  maxDepth: 16,
6699
- });
6700
- for (const mod of transitiveImporters) {
6701
- if (mod?.id) {
6702
- transformCacheInvalidationUrls.add(mod.id);
6703
- }
6704
- }
6705
- }
5759
+ })
5760
+ : [];
5761
+ const transformCacheInvalidationUrls = new Set(collectAngularTransformCacheInvalidationUrls({
5762
+ file,
5763
+ isTs,
5764
+ hotUpdateRoots: angularHotUpdateRoots,
5765
+ transitiveImporters,
5766
+ projectRoot: server.config.root || process.cwd(),
5767
+ }));
6706
5768
  if (transformCacheInvalidationUrls.size) {
6707
5769
  sharedTransformRequest.invalidateMany(transformCacheInvalidationUrls);
6708
5770
  if (verbose) {
@@ -6723,10 +5785,18 @@ export const piniaSymbol = p.piniaSymbol;
6723
5785
  type: 'ns:angular-update',
6724
5786
  origin,
6725
5787
  path: rel,
5788
+ version: graphVersion,
6726
5789
  timestamp: Date.now(),
6727
5790
  };
5791
+ if (verbose) {
5792
+ console.log('[hmr-ws][angular] broadcasting update', Array.from(wss.clients || []).map((client) => ({
5793
+ role: getHmrSocketRole(client),
5794
+ readyState: client.readyState,
5795
+ openState: client.OPEN,
5796
+ })));
5797
+ }
6728
5798
  wss.clients.forEach((client) => {
6729
- if (client.readyState === client.OPEN) {
5799
+ if (isSocketClientOpen(client)) {
6730
5800
  client.send(JSON.stringify(msg));
6731
5801
  }
6732
5802
  });
@@ -6748,10 +5818,7 @@ export const piniaSymbol = p.piniaSymbol;
6748
5818
  // Treat the changed file itself as a graph module with no deps. We only
6749
5819
  // care that its hash/identity changes so the client sees a delta and can
6750
5820
  // perform a TS root reset. Code is not used for execution here.
6751
- upsertGraphModule(rel, '', []);
6752
- const gm = graph.get(normalizeGraphId(rel));
6753
- if (gm)
6754
- emitDelta([gm], []);
5821
+ upsertGraphModule(rel, '', [], { emitDeltaOnInsert: true });
6755
5822
  }
6756
5823
  catch (e) {
6757
5824
  if (verbose)
@@ -6782,7 +5849,7 @@ export const piniaSymbol = p.piniaSymbol;
6782
5849
  if (!existing) {
6783
5850
  // Module not in graph yet — force upsert with timestamp-based
6784
5851
  // hash so the client sees a change.
6785
- upsertGraphModule(rel, `/* solid-hmr ${Date.now()} */`, []);
5852
+ upsertGraphModule(rel, `/* solid-hmr ${Date.now()} */`, [], { emitDeltaOnInsert: true });
6786
5853
  }
6787
5854
  // Log what we're sending so devs can trace the flow on the server side.
6788
5855
  if (verbose) {
@@ -6979,7 +6046,7 @@ if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
6979
6046
  version: graphVersion,
6980
6047
  };
6981
6048
  wss.clients.forEach((client) => {
6982
- if (client.readyState === client.OPEN) {
6049
+ if (isSocketClientOpen(client)) {
6983
6050
  client.send(JSON.stringify(registryUpdateMsg));
6984
6051
  }
6985
6052
  });
@@ -7150,4 +6217,6 @@ function getServerOrigin(server) {
7150
6217
  // Test-only export: allow unit tests to run the sanitizer on snippets without booting a server
7151
6218
  // Safe in production builds; this is a named export that tests can import explicitly.
7152
6219
  export const __test_processCodeForDevice = processCodeForDevice;
6220
+ export const __test_resolveVendorRouting = resolveVendorRouting;
6221
+ export const __test_getBlockedDeviceNodeModulesReason = getBlockedDeviceNodeModulesReason;
7153
6222
  //# sourceMappingURL=websocket.js.map