@nativescript/vite 8.0.0-alpha.4 → 8.0.0-alpha.5

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 (37) hide show
  1. package/configuration/base.js +57 -0
  2. package/configuration/base.js.map +1 -1
  3. package/helpers/config-as-json.js +10 -0
  4. package/helpers/config-as-json.js.map +1 -1
  5. package/helpers/main-entry.js +167 -13
  6. package/helpers/main-entry.js.map +1 -1
  7. package/helpers/ns-core-url.d.ts +84 -0
  8. package/helpers/ns-core-url.js +168 -0
  9. package/helpers/ns-core-url.js.map +1 -0
  10. package/hmr/server/core-sanitize.d.ts +8 -4
  11. package/hmr/server/core-sanitize.js +71 -41
  12. package/hmr/server/core-sanitize.js.map +1 -1
  13. package/hmr/server/import-map.js +7 -3
  14. package/hmr/server/import-map.js.map +1 -1
  15. package/hmr/server/ns-core-cjs-shape.d.ts +206 -0
  16. package/hmr/server/ns-core-cjs-shape.js +273 -0
  17. package/hmr/server/ns-core-cjs-shape.js.map +1 -0
  18. package/hmr/server/websocket-core-bridge.d.ts +0 -2
  19. package/hmr/server/websocket-core-bridge.js +60 -58
  20. package/hmr/server/websocket-core-bridge.js.map +1 -1
  21. package/hmr/server/websocket-module-specifiers.js +12 -0
  22. package/hmr/server/websocket-module-specifiers.js.map +1 -1
  23. package/hmr/server/websocket-ns-m-finalize.d.ts +0 -10
  24. package/hmr/server/websocket-ns-m-finalize.js +26 -11
  25. package/hmr/server/websocket-ns-m-finalize.js.map +1 -1
  26. package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
  27. package/hmr/server/websocket-served-module-helpers.js +589 -0
  28. package/hmr/server/websocket-served-module-helpers.js.map +1 -0
  29. package/hmr/server/websocket-vue-sfc.d.ts +0 -8
  30. package/hmr/server/websocket-vue-sfc.js +15 -14
  31. package/hmr/server/websocket-vue-sfc.js.map +1 -1
  32. package/hmr/server/websocket.d.ts +2 -2
  33. package/hmr/server/websocket.js +2355 -177
  34. package/hmr/server/websocket.js.map +1 -1
  35. package/hmr/shared/vendor/manifest.js +114 -12
  36. package/hmr/shared/vendor/manifest.js.map +1 -1
  37. package/package.json +1 -1
@@ -1,53 +1,56 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCoreSpecToBridge, isDeepCoreSubpath, rewriteSpecifiersForDevice } from './core-sanitize.js';
3
+ import { buildDefaultExportFooter, buildShapeInstallHeader, hasNamespaceReExport, rewriteNamespaceReExportsForShape } from './ns-core-cjs-shape.js';
3
4
  // AST tooling for robust transformations
4
5
  import { parse as babelParse } from '@babel/parser';
5
6
  import { genCode } from '../helpers/babel.js';
7
+ import babelCore from '@babel/core';
6
8
  import traverse from '@babel/traverse';
7
9
  // Ensure traverse callable across CJS/ESM builds
8
10
  const babelTraverse = traverse?.default || traverse;
9
11
  import * as t from '@babel/types';
10
12
  import { existsSync, readFileSync, statSync } from 'fs';
11
13
  import { astNormalizeModuleImportsAndHelpers, astVerifyAndAnnotateDuplicates } from '../helpers/ast-normalizer.js';
12
- import { stripDanglingViteCjsImports } from '../helpers/sanitize.js';
14
+ import { stripRtCoreSentinel, stripDanglingViteCjsImports } from '../helpers/sanitize.js';
13
15
  import { WebSocketServer } from 'ws';
14
16
  import * as path from 'path';
15
17
  import { createHash } from 'crypto';
16
18
  import * as PAT from './constants.js';
17
19
  import { getVendorManifest, resolveVendorSpecifier } from '../shared/vendor/registry.js';
18
- import { getProjectRootPath } from '../../helpers/project.js';
20
+ import { getPackageJson, getProjectFilePath, getProjectRootPath } from '../../helpers/project.js';
19
21
  import { loadPrebuiltVendorManifest } from '../shared/vendor/manifest-loader.js';
20
22
  import '../vendor-bootstrap.js';
23
+ import { NS_NATIVE_TAGS } from './compiler.js';
24
+ import { vueSfcCompiler } from '../frameworks/vue/server/compiler.js';
21
25
  import { linkAngularPartialsIfNeeded } from '../frameworks/angular/server/linker.js';
22
26
  import { vueServerStrategy } from '../frameworks/vue/server/strategy.js';
23
27
  import { angularServerStrategy } from '../frameworks/angular/server/strategy.js';
24
28
  import { solidServerStrategy } from '../frameworks/solid/server/strategy.js';
25
29
  import { typescriptServerStrategy } from '../frameworks/typescript/server/strategy.js';
26
- import { createProcessSfcCode } from '../frameworks/vue/server/sfc-transforms.js';
30
+ import { buildInlineTemplateBlock, createProcessSfcCode, extractTemplateRender, processTemplateVariantMinimal } from '../frameworks/vue/server/sfc-transforms.js';
31
+ import { astExtractImportsAndStripTypes } from '../helpers/ast-extract.js';
27
32
  import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
28
33
  import { buildRuntimeConfig, generateImportMap } from './import-map.js';
29
34
  import { getCliFlags } from '../../helpers/cli-flags.js';
35
+ import { normalizeCoreSub as normalizeCoreSubCanonical } from '../../helpers/ns-core-url.js';
30
36
  import { isRuntimeGraphExcludedPath, matchesRuntimeGraphModuleId, normalizeRuntimeGraphPath, shouldIncludeRuntimeGraphFile, shouldSkipRuntimeGraphDirectoryName } from './runtime-graph-filter.js';
31
37
  import { resolveAngularCoreHmrImportSource, rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
32
38
  import { canonicalizeTransformRequestCacheKey, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload } from './websocket-angular-hot-update.js';
33
39
  import { classifyGraphUpsert, shouldBroadcastGraphUpsertDelta } from './websocket-graph-upsert.js';
34
40
  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';
35
41
  import { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
36
- import { buildVersionedCoreMainBridgeModule, buildVersionedCoreSubpathAliasModule, collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, extractDirectExportedNames, hasModuleDefaultExport, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest, resolveRuntimeCoreModulePath } from './websocket-core-bridge.js';
37
- import { finalizeNsMServedModule } from './websocket-ns-m-finalize.js';
38
- import { createNsMRequestContext, resolveNsMTransformedModule } from './websocket-ns-m-request.js';
39
- import { getNumericServeVersionTag, rewriteNsMImportPathForHmr } from './websocket-ns-m-paths.js';
40
- import { registerRuntimeCompatHandlers } from './websocket-runtime-compat.js';
41
- import { registerTxnHandler } from './websocket-txn.js';
42
- import { registerVendorUnifierHandler } from './websocket-vendor-unifier.js';
43
- import { registerVueSfcHandlers } from './websocket-vue-sfc.js';
42
+ import { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, extractDirectExportedNames, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest, resolveRuntimeCoreModulePath } from './websocket-core-bridge.js';
44
43
  import { createSharedTransformRequestRunner } from './shared-transform-request.js';
45
44
  export { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
46
45
  export { stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
47
- export { buildVersionedCoreMainBridgeModule, buildVersionedCoreSubpathAliasModule, collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest } from './websocket-core-bridge.js';
48
- export { rewriteNsMImportPathForHmr } from './websocket-ns-m-paths.js';
46
+ export { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest } from './websocket-core-bridge.js';
49
47
  export { rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
50
48
  export { canonicalizeTransformRequestCacheKey, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, createSharedTransformRequestRunner, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload, classifyGraphUpsert, shouldBroadcastGraphUpsertDelta };
49
+ const pluginTransformTypescript = (() => {
50
+ const requireFromHere = createRequire(import.meta.url);
51
+ const loaded = requireFromHere('@babel/plugin-transform-typescript');
52
+ return loaded?.default || loaded;
53
+ })();
51
54
  // Build a serialized process.env object from CLI --env.* flags.
52
55
  // This is injected into every HTTP-served module so app code referencing
53
56
  // process.env.TEST_ENV (etc.) works on device in HMR dev mode.
@@ -63,6 +66,7 @@ try {
63
66
  }
64
67
  catch { }
65
68
  const __processEnvJson = JSON.stringify(__processEnvEntries);
69
+ const { parse, compileTemplate, compileScript } = vueSfcCompiler;
66
70
  const APP_ROOT_DIR = getProjectAppPath();
67
71
  const APP_VIRTUAL_PREFIX = getProjectAppVirtualPath();
68
72
  const APP_VIRTUAL_WITH_SLASH = `${APP_VIRTUAL_PREFIX}/`;
@@ -890,6 +894,52 @@ function toNodeModulesHttpModuleId(importPath) {
890
894
  }
891
895
  return `/ns/m/node_modules/${nodeModulesSpecifier}`;
892
896
  }
897
+ export function rewriteNsMImportPathForHmr(p, ver, bootTaggedRequest) {
898
+ const toHmrServeTag = (value) => {
899
+ const raw = String(value ?? '').trim();
900
+ if (!raw) {
901
+ return 'v0';
902
+ }
903
+ if (raw === 'live' || /^n\d+$/i.test(raw) || /^v[^/]+$/i.test(raw)) {
904
+ return raw;
905
+ }
906
+ if (/^\d+$/.test(raw)) {
907
+ return `v${raw}`;
908
+ }
909
+ return raw;
910
+ };
911
+ if (!p || !p.startsWith('/ns/m/')) {
912
+ return p;
913
+ }
914
+ 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/');
915
+ if (canonicalNodeModulesPath.startsWith('/ns/m/node_modules/')) {
916
+ return canonicalNodeModulesPath;
917
+ }
918
+ if (canonicalNodeModulesPath.startsWith('/ns/m/__ns_boot__/')) {
919
+ return canonicalNodeModulesPath;
920
+ }
921
+ if (canonicalNodeModulesPath.startsWith('/ns/m/__ns_hmr__/')) {
922
+ return bootTaggedRequest ? `/ns/m/__ns_boot__/b1${canonicalNodeModulesPath.slice('/ns/m'.length)}` : canonicalNodeModulesPath;
923
+ }
924
+ const tag = toHmrServeTag(ver);
925
+ const hmrPrefix = `/ns/m/__ns_hmr__/${tag}`;
926
+ const bootHmrPrefix = `/ns/m/__ns_boot__/b1/__ns_hmr__/${tag}`;
927
+ return (bootTaggedRequest ? bootHmrPrefix : hmrPrefix) + canonicalNodeModulesPath.slice('/ns/m'.length);
928
+ }
929
+ function getNumericServeVersionTag(tag, fallback) {
930
+ const raw = String(tag || '').trim();
931
+ if (!raw) {
932
+ return fallback;
933
+ }
934
+ const versionMatch = raw.match(/^v(\d+)$/);
935
+ if (versionMatch?.[1]) {
936
+ return Number(versionMatch[1]);
937
+ }
938
+ if (/^\d+$/.test(raw)) {
939
+ return Number(raw);
940
+ }
941
+ return fallback;
942
+ }
893
943
  function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
894
944
  if (!spec || typeof spec !== 'string') {
895
945
  return null;
@@ -1049,8 +1099,31 @@ export function wrapCommonJsModuleForDevice(code) {
1049
1099
  `var __ns_cjs_require_kind = (typeof globalThis.__nsBaseRequire === 'function' ? 'base-require' : (typeof globalThis.__nsRequire === 'function' ? 'vendor-require' : 'global-require'));\n` +
1050
1100
  `var require = function(spec) {\n` +
1051
1101
  ` if (!__ns_cjs_require_base) { throw new Error('require is not defined'); }\n` +
1052
- ` try { var __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(String(spec), { kind: __ns_cjs_require_kind, specifier: String(spec), via: 'cjs-wrapper', parent: (typeof import.meta !== 'undefined' && import.meta && import.meta.url) ? import.meta.url : undefined }); } } catch (e) {}\n` +
1053
- ` var mod = __ns_cjs_require_base(spec);\n` +
1102
+ // Resolve relative specifiers against the HTTP-served module's URL
1103
+ // before delegating to NS's runtime require. Without this step,
1104
+ // \`require('./base64-vlq')\` inside a CJS module served from
1105
+ // \`http://.../ns/m/node_modules/source-map-js/lib/source-map-generator.js\`
1106
+ // would pass a literal '"./base64-vlq"' to the native require, which
1107
+ // has no notion of the current HTTP-module's location and either
1108
+ // throws "Module not found" or fetches an arbitrary filesystem path
1109
+ // that happens to parse as code (producing misleading syntax errors
1110
+ // like "missing ) after argument list" from unrelated modules).
1111
+ ` var __nsResolvedSpec = spec;\n` +
1112
+ ` try {\n` +
1113
+ ` if (typeof spec === 'string' && (spec.indexOf('./') === 0 || spec.indexOf('../') === 0)) {\n` +
1114
+ ` var __nsParentUrl = (typeof import.meta !== 'undefined' && import.meta && typeof import.meta.url === 'string') ? import.meta.url : null;\n` +
1115
+ ` if (__nsParentUrl) {\n` +
1116
+ ` var __nsResolvedUrl = new URL(spec, __nsParentUrl);\n` +
1117
+ ` // Common Node-style bare extensions: prefer .js if the resolved URL lacks an extension in its last path segment.\n` +
1118
+ ` if (!/\\.[A-Za-z0-9]+$/.test(__nsResolvedUrl.pathname.split('/').pop() || '')) {\n` +
1119
+ ` __nsResolvedUrl.pathname = __nsResolvedUrl.pathname.replace(/\\/+$/, '') + '.js';\n` +
1120
+ ` }\n` +
1121
+ ` __nsResolvedSpec = __nsResolvedUrl.href;\n` +
1122
+ ` }\n` +
1123
+ ` }\n` +
1124
+ ` } catch (e) {}\n` +
1125
+ ` try { var __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(String(__nsResolvedSpec), { kind: __ns_cjs_require_kind, specifier: String(spec), url: __nsResolvedSpec !== spec ? __nsResolvedSpec : undefined, via: 'cjs-wrapper', parent: (typeof import.meta !== 'undefined' && import.meta && import.meta.url) ? import.meta.url : undefined }); } } catch (e) {}\n` +
1126
+ ` var mod = __ns_cjs_require_base(__nsResolvedSpec);\n` +
1054
1127
  ` try {\n` +
1055
1128
  ` if (mod && (typeof mod === 'object' || typeof mod === 'function') && mod.default !== undefined) {\n` +
1056
1129
  ` var keys = [];\n` +
@@ -2096,6 +2169,27 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
2096
2169
  return `${prefix}./${depFile}${suffix}`;
2097
2170
  }
2098
2171
  }
2172
+ // Bare npm package specifier fallback — route to /ns/m/node_modules/.
2173
+ // This catches specifiers like `source-map-js/lib/source-map-generator.js`
2174
+ // emitted by helpers such as the CommonJS compat transform, which Vite
2175
+ // would normally resolve to an absolute path but which pass through the
2176
+ // rewriter as bare strings here. Under HMR (core external) bundle.mjs
2177
+ // depends on these resolving over HTTP rather than via a filesystem
2178
+ // bare-specifier lookup, which iOS can't satisfy and which crashes with
2179
+ // "Module not found".
2180
+ if (spec && !spec.startsWith('/') && !spec.startsWith('./') && !spec.startsWith('../') && !/^https?:\/\//i.test(spec) && !spec.startsWith('ns-vendor:') && !spec.startsWith('@nativescript/core')) {
2181
+ // Only treat as a package spec if it looks like one — disallow
2182
+ // plain identifiers like `moment` unresolved (those are left alone
2183
+ // for existing vendor-routing paths to handle).
2184
+ const bareNpmRe = /^(?:@[A-Za-z0-9][\w.-]*\/)?[A-Za-z0-9][\w.-]*(?:\/[\w.\-/]+)?$/;
2185
+ if (bareNpmRe.test(spec)) {
2186
+ const httpSpec = `/ns/m/node_modules/${spec}`;
2187
+ if (httpOriginSafe) {
2188
+ return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
2189
+ }
2190
+ return `${prefix}${httpSpec}${suffix}`;
2191
+ }
2192
+ }
2099
2193
  // Leave everything else unchanged (vendor imports, etc.)
2100
2194
  return `${prefix}${spec}${suffix}`;
2101
2195
  };
@@ -2816,27 +2910,139 @@ function createHmrWebSocketPlugin(opts) {
2816
2910
  const urlObj = new URL(req.url || '', 'http://localhost');
2817
2911
  if (!urlObj.pathname.startsWith('/ns/m'))
2818
2912
  return next();
2913
+ // Populate the initial graph on first /ns/m request so graphVersion is
2914
+ // non-zero and stable before we rewrite any child import paths. Without
2915
+ // this the first dyn-imports (e.g. main.ts → routed tab components) are
2916
+ // served with graphVersion=0, which makes the 'live' → v{N} substitution
2917
+ // below no-op; later dyn-imports (after the HMR websocket client connects
2918
+ // and populateInitialGraph runs inside 'connection') arrive when
2919
+ // graphVersion has jumped to some N>0, so their child URLs land at /v{N}/
2920
+ // while the early tree still references /live/ — two distinct iOS HTTP
2921
+ // ESM cache entries for the same file, two Angular class identities,
2922
+ // NG0912 selector collisions.
2923
+ if (graph.size === 0) {
2924
+ try {
2925
+ await populateInitialGraph(server);
2926
+ }
2927
+ catch (e) {
2928
+ if (verbose)
2929
+ console.warn('[hmr-ws][graph] lazy initial population failed', e);
2930
+ }
2931
+ }
2819
2932
  res.setHeader('Access-Control-Allow-Origin', '*');
2820
2933
  res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
2821
2934
  // Disable caching for dev ESM endpoints to avoid device-side stale module reuse
2822
2935
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
2823
2936
  res.setHeader('Pragma', 'no-cache');
2824
2937
  res.setHeader('Expires', '0');
2825
- const serverRoot = (server.config?.root || process.cwd());
2826
- const requestContextResult = createNsMRequestContext(req.url || '', serverRoot, APP_VIRTUAL_WITH_SLASH);
2827
- if (requestContextResult.kind === 'next')
2828
- return next();
2829
- if (requestContextResult.kind === 'response') {
2830
- res.statusCode = requestContextResult.statusCode;
2831
- res.end(requestContextResult.code);
2938
+ // Support both query (?path=/abs) and path-style (/ns/m/abs)
2939
+ let spec = urlObj.searchParams.get('path') || '';
2940
+ // Optional graph version pin for deterministic boot
2941
+ let forcedVer = urlObj.searchParams.get('v');
2942
+ let bootTaggedRequest = false;
2943
+ if (!spec) {
2944
+ const base = '/ns/m';
2945
+ let rest = urlObj.pathname.slice(base.length);
2946
+ if (rest && rest !== '/')
2947
+ spec = rest;
2948
+ }
2949
+ // Special-case stub for anomalous '@' imports emitted as '/__invalid_at__.mjs'
2950
+ if (spec === '/__invalid_at__.mjs' || spec === '__invalid_at__.mjs') {
2951
+ res.statusCode = 200;
2952
+ res.end("// invalid '@' import stub\nexport {}\n");
2953
+ return;
2954
+ }
2955
+ if (!spec) {
2956
+ res.statusCode = 200;
2957
+ res.end('export {}\n');
2832
2958
  return;
2833
2959
  }
2834
- let { spec, forcedVer, bootTaggedRequest, transformCandidates, candidates } = requestContextResult.value;
2960
+ const serverRoot = (server.config?.root || process.cwd());
2961
+ spec = spec.replace(/[?#].*$/, '');
2962
+ // Accept path-based boot/HMR prefixes:
2963
+ // /ns/m/__ns_boot__/b1/<real-spec>
2964
+ // /ns/m/__ns_hmr__/<tag>/<real-spec>
2965
+ // /ns/m/__ns_boot__/b1/__ns_hmr__/<tag>/<real-spec>
2966
+ // The iOS HTTP ESM loader canonicalizes cache keys by stripping query params,
2967
+ // so we must carry the cache-buster in the path.
2968
+ try {
2969
+ const decorated = stripDecoratedServePrefixes(spec);
2970
+ spec = decorated.cleanedSpec;
2971
+ bootTaggedRequest = decorated.bootTaggedRequest;
2972
+ forcedVer || (forcedVer = decorated.forcedVer);
2973
+ }
2974
+ catch { }
2975
+ // Normalize absolute filesystem paths back to project-relative ids (e.g. /src/app.ts)
2976
+ try {
2977
+ const toPosix = (p) => p.replace(/\\/g, '/');
2978
+ const rootPosix = toPosix(serverRoot);
2979
+ const specPosix = toPosix(spec);
2980
+ // If spec is an absolute path under the project root, convert to '/'+relative
2981
+ const isAbsFs = /^\//.test(specPosix) || /^[A-Za-z]:\//.test(spec); // posix or win drive
2982
+ if (isAbsFs) {
2983
+ let rel = specPosix.startsWith(rootPosix) ? specPosix.slice(rootPosix.length) : require('path').posix.relative(rootPosix, specPosix);
2984
+ if (!rel.startsWith('..')) {
2985
+ if (!rel.startsWith('/'))
2986
+ rel = '/' + rel;
2987
+ // Ensure leading '/src' style when path maps into src
2988
+ spec = rel;
2989
+ }
2990
+ }
2991
+ }
2992
+ catch { }
2835
2993
  // Serve Vite virtual modules (/@id/ prefix). These are internal
2836
2994
  // virtual modules (e.g., \0nsvite:nsconfig-json for ~/package.json)
2837
2995
  // that don't exist on disk. Decode the ID and load via plugin container.
2996
+ if (spec.startsWith('/@id/')) {
2997
+ try {
2998
+ // First try Vite's transform pipeline directly
2999
+ const vr = await sharedTransformRequest(spec);
3000
+ if (vr?.code) {
3001
+ res.statusCode = 200;
3002
+ res.end(vr.code);
3003
+ return;
3004
+ }
3005
+ }
3006
+ catch { }
3007
+ try {
3008
+ // Fallback: decode the virtual module ID (__x00__ → \0) and
3009
+ // load through the plugin container directly
3010
+ const rawId = spec.slice('/@id/'.length).replace(/__x00__/g, '\0');
3011
+ const loadResult = await server.pluginContainer.load(rawId);
3012
+ if (loadResult) {
3013
+ const code = typeof loadResult === 'string' ? loadResult : loadResult.code;
3014
+ if (code) {
3015
+ res.statusCode = 200;
3016
+ res.end(code);
3017
+ return;
3018
+ }
3019
+ }
3020
+ }
3021
+ catch { }
3022
+ }
3023
+ if (spec.startsWith('@/'))
3024
+ spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
3025
+ if (spec.startsWith('./'))
3026
+ spec = spec.slice(1);
3027
+ const blockedNodeModulesReason = getBlockedDeviceNodeModulesReason(spec);
3028
+ if (blockedNodeModulesReason) {
3029
+ res.statusCode = 404;
3030
+ res.end(`// [ns:m] blocked device import\nthrow new Error(${JSON.stringify(`[ns/m] ${blockedNodeModulesReason}`)});\nexport {};\n`);
3031
+ return;
3032
+ }
3033
+ if (!spec.startsWith('/'))
3034
+ spec = '/' + spec;
3035
+ const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
3036
+ const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
3037
+ const candidates = [...(hasExt ? [spec] : []), baseNoExt + '.ts', baseNoExt + '.js', baseNoExt + '.tsx', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', baseNoExt + '.vue', baseNoExt + '/index.ts', baseNoExt + '/index.js', baseNoExt + '/index.tsx', baseNoExt + '/index.jsx', baseNoExt + '/index.mjs'];
3038
+ const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, serverRoot);
2838
3039
  let transformed = null;
2839
3040
  let resolvedCandidate = null;
3041
+ const rawExplicitModule = tryReadRawExplicitJavaScriptModule(spec, serverRoot);
3042
+ if (rawExplicitModule) {
3043
+ transformed = { code: rawExplicitModule.code };
3044
+ resolvedCandidate = rawExplicitModule.resolvedId;
3045
+ }
2840
3046
  // Queue and dedupe transformRequest calls so heavy app graphs do not
2841
3047
  // overwhelm Vite with concurrent work. Slow-transform warnings start only
2842
3048
  // when the transform actually begins executing, and requests stay pending
@@ -2844,15 +3050,86 @@ function createHmrWebSocketPlugin(opts) {
2844
3050
  const transformWithTimeout = (url, timeoutMs = 120000) => {
2845
3051
  return sharedTransformRequest(url, timeoutMs);
2846
3052
  };
2847
- ({ transformed, resolvedCandidate } = await resolveNsMTransformedModule({
2848
- context: requestContextResult.value,
2849
- transformRequest: transformWithTimeout,
2850
- resolveId: async (id) => {
2851
- const resolved = await server.pluginContainer?.resolveId?.(id, undefined);
2852
- return typeof resolved === 'string' ? resolved : resolved?.id || null;
2853
- },
2854
- loadVirtualId: async (id) => await server.pluginContainer.load(id),
2855
- }));
3053
+ if (!transformed?.code) {
3054
+ for (const cand of transformCandidates) {
3055
+ try {
3056
+ const r = await transformWithTimeout(cand);
3057
+ if (r?.code) {
3058
+ transformed = r;
3059
+ resolvedCandidate = cand;
3060
+ break;
3061
+ }
3062
+ }
3063
+ catch { }
3064
+ }
3065
+ }
3066
+ // Fallback 1: ask Vite to resolve the id, then transform the resolved id (handles aliases and virtual ids)
3067
+ if (!transformed?.code) {
3068
+ try {
3069
+ const rid = await server.pluginContainer?.resolveId?.(spec, undefined);
3070
+ const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
3071
+ if (ridStr) {
3072
+ const r = await transformWithTimeout(ridStr);
3073
+ if (r?.code) {
3074
+ transformed = r;
3075
+ resolvedCandidate = ridStr;
3076
+ }
3077
+ }
3078
+ }
3079
+ catch { }
3080
+ }
3081
+ // Fallback 1b: if spec is a /node_modules/ path, extract bare specifier
3082
+ // and try resolveId with that. This handles package.json "exports" field
3083
+ // resolution (e.g., solid-js/jsx-runtime → solid-js/dist/solid.js).
3084
+ if (!transformed?.code && spec.includes('/node_modules/')) {
3085
+ try {
3086
+ const nmIdx = spec.lastIndexOf('/node_modules/');
3087
+ const bare = spec.slice(nmIdx + '/node_modules/'.length);
3088
+ if (bare && !bare.startsWith('.')) {
3089
+ const rid = await server.pluginContainer?.resolveId?.(bare, undefined);
3090
+ const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
3091
+ if (ridStr) {
3092
+ const r = await sharedTransformRequest(ridStr);
3093
+ if (r?.code) {
3094
+ transformed = r;
3095
+ resolvedCandidate = ridStr;
3096
+ }
3097
+ }
3098
+ }
3099
+ }
3100
+ catch { }
3101
+ }
3102
+ // Fallback 2: try /@fs absolute path under project root (Vite file system alias)
3103
+ if (!transformed?.code) {
3104
+ try {
3105
+ const toPosix = (p) => p.replace(/\\/g, '/');
3106
+ const rootPosix = toPosix(serverRoot).replace(/\/$/, '');
3107
+ const absPosix = `${rootPosix}${spec.startsWith('/') ? '' : '/'}${spec}`;
3108
+ const fsId = `/@fs${absPosix}`;
3109
+ if (resolveCandidateFilePath(fsId, serverRoot)) {
3110
+ const r = await transformWithTimeout(fsId);
3111
+ if (r?.code) {
3112
+ transformed = r;
3113
+ resolvedCandidate = fsId;
3114
+ }
3115
+ }
3116
+ }
3117
+ catch { }
3118
+ }
3119
+ // Fallback 3: try adding ?import to hint Vite's transform pipeline
3120
+ if (!transformed?.code) {
3121
+ for (const cand of transformCandidates) {
3122
+ try {
3123
+ const r = await transformWithTimeout(`${cand}${cand.includes('?') ? '&' : '?'}import`);
3124
+ if (r?.code) {
3125
+ transformed = r;
3126
+ resolvedCandidate = `${cand}?import`;
3127
+ break;
3128
+ }
3129
+ }
3130
+ catch { }
3131
+ }
3132
+ }
2856
3133
  // Solid HMR: patch @@solid-refresh's $$refreshESM to do inline patching
2857
3134
  // during module re-evaluation instead of deferring to hot.accept() callback.
2858
3135
  // In NativeScript's HTTP ESM environment, accept callbacks are registered
@@ -3055,47 +3332,183 @@ export const piniaSymbol = p.piniaSymbol;
3055
3332
  }
3056
3333
  }
3057
3334
  }
3335
+ let code = transformed.code;
3336
+ // Prepend guard to capture any URL-based require attempts
3337
+ code = REQUIRE_GUARD_SNIPPET + code;
3338
+ code = cleanCode(code);
3339
+ const isNodeMod = /(?:^|\/)node_modules\//.test(resolvedCandidate || spec || '');
3340
+ code = processCodeForDevice(code, false, true, isNodeMod, resolvedCandidate || spec);
3341
+ // Solid HMR: The NativeScript iOS/Android runtime provides import.meta.hot
3342
+ // natively (via InitializeImportMetaHot in HMRSupport.mm) with C++-backed
3343
+ // persistent hot.data that survives across module re-evaluations.
3344
+ // cleanCode() strips Vite's __vite__createHotContext assignment, which is
3345
+ // correct — the runtime's native hot context is better.
3058
3346
  const projectRoot = server.config?.root || process.cwd();
3059
3347
  const serverOrigin = getServerOrigin(server);
3060
- let code;
3348
+ if (ACTIVE_STRATEGY?.flavor === 'angular') {
3349
+ code = prepareAngularEntryForDevice(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
3350
+ }
3351
+ else {
3352
+ code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
3353
+ }
3354
+ // Expand `export * from "url"` into explicit named re-exports.
3355
+ // NativeScript's HTTP ESM loader may not propagate star-re-exports across
3356
+ // HTTP module boundaries (the namespace object gets direct exports but
3357
+ // misses re-exported names). By expanding to `export { a, b } from "url"`,
3358
+ // the engine sees explicit named exports and resolves them correctly.
3061
3359
  try {
3062
- code = await finalizeNsMServedModule({
3063
- code: transformed.code,
3064
- spec,
3065
- resolvedCandidate,
3066
- forcedVer,
3067
- bootTaggedRequest,
3068
- graphVersion: Number(graphVersion || 0),
3069
- serverOrigin,
3070
- strategy: ACTIVE_STRATEGY,
3071
- helpers: {
3072
- requireGuardSnippet: REQUIRE_GUARD_SNIPPET,
3073
- cleanCode,
3074
- processCodeForDevice: (value, sourceId) => processCodeForDevice(value, false, true, /(?:^|\/)node_modules\//.test(sourceId || spec || ''), sourceId || spec),
3075
- rewriteImports: (value, importerPath) => rewriteImports(value, importerPath, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true),
3076
- rewriteAngularEntry: (value, importerPath) => prepareAngularEntryForDevice(value, importerPath, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true),
3077
- expandStarExports: async (value) => expandStarExports(value, server, projectRoot, verbose),
3078
- dedupeRtNamedImportsAgainstDestructures,
3079
- ensureVariableDynamicImportHelper,
3080
- ensureGuardPlainDynamicImports,
3081
- deduplicateLinkerImports,
3082
- wrapCommonJsModuleForDevice,
3083
- assertNoOptimizedArtifacts,
3084
- ensureVersionedRtImports,
3085
- ensureDestructureCoreImports,
3086
- buildBootProgressSnippet,
3087
- hoistTopLevelStaticImports,
3088
- warn: (message, error) => {
3089
- if (verbose)
3090
- console.warn(`${message}:`, error?.message || error);
3091
- },
3092
- },
3093
- });
3360
+ code = await expandStarExports(code, server, server.config?.root || process.cwd(), verbose);
3361
+ }
3362
+ catch (e) {
3363
+ if (verbose)
3364
+ console.warn('[ns/m] export* expansion failed:', e?.message);
3365
+ }
3366
+ // Dedupe any /ns/rt named imports that duplicate destructured bindings off default /ns/rt
3367
+ try {
3368
+ code = dedupeRtNamedImportsAgainstDestructures(code);
3369
+ }
3370
+ catch { }
3371
+ code = ensureVariableDynamicImportHelper(code);
3372
+ // Final safety: guard any plain dynamic import(...) occurrences to reroute anomalous '@' specs
3373
+ try {
3374
+ code = ensureGuardPlainDynamicImports(code, getServerOrigin(server));
3375
+ }
3376
+ catch { }
3377
+ // Extra hardening: normalize any remaining core references to the unified bridge
3378
+ // - Stray string-literals
3379
+ // - Dangling `from` merges
3380
+ // - Any spec (including /node_modules resolves) that still references '@nativescript/core'
3381
+ // Do this right before the final fast-fail assertion. If a rewrite occurred, add a small marker for diagnostics.
3382
+ try {
3383
+ const __before = code;
3384
+ code = normalizeStrayCoreStringLiterals(code);
3385
+ code = fixDanglingCoreFrom(code);
3386
+ code = normalizeAnyCoreSpecToBridge(code);
3387
+ if (code !== __before) {
3388
+ code = `// [hmr-sanitize] core-literal->bridge\n` + code;
3389
+ }
3390
+ }
3391
+ catch { }
3392
+ // Final pass: deduplicate/resolve any bare-specifier imports that slipped
3393
+ // through the pipeline (e.g., extracted from JSDoc comments by import-splitting
3394
+ // regexes, or injected by the Angular linker on already-resolved code).
3395
+ try {
3396
+ code = deduplicateLinkerImports(code);
3397
+ }
3398
+ catch { }
3399
+ // CJS/UMD wrapping: if a module uses module.exports but has no ESM export default,
3400
+ // wrap it with CJS shims so the device HTTP ESM loader can consume it.
3401
+ // This handles npm packages that use CommonJS but aren't pre-bundled by Vite.
3402
+ //
3403
+ // Key constraints this must handle:
3404
+ // - CJS modules often declare local vars with the same names as their exports
3405
+ // (e.g. `function createLTTB() {...}; exports.createLTTB = createLTTB;`)
3406
+ // so `export var { createLTTB }` would cause a duplicate declaration.
3407
+ // - UMD modules reference `this` at top level (undefined in ESM) but
3408
+ // typically fall back to `self` or `globalThis`.
3409
+ // - `module`, `exports` must be shims since they don't exist in ESM.
3410
+ try {
3411
+ code = wrapCommonJsModuleForDevice(code);
3412
+ }
3413
+ catch { }
3414
+ try {
3415
+ assertNoOptimizedArtifacts(code, `NS M ${resolvedCandidate || spec}`);
3094
3416
  }
3095
3417
  catch (e) {
3096
3418
  res.statusCode = 500;
3097
3419
  return void res.end(`throw new Error(${JSON.stringify(e?.message || String(e))});\nexport {};`);
3098
3420
  }
3421
+ // Defensive export normalization: if a module defines `routes` and only exports it named,
3422
+ // add a default export alias so both `import { routes }` and `import routes` work.
3423
+ try {
3424
+ if (!/\bexport\s+default\b/.test(code)) {
3425
+ const hasNamedRoutes = /\bexport\s*\{\s*routes\s*\}/.test(code);
3426
+ const hasConstRoutes = /\bconst\s+routes\s*=/.test(code) || /\bvar\s+routes\s*=/.test(code) || /\blet\s+routes\s*=/.test(code);
3427
+ if (hasNamedRoutes && hasConstRoutes) {
3428
+ code += `\nexport default routes;\n`;
3429
+ }
3430
+ }
3431
+ }
3432
+ catch { }
3433
+ try {
3434
+ const verNum = getNumericServeVersionTag(forcedVer, Number(graphVersion || 0));
3435
+ code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
3436
+ code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
3437
+ code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
3438
+ }
3439
+ catch { }
3440
+ // Finalize: stamp all internal /ns/m imports with PATH-based cache busting.
3441
+ // IMPORTANT: use path prefix (not ?v= query) because the iOS HTTP ESM loader
3442
+ // strips query params when computing module cache keys, so ?v= doesn't bust the V8 cache.
3443
+ try {
3444
+ const ver = (() => {
3445
+ const raw = String(forcedVer || '').trim();
3446
+ const gv = Number(graphVersion || 0);
3447
+ // 'live' is the client dynamic-import helper's fallback when
3448
+ // globalThis.__NS_HMR_GRAPH_VERSION__ has not yet been set (i.e. before the
3449
+ // full HMR client takes over). If we propagate 'live' into child import
3450
+ // URLs, a file loaded through a 'live'-tagged parent ends up at
3451
+ // /ns/m/__ns_hmr__/live/... in the iOS V8 module cache while a later
3452
+ // dyn-import at v${N} loads the same file at /ns/m/__ns_hmr__/v${N}/...
3453
+ // — two distinct cache entries, two module realms, two Angular class
3454
+ // identities per @Component, and NG0912 selector collisions. Replace
3455
+ // 'live' with the current graph version so every child resolves to one
3456
+ // canonical URL regardless of the parent's request tag.
3457
+ if (raw === 'live' && gv > 0) {
3458
+ return `v${gv}`;
3459
+ }
3460
+ if (raw) {
3461
+ if (raw === 'live' || /^n\d+$/i.test(raw) || /^v[^/]+$/i.test(raw)) {
3462
+ return raw;
3463
+ }
3464
+ if (/^\d+$/.test(raw)) {
3465
+ return `v${raw}`;
3466
+ }
3467
+ }
3468
+ return `v${String(graphVersion || 0)}`;
3469
+ })();
3470
+ const origin = getServerOrigin(server);
3471
+ const rewritePath = (p) => rewriteNsMImportPathForHmr(p, ver, bootTaggedRequest);
3472
+ // 1) Static imports: import ... from "/ns/m/..."
3473
+ code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3474
+ // 2) Side-effect imports: import "/ns/m/..."
3475
+ code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3476
+ // 3) Dynamic imports: import("/ns/m/...")
3477
+ code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3478
+ // 4) new URL("/ns/m/...", import.meta.url)
3479
+ code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3480
+ // 5) __ns_import(new URL('/ns/m/...', import.meta.url).href)
3481
+ code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\)\.href)/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3482
+ // 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href → "${origin}/ns/m/__ns_hmr__/..."
3483
+ try {
3484
+ code = code.replace(/new\s+URL\(\s*["'](\/ns\/m\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}${rewritePath(p1)}`)}`);
3485
+ }
3486
+ catch { }
3487
+ // 7) Also fix SFC new URL('/ns/sfc/...', import.meta.url).href → "${origin}/ns/sfc/<ver>/..."
3488
+ try {
3489
+ code = code.replace(/new\s+URL\(\s*["']\/ns\/sfc(\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}/ns/sfc/${ver}${p1}`)}`);
3490
+ }
3491
+ catch { }
3492
+ }
3493
+ catch { }
3494
+ // Final guard: eliminate any lingering named imports from /ns/core to avoid
3495
+ // evaluation-time "does not provide an export named ..." in the device runtime.
3496
+ try {
3497
+ code = ensureDestructureCoreImports(code);
3498
+ }
3499
+ catch { }
3500
+ // Boot-time module graph progress: while the app is still replacing the
3501
+ // placeholder, emit lightweight progress updates as /ns/m modules begin
3502
+ // evaluating. This keeps the overlay moving during large initial graphs.
3503
+ try {
3504
+ if (bootTaggedRequest) {
3505
+ const bootModuleLabel = String(spec || '').replace(/\\/g, '/');
3506
+ const bootProgressSnippet = buildBootProgressSnippet(bootModuleLabel);
3507
+ code = bootProgressSnippet + code;
3508
+ code = hoistTopLevelStaticImports(code);
3509
+ }
3510
+ }
3511
+ catch { }
3099
3512
  // Dev-only: link-check static imports to surface missing bindings early
3100
3513
  try {
3101
3514
  const devCheck = process.env.NODE_ENV !== 'production';
@@ -3224,23 +3637,231 @@ export const piniaSymbol = p.piniaSymbol;
3224
3637
  res.end('export {}\n');
3225
3638
  }
3226
3639
  });
3227
- registerRuntimeCompatHandlers(server, {
3228
- verbose,
3229
- requireGuardSnippet: REQUIRE_GUARD_SNIPPET,
3230
- appRootDir: APP_ROOT_DIR,
3231
- defaultMainEntry: DEFAULT_MAIN_ENTRY,
3232
- defaultMainEntryVirtual: DEFAULT_MAIN_ENTRY_VIRTUAL,
3233
- getGraphVersion: () => Number(graphVersion || 0),
3234
- getServerOrigin,
3640
+ // 2.5) ESM runtime bridge for NativeScript-Vue: GET /ns/rt
3641
+ // Provides a single authoritative source of Vue helpers bound to the NativeScript renderer.
3642
+ // V2.1: Lazy ensure bridge — does not statically import vue. It lazily resolves helpers from
3643
+ // globalThis or vendor registry/require on first evaluation, then exports references so SFCs
3644
+ // can immediately call them during module evaluation.
3645
+ server.middlewares.use(async (req, res, next) => {
3646
+ try {
3647
+ const urlObj = new URL(req.url || '', 'http://localhost');
3648
+ // Accept only /ns/rt and /ns/rt/<ver> for cache-busting semantics
3649
+ if (!(urlObj.pathname === '/ns/rt' || /^\/ns\/rt\/[\d]+$/.test(urlObj.pathname)))
3650
+ return next();
3651
+ res.setHeader('Access-Control-Allow-Origin', '*');
3652
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
3653
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3654
+ res.setHeader('Pragma', 'no-cache');
3655
+ res.setHeader('Expires', '0');
3656
+ const rtVerSeg = urlObj.pathname.replace(/^\/ns\/rt\/?/, '');
3657
+ const rtVer = /^[0-9]+$/.test(rtVerSeg) ? rtVerSeg : String(graphVersion || 0);
3658
+ const origin = getServerOrigin(server);
3659
+ let code = `// [ns-rt][v2.3] NativeScript-Vue runtime bridge (module-scoped cache, no globals)\n` +
3660
+ `const __origin = ((typeof globalThis !== 'undefined' && globalThis && globalThis.__NS_HTTP_ORIGIN__) || (new URL(import.meta.url)).origin);\n` +
3661
+ `let __ns_core_bridge = null; try { import(__origin + "/ns/core/${rtVer}").then(m => { __ns_core_bridge = m; }).catch(() => {}); } catch {}\n` +
3662
+ `const g = globalThis;\n` +
3663
+ `const reg = (g.__nsVendorRegistry ||= new Map());\n` +
3664
+ `const req = reg && reg.get ? (g.__nsVendorRequire || g.__nsRequire || g.require) : (g.__nsRequire || g.require);\n` +
3665
+ `let __cached_rt = null;\n` +
3666
+ `let __cached_vm = null;\n` +
3667
+ `const __RT_REALM_TAG = (globalThis.__NS_RT_REALM__ ||= Math.random().toString(36).slice(2));\n` +
3668
+ // Unconditional one-shot evaluation marker to confirm bridge is executed on device
3669
+ `try { if (!(globalThis.__NS_RT_ONCE__ && globalThis.__NS_RT_ONCE__.eval)) { (globalThis.__NS_RT_ONCE__ ||= {}).eval = true; console.log('[ns-rt] evaluated', { rtRealm: __RT_REALM_TAG }); } } catch {}\n` +
3670
+ `function __ensure(){\n` +
3671
+ ` if (__cached_rt) return __cached_rt;\n` +
3672
+ ` let vm = null;\n` +
3673
+ ` try { vm = reg && reg.has && reg.has('nativescript-vue') ? reg.get('nativescript-vue') : (typeof req==='function' ? req('nativescript-vue') : null); } catch {}\n` +
3674
+ ` if (!vm) { try { vm = reg && reg.has && reg.has('vue') ? reg.get('vue') : (typeof req==='function' ? req('vue') : null); } catch {} }\n` +
3675
+ ` const rt = (vm && (vm.default ?? vm)) || {};\n` +
3676
+ ` __cached_vm = vm;\n` +
3677
+ ` __cached_rt = rt;\n` +
3678
+ ` return rt;\n` +
3679
+ `}\n` +
3680
+ `// Soft-globals for @nativescript/core when missing (dev-only safety)\n` +
3681
+ `try {\n` +
3682
+ ` const dev = typeof __DEV__ !== 'undefined' ? __DEV__ : true;\n` +
3683
+ ` if (dev) {\n` +
3684
+ ` const ns = (__ns_core_bridge && (__ns_core_bridge.__esModule && __ns_core_bridge.default ? __ns_core_bridge.default : (__ns_core_bridge.default || __ns_core_bridge))) || __ns_core_bridge || {};\n` +
3685
+ ` if (ns) {\n` +
3686
+ ` if (!g.Frame && ns.Frame) g.Frame = ns.Frame;\n` +
3687
+ ` if (!g.Page && ns.Page) g.Page = ns.Page;\n` +
3688
+ ` if (!g.Application && (ns.Application||ns.app||ns.application)) g.Application = (ns.Application||ns.app||ns.application);\n` +
3689
+ ` }\n` +
3690
+ ` }\n` +
3691
+ `} catch {}\n` +
3692
+ `const __get = (k) => { const rt = __ensure(); const v = rt && rt[k]; if (typeof v !== 'function' && v === undefined) { throw new Error('[ns-rt] missing export '+k); } return v; };\n` +
3693
+ `export const __realm = __RT_REALM_TAG;\n` +
3694
+ `export const defineComponent = (...a) => (__get('defineComponent'))(...a);\n` +
3695
+ `export const resolveComponent = (...a) => (__ensure().resolveComponent)(...a);\n` +
3696
+ `export const createVNode = (...a) => (__ensure().createVNode)(...a);\n` +
3697
+ `export const createTextVNode = (...a) => (__ensure().createTextVNode)(...a);\n` +
3698
+ `export const createCommentVNode = (...a) => (__ensure().createCommentVNode)(...a);\n` +
3699
+ `export const Fragment = (__ensure().Fragment);\n` +
3700
+ `export const Teleport = (__ensure().Teleport);\n` +
3701
+ `export const Transition = (__ensure().Transition);\n` +
3702
+ `export const TransitionGroup = (__ensure().TransitionGroup);\n` +
3703
+ `export const KeepAlive = (__ensure().KeepAlive);\n` +
3704
+ `export const Suspense = (__ensure().Suspense);\n` +
3705
+ `export const withCtx = (...a) => (__ensure().withCtx)(...a);\n` +
3706
+ `export const openBlock = (...a) => (__ensure().openBlock)(...a);\n` +
3707
+ `export const createBlock = (...a) => (__ensure().createBlock)(...a);\n` +
3708
+ `export const createElementVNode = (...a) => (__ensure().createElementVNode)(...a);\n` +
3709
+ `export const createElementBlock = (...a) => (__ensure().createElementBlock)(...a);\n` +
3710
+ `export const renderSlot = (...a) => (__ensure().renderSlot)(...a);\n` +
3711
+ `export const mergeProps = (...a) => (__ensure().mergeProps)(...a);\n` +
3712
+ `export const toHandlers = (...a) => (__ensure().toHandlers)(...a);\n` +
3713
+ `export const renderList = (...a) => (__ensure().renderList)(...a);\n` +
3714
+ `export const normalizeProps = (...a) => (__ensure().normalizeProps)(...a);\n` +
3715
+ `export const guardReactiveProps = (...a) => (__ensure().guardReactiveProps)(...a);\n` +
3716
+ `export const normalizeClass = (...a) => (__ensure().normalizeClass)(...a);\n` +
3717
+ `export const normalizeStyle = (...a) => (__ensure().normalizeStyle)(...a);\n` +
3718
+ `export const toDisplayString = (...a) => (__ensure().toDisplayString)(...a);\n` +
3719
+ `export const withDirectives = (...a) => (__ensure().withDirectives)(...a);\n` +
3720
+ `export const resolveDirective = (...a) => (__ensure().resolveDirective)(...a);\n` +
3721
+ `export const withModifiers = (...a) => (__ensure().withModifiers)(...a);\n` +
3722
+ `export const withKeys = (...a) => (__ensure().withKeys)(...a);\n` +
3723
+ `export const resolveDynamicComponent = (...a) => (__ensure().resolveDynamicComponent)(...a);\n` +
3724
+ `export const isVNode = (...a) => (__ensure().isVNode)(...a);\n` +
3725
+ `export const cloneVNode = (...a) => (__ensure().cloneVNode)(...a);\n` +
3726
+ `export const isRef = (...a) => (__ensure().isRef)(...a);\n` +
3727
+ `export const ref = (...a) => (__ensure().ref)(...a);\n` +
3728
+ `export const shallowRef = (...a) => (__ensure().shallowRef)(...a);\n` +
3729
+ `export const unref = (...a) => (__ensure().unref)(...a);\n` +
3730
+ `export const computed = (...a) => (__ensure().computed)(...a);\n` +
3731
+ `export const reactive = (...a) => (__ensure().reactive)(...a);\n` +
3732
+ `export const readonly = (...a) => (__ensure().readonly)(...a);\n` +
3733
+ `export const isReactive = (...a) => (__ensure().isReactive)(...a);\n` +
3734
+ `export const isReadonly = (...a) => (__ensure().isReadonly)(...a);\n` +
3735
+ `export const toRaw = (...a) => (__ensure().toRaw)(...a);\n` +
3736
+ `export const markRaw = (...a) => (__ensure().markRaw)(...a);\n` +
3737
+ `export const shallowReactive = (...a) => (__ensure().shallowReactive)(...a);\n` +
3738
+ `export const shallowReadonly = (...a) => (__ensure().shallowReadonly)(...a);\n` +
3739
+ `export const watch = (...a) => (__ensure().watch)(...a);\n` +
3740
+ `export const watchEffect = (...a) => (__ensure().watchEffect)(...a);\n` +
3741
+ `export const watchPostEffect = (...a) => (__ensure().watchPostEffect)(...a);\n` +
3742
+ `export const watchSyncEffect = (...a) => (__ensure().watchSyncEffect)(...a);\n` +
3743
+ `export const onBeforeMount = (...a) => (__ensure().onBeforeMount)(...a);\n` +
3744
+ `export const onMounted = (...a) => (__ensure().onMounted)(...a);\n` +
3745
+ `export const onBeforeUpdate = (...a) => (__ensure().onBeforeUpdate)(...a);\n` +
3746
+ `export const onUpdated = (...a) => (__ensure().onUpdated)(...a);\n` +
3747
+ `export const onBeforeUnmount = (...a) => (__ensure().onBeforeUnmount)(...a);\n` +
3748
+ `export const onUnmounted = (...a) => (__ensure().onUnmounted)(...a);\n` +
3749
+ `export const onActivated = (...a) => (__ensure().onActivated)(...a);\n` +
3750
+ `export const onDeactivated = (...a) => (__ensure().onDeactivated)(...a);\n` +
3751
+ `export const onErrorCaptured = (...a) => (__ensure().onErrorCaptured)(...a);\n` +
3752
+ `export const onRenderTracked = (...a) => (__ensure().onRenderTracked)(...a);\n` +
3753
+ `export const onRenderTriggered = (...a) => (__ensure().onRenderTriggered)(...a);\n` +
3754
+ `export const nextTick = (...a) => (__ensure().nextTick)(...a);\n` +
3755
+ `export const h = (...a) => (__ensure().h)(...a);\n` +
3756
+ `export const provide = (...a) => (__ensure().provide)(...a);\n` +
3757
+ `export const inject = (...a) => (__ensure().inject)(...a);\n` +
3758
+ `export const vShow = (__ensure().vShow);\n` +
3759
+ `export const createApp = (...a) => (__ensure().createApp)(...a);\n` +
3760
+ `export const registerElement = (...a) => (__ensure().registerElement)(...a);\n` +
3761
+ `export const $navigateTo = (...a) => { const vm = (__cached_vm || (void __ensure(), __cached_vm)); const rt = __ensure(); try { if (!(g && g.Frame)) { const ns = (__ns_core_bridge && (__ns_core_bridge.__esModule && __ns_core_bridge.default ? __ns_core_bridge.default : (__ns_core_bridge.default || __ns_core_bridge))) || __ns_core_bridge || {}; if (ns) { if (!g.Frame && ns.Frame) g.Frame = ns.Frame; if (!g.Page && ns.Page) g.Page = ns.Page; if (!g.Application && (ns.Application||ns.app||ns.application)) g.Application = (ns.Application||ns.app||ns.application); } } } catch {} try { const hmrRealm = (g && g.__NS_HMR_REALM__) || 'unknown'; const hasTop = !!(g && g.Frame && g.Frame.topmost && g.Frame.topmost()); const top = hasTop ? g.Frame.topmost() : null; const ctor = top && top.constructor && top.constructor.name; } catch {} if (g && typeof g.__nsNavigateUsingApp === 'function') { try { return g.__nsNavigateUsingApp(...a); } catch (e) { try { console.error('[ns-rt] $navigateTo app navigator error', e); } catch {} throw e; } } try { console.error('[ns-rt] $navigateTo unavailable: app navigator missing'); } catch {} throw new Error('$navigateTo unavailable: app navigator missing'); } ;\n` +
3762
+ `export const $navigateBack = (...a) => { const vm = (__cached_vm || (void __ensure(), __cached_vm)); const rt = __ensure(); const impl = (vm && (vm.$navigateBack || (vm.default && vm.default.$navigateBack))) || (rt && (rt.$navigateBack || (rt.runtimeHelpers && rt.runtimeHelpers.navigateBack))); let res; try { const via = (impl && (impl === (vm && vm.$navigateBack) || impl === (vm && vm.default && vm.default.$navigateBack))) ? 'vm' : (impl ? 'rt' : 'none'); } catch {} try { if (typeof impl === 'function') res = impl(...a); } catch {} try { const top = (g && g.Frame && g.Frame.topmost && g.Frame.topmost()); if (!res && top && top.canGoBack && top.canGoBack()) { res = top.goBack(); } } catch {} try { const hook = g && (g.__NS_HMR_ON_NAVIGATE_BACK || g.__NS_HMR_ON_BACK || g.__nsAttemptBackRemount); if (typeof hook === 'function') hook(); } catch {} return res; }\n` +
3763
+ `export const $showModal = (...a) => { const vm = (__cached_vm || (void __ensure(), __cached_vm)); const rt = __ensure(); const impl = (vm && (vm.$showModal || (vm.default && vm.default.$showModal))) || (rt && (rt.$showModal || (rt.runtimeHelpers && rt.runtimeHelpers.showModal))); try { if (typeof impl === 'function') return impl(...a); } catch (e) { } return undefined; }\n` +
3764
+ `export default {\n` +
3765
+ ` defineComponent, resolveComponent, createVNode, createTextVNode, createCommentVNode,\n` +
3766
+ ` Fragment, Teleport, Transition, TransitionGroup, KeepAlive, Suspense, withCtx, openBlock,\n` +
3767
+ ` createBlock, createElementVNode, createElementBlock, renderSlot, mergeProps, toHandlers,\n` +
3768
+ ` renderList, normalizeProps, guardReactiveProps, normalizeClass, normalizeStyle, toDisplayString,\n` +
3769
+ ` withDirectives, resolveDirective, withModifiers, withKeys, resolveDynamicComponent,\n` +
3770
+ ` isVNode, cloneVNode, isRef, ref, shallowRef, unref, computed, reactive, readonly, isReactive, isReadonly, toRaw, markRaw, shallowReactive, shallowReadonly,\n` +
3771
+ ` watch, watchEffect, watchPostEffect, watchSyncEffect, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,\n` +
3772
+ ` onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured, onRenderTracked, onRenderTriggered, nextTick, h, provide, inject, vShow, createApp, registerElement,\n` +
3773
+ ` $navigateTo, $navigateBack, $showModal\n` +
3774
+ `};\n`;
3775
+ // Prepend guard and ship (harmless, keeps diagnostics consistent)
3776
+ code = REQUIRE_GUARD_SNIPPET + code;
3777
+ res.statusCode = 200;
3778
+ res.end(code);
3779
+ }
3780
+ catch (e) {
3781
+ res.statusCode = 500;
3782
+ res.end('export {}\n');
3783
+ }
3235
3784
  });
3236
3785
  // 2.55) Dev-only vendor import unifier: rewrite 'vue'/'nativescript-vue' to /ns/rt/<ver>
3237
3786
  // This ensures plugins and app share a single Vue/NativeScript-Vue instance/realm.
3238
- registerVendorUnifierHandler(server, {
3239
- getGraphVersion: () => Number(graphVersion || 0),
3240
- getServerOrigin,
3241
- getStrategy: () => ACTIVE_STRATEGY,
3787
+ server.middlewares.use(async (req, res, next) => {
3788
+ try {
3789
+ const urlObj = new URL(req.url || '', 'http://localhost');
3790
+ const p = urlObj.pathname || '';
3791
+ // Ignore our own core/rt bridge endpoints and non-JS assets, but DO allow /ns/m/* through
3792
+ if (/^\/ns\/(?:rt|core)(?:\/|$)/.test(p))
3793
+ return next();
3794
+ if (!/(\.m?js$|\.ts$|\/node_modules\/|\/\.vite\/deps\/|^\/@id\/|^\/@fs\/)/.test(p))
3795
+ return next();
3796
+ if (/\.css($|\?)/.test(p))
3797
+ return next();
3798
+ const reqUrl = req.url || '';
3799
+ const transformed = await server.transformRequest(reqUrl);
3800
+ if (!transformed?.code)
3801
+ return next();
3802
+ const origin = getServerOrigin(server);
3803
+ const ver = Number(graphVersion || 0);
3804
+ const rewrite = ACTIVE_STRATEGY.rewriteVendorSpec;
3805
+ if (!rewrite)
3806
+ return next();
3807
+ const before = transformed.code;
3808
+ const code = rewrite(before, origin, ver);
3809
+ if (code === before)
3810
+ return next();
3811
+ res.setHeader('Access-Control-Allow-Origin', '*');
3812
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
3813
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3814
+ res.setHeader('Pragma', 'no-cache');
3815
+ res.setHeader('Expires', '0');
3816
+ res.statusCode = 200;
3817
+ res.end(code);
3818
+ }
3819
+ catch {
3820
+ return next();
3821
+ }
3822
+ });
3823
+ // 2.5.1) Catch-all redirect for stray /node_modules/@nativescript/core/*
3824
+ // requests — route them to the /ns/core bridge so they get the same
3825
+ // __DEV__/__IOS__ preamble and specifier rewriting. Without this,
3826
+ // Vite's default /node_modules/ handler serves the raw file, which
3827
+ // references bare __DEV__ and crashes at module eval.
3828
+ server.middlewares.use((req, _res, next) => {
3829
+ try {
3830
+ const urlObj = new URL(req.url || '', 'http://localhost');
3831
+ const coreNmPrefix = '/node_modules/@nativescript/core';
3832
+ if (!urlObj.pathname.startsWith(coreNmPrefix))
3833
+ return next();
3834
+ const sub = urlObj.pathname.slice(coreNmPrefix.length).replace(/^\/+/, '');
3835
+ if (sub === '' || sub === 'index.js' || sub === 'index') {
3836
+ req.url = `/ns/core`;
3837
+ }
3838
+ else {
3839
+ req.url = `/ns/core/${sub}`;
3840
+ }
3841
+ return next();
3842
+ }
3843
+ catch {
3844
+ return next();
3845
+ }
3242
3846
  });
3243
3847
  // 2.6) ESM bridge for @nativescript/core: GET /ns/core[/<ver>][?p=sub/path]
3848
+ //
3849
+ // Since bundle.mjs no longer bundles @nativescript/core (see
3850
+ // HMR_CORE_REALM_DETERMINISTIC_PLAN.md — external in the rolldown
3851
+ // config under HMR), this endpoint is the ONE place core is
3852
+ // evaluated. Every consumer — bundle.mjs's own `@nativescript/core*`
3853
+ // imports (resolved to full HTTP URLs in the entry virtual module),
3854
+ // externalized vendor packages, HTTP-served app modules — all end
3855
+ // up here. No more proxy bridge, no enumeration, no namespace
3856
+ // detection, no prototype-polluted maps. We just serve Vite's
3857
+ // authoritative transformed module content.
3858
+ //
3859
+ // iOS caches by URL path, so each unique URL is evaluated exactly
3860
+ // once per app lifetime. Every class identity is shared, every
3861
+ // `register()` side effect runs once, every `Application` reference
3862
+ // is the same iosApp singleton. The entire class of "does not
3863
+ // provide an export named X" and "Cannot redefine property" errors
3864
+ // is eliminated by construction.
3244
3865
  server.middlewares.use(async (req, res, next) => {
3245
3866
  try {
3246
3867
  const urlObj = new URL(req.url || '', 'http://localhost');
@@ -3252,84 +3873,338 @@ export const piniaSymbol = p.piniaSymbol;
3252
3873
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3253
3874
  res.setHeader('Pragma', 'no-cache');
3254
3875
  res.setHeader('Expires', '0');
3255
- const { hasExplicitVersion, key, normalizedSub, sub, ver } = coreRequest;
3256
- // Any @nativescript/core subpath import (including shallow ones like
3257
- // `utils`) may expose exports that are not available from the root
3258
- // vendor bundle namespace. Serve the actual transformed module content
3259
- // instead of the lightweight proxy bridge.
3876
+ const { normalizedSub, sub, ver } = coreRequest;
3877
+ const resolveModuleId = async (moduleId) => {
3878
+ const resolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
3879
+ return typeof resolved === 'string' ? resolved : resolved?.id || null;
3880
+ };
3881
+ let modulePath = null;
3260
3882
  if (sub) {
3261
- try {
3262
- const resolvedSubpath = normalizedSub || sub;
3263
- const projectRoot = server.config?.root || process.cwd();
3264
- const resolveModuleId = async (moduleId) => {
3265
- const resolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
3266
- return typeof resolved === 'string' ? resolved : resolved?.id || null;
3267
- };
3268
- const resolvedId = await resolveRuntimeCoreModulePath(resolvedSubpath, resolveModuleId);
3269
- const modulePath = resolvedId || `/node_modules/@nativescript/core/${resolvedSubpath}`;
3270
- const transformed = await sharedTransformRequest(modulePath);
3271
- if (!hasExplicitVersion) {
3272
- if (transformed?.code) {
3273
- const expandedModuleCode = await expandStarExports(transformed.code, server, projectRoot, verbose);
3274
- res.statusCode = 200;
3275
- res.end(buildVersionedCoreSubpathAliasModule(resolvedSubpath, ver, extractExportedNames(expandedModuleCode), hasModuleDefaultExport(expandedModuleCode)));
3276
- return;
3277
- }
3278
- res.statusCode = 200;
3279
- res.end(buildVersionedCoreSubpathAliasModule(resolvedSubpath, ver));
3280
- return;
3281
- }
3282
- if (transformed?.code) {
3283
- // Minimal pipeline: Vite already produces correct ESM.
3284
- // ONLY rewrite specifier strings to device-fetchable URLs.
3285
- // Do NOT run processCodeForDevice, rewriteImports, or any
3286
- // other heavy transform — those mangle newlines, eat exports,
3287
- // and cause cascading "does not provide an export" failures.
3288
- const moduleCode = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
3289
- res.statusCode = 200;
3290
- res.end(moduleCode);
3291
- return;
3292
- }
3293
- }
3294
- catch (e) {
3295
- try {
3296
- console.warn('[ns-core-bridge] deep subpath serve failed:', sub, e?.message);
3297
- }
3298
- catch { }
3883
+ const resolvedSubpath = normalizedSub || sub;
3884
+ modulePath = await resolveRuntimeCoreModulePath(resolvedSubpath, resolveModuleId);
3885
+ if (!modulePath) {
3886
+ modulePath = `/node_modules/@nativescript/core/${resolvedSubpath}`;
3299
3887
  }
3300
3888
  }
3301
- // Main entry or shallow subpath: use proxy bridge
3302
- let code = buildVersionedCoreMainBridgeModule(key, ver);
3303
- if (!sub) {
3304
- try {
3305
- const projectRoot = server.config?.root || process.cwd();
3306
- const coreSpecifier = '@nativescript/core';
3307
- const resolved = await server.pluginContainer?.resolveId?.(coreSpecifier, undefined);
3308
- const resolvedId = typeof resolved === 'string' ? resolved : resolved?.id || null;
3309
- const modulePath = resolvedId || '/node_modules/@nativescript/core/index.js';
3310
- const staticExportNames = collectStaticExportNamesFromFile(modulePath);
3311
- const staticExportOrigins = await normalizeCoreExportOriginsForRuntime(collectStaticExportOriginsFromFile(modulePath), async (moduleId) => {
3312
- const nextResolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
3313
- return typeof nextResolved === 'string' ? nextResolved : nextResolved?.id || null;
3314
- }, modulePath);
3315
- if (staticExportNames.length) {
3316
- code = buildVersionedCoreMainBridgeModule(key, ver, staticExportNames, staticExportOrigins);
3317
- }
3318
- else {
3319
- const transformed = await sharedTransformRequest(modulePath);
3320
- if (transformed?.code) {
3321
- const expandedModuleCode = await expandStarExports(transformed.code, server, projectRoot, verbose);
3322
- code = buildVersionedCoreMainBridgeModule(key, ver, extractExportedNames(expandedModuleCode));
3323
- }
3324
- }
3325
- }
3326
- catch (e) {
3327
- try {
3328
- console.warn('[ns-core-bridge] main bridge export discovery failed:', e?.message);
3329
- }
3330
- catch { }
3331
- }
3889
+ else {
3890
+ modulePath = (await resolveModuleId('@nativescript/core')) || '/node_modules/@nativescript/core/index.js';
3891
+ }
3892
+ const transformed = await sharedTransformRequest(modulePath);
3893
+ if (!transformed?.code) {
3894
+ res.statusCode = 500;
3895
+ res.setHeader('Content-Type', 'application/json');
3896
+ res.end(JSON.stringify({ error: 'core-transform-failed', modulePath, sub: sub || null }));
3897
+ return;
3898
+ }
3899
+ // Vite's transform output references module IDs with /@fs,
3900
+ // relative specifiers, or absolute project paths. Rewrite
3901
+ // those to URLs iOS can fetch over HTTP.
3902
+ let rewritten = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
3903
+ // Invariant D (CJS/ESM interop shape) — EXPORT-SIDE fix.
3904
+ //
3905
+ // `@nativescript/core/index.js` declares namespace
3906
+ // re-exports like:
3907
+ // export * as Utils from './utils';
3908
+ // The ES spec says these produce Module Namespace Objects
3909
+ // with [[Prototype]] = null. Consumers that reach them
3910
+ // via direct ESM import — `import { Utils } from
3911
+ // '@nativescript/core'` — get the raw null-proto value,
3912
+ // bypassing any CJS `require` shim we install. Most
3913
+ // consumers tolerate this, but CJS-style interop (most
3914
+ // notably zone.js's `patchMethod`) calls
3915
+ // `hasOwnProperty` on the target and crashes on
3916
+ // null-proto.
3917
+ //
3918
+ // We rewrite the re-export to a shape-wrapped const:
3919
+ // import * as __ns_re_Utils__ from './utils';
3920
+ // export const Utils = __NS_CJS_SHAPE__(__ns_re_Utils__);
3921
+ // so the EXPORT itself is a plain object — visible to
3922
+ // both ESM and CJS consumers consistently.
3923
+ //
3924
+ // We only pay the rewrite cost when the module actually
3925
+ // contains namespace re-exports (i.e., the main
3926
+ // `index.js`). Subpaths (`/utils`, `/http`, …) don't
3927
+ // re-export via `export * as`; they expose named
3928
+ // exports directly, so the rewrite is a no-op on them.
3929
+ if (hasNamespaceReExport(rewritten)) {
3930
+ rewritten = rewriteNamespaceReExportsForShape(rewritten);
3931
+ }
3932
+ // Prepend the build-time defines (__DEV__, __IOS__, __ANDROID__,
3933
+ // __APPLE__, …) that @nativescript/core source references directly.
3934
+ // Vite's `define` config substitutes these in user-code transforms but
3935
+ // skips node_modules by default; since core is now external and served
3936
+ // over HTTP from this endpoint, the served transformed code still has
3937
+ // bare identifiers like `if (__DEV__) …`. Without these consts, V8
3938
+ // hits `ReferenceError: __DEV__ is not defined` at module eval because
3939
+ // globalThis.__DEV__ is set by bundle.mjs's body AFTER all static
3940
+ // imports (including these core modules) have resolved.
3941
+ //
3942
+ // We inject LITERAL boolean values based on CLI flags + dev-server
3943
+ // mode rather than reading from globalThis, so the defines are
3944
+ // resolved even before bundle.mjs's body runs.
3945
+ const __cliFlags = getCliFlags() || {};
3946
+ const __platformIsAndroid = !!__cliFlags.android;
3947
+ const __platformIsVisionOS = !!__cliFlags.visionos;
3948
+ const __platformIsIOS = !__platformIsAndroid && !__platformIsVisionOS;
3949
+ const preamble = [
3950
+ `const __ANDROID__ = ${__platformIsAndroid ? 'true' : 'false'};`,
3951
+ `const __IOS__ = ${__platformIsIOS ? 'true' : 'false'};`,
3952
+ `const __VISIONOS__ = ${__platformIsVisionOS ? 'true' : 'false'};`,
3953
+ `const __APPLE__ = __IOS__ || __VISIONOS__;`,
3954
+ `const __DEV__ = ${server.config?.mode === 'development' ? 'true' : 'false'};`,
3955
+ `const __COMMONJS__ = false;`,
3956
+ `const __NS_WEBPACK__ = false;`,
3957
+ `const __NS_ENV_VERBOSE__ = globalThis.__NS_ENV_VERBOSE__ !== undefined ? !!globalThis.__NS_ENV_VERBOSE__ : false;`,
3958
+ `const __CSS_PARSER__ = 'css-tree';`,
3959
+ `const __UI_USE_XML_PARSER__ = true;`,
3960
+ `const __UI_USE_EXTERNAL_RENDERER__ = false;`,
3961
+ `const __TEST__ = false;`,
3962
+ ].join('\n');
3963
+ // Boot-time instrumentation + module self-registration.
3964
+ // See HMR_CORE_REALM_DETERMINISTIC_PLAN.md:
3965
+ // - Invariant A (URL canonicalization): the same
3966
+ // logical module must always resolve to byte-
3967
+ // identical URLs across every emitter. The /ns/core
3968
+ // handler records the first URL seen for each
3969
+ // canonical sub (or '' for main) in
3970
+ // `globalThis.__NS_CORE_FIRST_URL__` and fails hard
3971
+ // on mismatch so drift in any emitter surfaces
3972
+ // immediately, before the realm splits.
3973
+ // - Invariant C (boot-order): CommonJS
3974
+ // `require('@nativescript/core/...')` calls from
3975
+ // vendor install() hooks must resolve to the SAME
3976
+ // ESM namespace that ran this side-effect preamble.
3977
+ // The registration below keys the namespace object
3978
+ // under BOTH the bare specifier and the canonical
3979
+ // subpath (and raw subpath for back-compat) so the
3980
+ // vendor shim's `createRequire` and the main-entry
3981
+ // `_nsReq` hit on any lookup form.
3982
+ const rawSub = normalizedSub || sub || '';
3983
+ const canonicalSub = normalizeCoreSubCanonical(rawSub);
3984
+ const registrationKeySet = new Set();
3985
+ registrationKeySet.add(canonicalSub ? `@nativescript/core/${canonicalSub}` : '@nativescript/core');
3986
+ registrationKeySet.add(canonicalSub);
3987
+ if (rawSub && rawSub !== canonicalSub) {
3988
+ registrationKeySet.add(`@nativescript/core/${rawSub}`);
3989
+ registrationKeySet.add(rawSub);
3332
3990
  }
3991
+ const registrationKeys = Array.from(registrationKeySet).map((k) => JSON.stringify(k));
3992
+ const canonicalUrl = `${getServerOrigin(server)}` + (canonicalSub ? `/ns/core/${canonicalSub}` : '/ns/core');
3993
+ const instrumentationHeader = [
3994
+ `/* @nativescript/core bridge — canonical URL: ${canonicalUrl} */`,
3995
+ `try { if (typeof globalThis !== 'undefined') {`,
3996
+ ` const __nsFirst = globalThis.__NS_CORE_FIRST_URL__ || (globalThis.__NS_CORE_FIRST_URL__ = Object.create(null));`,
3997
+ ` const __nsSeen = globalThis.__NS_CORE_FETCHED_URLS__ || (globalThis.__NS_CORE_FETCHED_URLS__ = []);`,
3998
+ ` const __nsKey = ${JSON.stringify(canonicalSub)};`,
3999
+ ` const __nsUrl = ${JSON.stringify(canonicalUrl)};`,
4000
+ ` __nsSeen.push(__nsUrl);`,
4001
+ ` if (typeof __nsFirst[__nsKey] === 'string' && __nsFirst[__nsKey] !== __nsUrl) {`,
4002
+ ` throw new Error('[ns-core] URL drift for sub=' + __nsKey + ': first=' + __nsFirst[__nsKey] + ' now=' + __nsUrl + ' (see HMR_CORE_REALM_DETERMINISTIC_PLAN.md Invariant A)');`,
4003
+ ` }`,
4004
+ ` if (!__nsFirst[__nsKey]) __nsFirst[__nsKey] = __nsUrl;`,
4005
+ ` globalThis.__NS_CORE_EVAL_COUNT__ = (globalThis.__NS_CORE_EVAL_COUNT__ || 0) + 1;`,
4006
+ `} } catch (e) { try { console.warn('[ns-core] instrumentation failed:', (e && e.message) || e); } catch {} }`,
4007
+ ].join('\n');
4008
+ // Invariant D (CJS/ESM interop shape) — REGISTRATION side.
4009
+ //
4010
+ // The actual shape installer runs earlier in the module
4011
+ // body (between preamble and selfImport; see
4012
+ // buildShapeInstallHeader). At this point we just read
4013
+ // globalThis.__NS_CJS_SHAPE__ and apply it to the self
4014
+ // namespace before registering under the CJS key space.
4015
+ //
4016
+ // Why shape self at registration: consumers that reach
4017
+ // `@nativescript/core` via `require()` (legacy vendors,
4018
+ // `globalThis.require` shim) look up the registry. They
4019
+ // expect a plain object (Object.prototype in chain) so
4020
+ // `.hasOwnProperty` / `.toString` work. Shaping once on
4021
+ // registration — the shape function is identity-preserving
4022
+ // via WeakMap — gives a stable, shared, CJS-compatible
4023
+ // view without copying on every require.
4024
+ //
4025
+ // See HMR_CORE_REALM_DETERMINISTIC_PLAN.md § "Invariant D"
4026
+ // for the full rationale.
4027
+ const registrationFooter = [
4028
+ `try { if (typeof globalThis !== 'undefined') {`,
4029
+ ` const __nsReg = globalThis.__NS_CORE_MODULES__ || (globalThis.__NS_CORE_MODULES__ = Object.create(null));`,
4030
+ ` const __nsShapeFn = typeof globalThis.__NS_CJS_SHAPE__ === 'function' ? globalThis.__NS_CJS_SHAPE__ : function (x) { return x; };`,
4031
+ ` const __nsSelfRaw = (typeof __ns_core_self_ns__ !== 'undefined') ? __ns_core_self_ns__ : { default: undefined };`,
4032
+ ` const __nsSelf = __nsShapeFn(__nsSelfRaw);`,
4033
+ ...registrationKeys.map((k) => ` __nsReg[${k}] = __nsSelf;`),
4034
+ `} } catch (e) { try { console.warn('[ns-core] self-register failed:', (e && e.message) || e); } catch {} }`,
4035
+ ].join('\n');
4036
+ // Bind `import * as __ns_core_self_ns__` to the module's
4037
+ // own export namespace so the footer can stash it into
4038
+ // the registry. Self-import is a no-op at eval time —
4039
+ // V8 resolves it to the module record we're already
4040
+ // evaluating and the final namespace is the same object
4041
+ // the registry receives. We use the CANONICAL URL here
4042
+ // so the self-import participates in Invariant A along
4043
+ // with every other @nativescript/core URL.
4044
+ const canonicalUrlForSelf = canonicalSub ? `/ns/core/${canonicalSub}` : '/ns/core';
4045
+ const selfImport = `import * as __ns_core_self_ns__ from ${JSON.stringify(canonicalUrlForSelf)};`;
4046
+ // Invariant D — SHAPE INSTALLER.
4047
+ //
4048
+ // Emits idempotent body-code that installs
4049
+ // globalThis.__NS_CJS_SHAPE__ BEFORE `rewritten`'s body
4050
+ // runs. This matters because the rewrite step above may
4051
+ // have produced statements like
4052
+ // `export const Utils = (typeof globalThis.__NS_CJS_SHAPE__ ...)(__ns_re_Utils__);`
4053
+ // that execute during module evaluation. Without the
4054
+ // installer running first, the ternary falls back to
4055
+ // identity — still safe, but the null-proto namespace
4056
+ // leaks through and consumers that expect a plain
4057
+ // object would still crash.
4058
+ //
4059
+ // Placement is important: BEFORE selfImport in the
4060
+ // concatenation. ESM imports are hoisted regardless of
4061
+ // textual position, but body code executes in source
4062
+ // order. Placing the installer first guarantees it
4063
+ // runs before any body statement in `rewritten`.
4064
+ //
4065
+ // Install is idempotent: `|| (globalThis.X = ...)` so
4066
+ // whichever /ns/core module evaluates first wins and
4067
+ // every subsequent module becomes a no-op.
4068
+ const shapeInstallHeader = buildShapeInstallHeader();
4069
+ // Invariant D — DEFAULT EXPORT BRIDGE.
4070
+ //
4071
+ // See `buildDefaultExportFooter` in ns-core-cjs-shape.ts
4072
+ // for the full rationale (consumer matrix, skip conditions,
4073
+ // why the default isn't shaped). The short version:
4074
+ // upstream rewrites turn `import { X } from '@nativescript/core'`
4075
+ // into a DEFAULT import, and the bridge has to provide one.
4076
+ const defaultExportFooter = buildDefaultExportFooter(rewritten);
4077
+ const moduleCode = [instrumentationHeader, preamble, shapeInstallHeader, selfImport, rewritten, defaultExportFooter, registrationFooter].join('\n');
4078
+ res.statusCode = 200;
4079
+ res.end(moduleCode);
4080
+ }
4081
+ catch (e) {
4082
+ try {
4083
+ console.warn('[ns-core-bridge] serve failed:', e?.message);
4084
+ }
4085
+ catch { }
4086
+ next();
4087
+ }
4088
+ });
4089
+ // 2.6a) Serve compiled entry runtime module: GET /ns/entry-rt[?v=<ver>]
4090
+ server.middlewares.use(async (req, res, next) => {
4091
+ try {
4092
+ const urlObj = new URL(req.url || '', 'http://localhost');
4093
+ if (!(urlObj.pathname === '/ns/entry-rt'))
4094
+ return next();
4095
+ try {
4096
+ if (verbose) {
4097
+ const ra = req.socket?.remoteAddress;
4098
+ const rp = req.socket?.remotePort;
4099
+ console.log('[hmr-http] GET /ns/entry-rt from', ra + (rp ? ':' + rp : ''));
4100
+ }
4101
+ }
4102
+ catch { }
4103
+ res.setHeader('Access-Control-Allow-Origin', '*');
4104
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4105
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4106
+ res.setHeader('Pragma', 'no-cache');
4107
+ res.setHeader('Expires', '0');
4108
+ let content = '';
4109
+ try {
4110
+ const _req = createRequire(import.meta.url);
4111
+ const entryRtPath = _req.resolve('@nativescript/vite/hmr/entry-runtime.js');
4112
+ content = readFileSync(entryRtPath, 'utf-8');
4113
+ }
4114
+ catch (e) {
4115
+ // .js not found (source tree without build) — transform .ts on the fly
4116
+ try {
4117
+ const tsPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'entry-runtime.ts');
4118
+ if (existsSync(tsPath)) {
4119
+ const tsSource = readFileSync(tsPath, 'utf-8');
4120
+ const result = babelCore.transformSync(tsSource, {
4121
+ filename: tsPath,
4122
+ plugins: [[pluginTransformTypescript, { isTSX: false, allowDeclareFields: true }]],
4123
+ sourceType: 'module',
4124
+ });
4125
+ if (result?.code) {
4126
+ content = result.code;
4127
+ }
4128
+ }
4129
+ }
4130
+ catch (e2) {
4131
+ if (verbose)
4132
+ console.warn('[hmr-http] entry-runtime.ts transform failed', e2);
4133
+ }
4134
+ if (!content) {
4135
+ content = 'export default async function start(){ console.error("[/ns/entry-rt] not found"); }\n';
4136
+ }
4137
+ }
4138
+ console.log('[hmr-http] /ns/entry-rt serving', content.length, 'bytes');
4139
+ res.statusCode = 200;
4140
+ res.end(content);
4141
+ }
4142
+ catch (e) {
4143
+ console.warn('[hmr-http] /ns/entry-rt error', e);
4144
+ next();
4145
+ }
4146
+ });
4147
+ // 2.6b) HTTP-only app entry endpoint: GET /ns/entry[/<ver>]
4148
+ // Thin wrapper that imports the compiled entry runtime and starts it with parameters.
4149
+ server.middlewares.use(async (req, res, next) => {
4150
+ try {
4151
+ const urlObj = new URL(req.url || '', 'http://localhost');
4152
+ if (!(urlObj.pathname === '/ns/entry' || /^\/ns\/entry\/[\d]+$/.test(urlObj.pathname)))
4153
+ return next();
4154
+ try {
4155
+ if (verbose) {
4156
+ const ra = req.socket?.remoteAddress;
4157
+ const rp = req.socket?.remotePort;
4158
+ console.log('[hmr-http] GET /ns/entry from', ra + (rp ? ':' + rp : ''));
4159
+ }
4160
+ }
4161
+ catch { }
4162
+ const verSeg = urlObj.pathname.replace(/^\/ns\/entry\/?/, '');
4163
+ // Resolve app main entry to an absolute path-like key used by /ns/m
4164
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4165
+ res.setHeader('Pragma', 'no-cache');
4166
+ res.setHeader('Expires', '0');
4167
+ const ver = /^[0-9]+$/.test(verSeg) ? verSeg : String(graphVersion || 0);
4168
+ const origin = getServerOrigin(server) || `${urlObj.protocol}//${urlObj.host}`;
4169
+ // Resolve app main entry to an absolute path-like key used by /ns/m
4170
+ let mainEntry = '/';
4171
+ try {
4172
+ const pkg = getPackageJson();
4173
+ const main = pkg?.main || DEFAULT_MAIN_ENTRY;
4174
+ const abs = getProjectFilePath(main).replace(/\\/g, '/');
4175
+ // Normalize to '/app/...'
4176
+ const marker = `/${APP_ROOT_DIR}/`;
4177
+ const idx = abs.indexOf(marker);
4178
+ mainEntry = idx >= 0 ? abs.substring(idx) : DEFAULT_MAIN_ENTRY_VIRTUAL;
4179
+ }
4180
+ catch { }
4181
+ // Build a tiny wrapper that imports the compiled entry runtime from the dev server
4182
+ let code = REQUIRE_GUARD_SNIPPET +
4183
+ `// [ns-entry][v${ver}] wrapper (script-safe) bytes will follow\n` +
4184
+ `(async function(){\n` +
4185
+ ` let origin = ${JSON.stringify(origin)}; const main = ${JSON.stringify(mainEntry)}; const __ns_graph_ver = ${JSON.stringify(ver)};\n` +
4186
+ ` try { const __b = (globalThis && globalThis.__NS_ENTRY_BASE__) ? String(globalThis.__NS_ENTRY_BASE__) : ''; if (__b) { try { const __o = new URL(__b).origin; if (__o) origin = __o; } catch {} } } catch {}\n` +
4187
+ ` const __VERBOSE__ = (typeof __NS_ENV_VERBOSE__ !== 'undefined' && __NS_ENV_VERBOSE__) || (globalThis && globalThis.process && globalThis.process.env && globalThis.process.env.verbose) || (globalThis && globalThis.__NS_ENV_VERBOSE__) || ${JSON.stringify(!!verbose)};\n` +
4188
+ ` if (__VERBOSE__) console.info('[ns-entry][wrapper] start', { origin, main, ver: __ns_graph_ver });\n` +
4189
+ ` async function __ns_import_entry_rt(u){\n` +
4190
+ ` // Prefer fetch+eval script transformation to avoid module import limitations on device\n` +
4191
+ ` try { const r = await fetch(u); const t = await r.text(); if (__VERBOSE__) console.info('[ns-entry][wrapper] entry-rt fetched bytes', (t&&t.length)||0);\n` +
4192
+ ` // Transform 'export default function' or 'export default async function' into global assignment\n` +
4193
+ ` let s = t.replace(/export\\s+default\\s+async\\s+function\\s+([A-Za-z0-9_$]+)?/,'globalThis.__NS_START_ENTRY__=async function $1')\n` +
4194
+ ` .replace(/export\\s+default\\s+function\\s+([A-Za-z0-9_$]+)?/,'globalThis.__NS_START_ENTRY__=function $1');\n` +
4195
+ ` // Fallback: if function-form replacements didn't run, handle expression default export too\n` +
4196
+ ` if (String(s).indexOf('__NS_START_ENTRY__') === -1) { s = 'globalThis.__NS_START_ENTRY__=' + s.replace(/export\\s+default\\s*/,''); }\n` +
4197
+ ` try { (0,eval)(s); } catch (ee) { console.error('[ns-entry][wrapper] eval entry-rt failed', ee && (ee.message||ee)); throw ee; }\n` +
4198
+ ` const fn = globalThis.__NS_START_ENTRY__; if (!fn) { throw new Error('entry-rt missing __NS_START_ENTRY__'); }\n` +
4199
+ ` return { default: fn };\n` +
4200
+ ` } catch(e) { console.error('[ns-entry][wrapper] entry-rt fetch/eval failed', e && (e.message||e)); throw e; }\n` +
4201
+ ` }\n` +
4202
+ ` const __entryRtUrl = '/ns/entry-rt?v=' + String(__ns_graph_ver);\n` +
4203
+ ` let __mod; try { __mod = await __ns_import_entry_rt(__entryRtUrl); if (__VERBOSE__) console.info('[ns-entry][wrapper] entry-rt ready'); } catch (e) { console.error('[ns-entry][wrapper] failed to prepare entry-rt', e && (e.message||e)); throw e; }\n` +
4204
+ ` const startEntry = (__mod && (__mod.default || __mod));\n` +
4205
+ ` try { await startEntry({ origin, main, ver: __ns_graph_ver, verbose: !!__VERBOSE__ }); if (__VERBOSE__) console.info('[ns-entry][wrapper] startEntry() resolved'); } catch (e) { console.error('[ns-entry][wrapper] startEntry() failed', e && (e.message||e)); throw e; }\n` +
4206
+ `})();\n`;
4207
+ code = code + `\n//# sourceURL=${origin}/ns/entry`;
3333
4208
  res.statusCode = 200;
3334
4209
  res.end(code);
3335
4210
  }
@@ -3337,33 +4212,1336 @@ export const piniaSymbol = p.piniaSymbol;
3337
4212
  next();
3338
4213
  }
3339
4214
  });
3340
- registerTxnHandler(server, {
3341
- resolveTxnIds: (version, fallbackChangedIds) => {
3342
- const ids = txnBatches.get(version) || [];
3343
- if (ids.length) {
3344
- return ids;
3345
- }
3346
- return fallbackChangedIds.length ? computeTxnOrderForChanged(fallbackChangedIds) : [];
3347
- },
4215
+ // 2.6) Transactional HMR endpoint: GET /ns/txn/<ver>
4216
+ // Returns a single ESM that sequentially imports all changed modules for the given graphVersion.
4217
+ server.middlewares.use(async (req, res, next) => {
4218
+ try {
4219
+ const urlObj = new URL(req.url || '', 'http://localhost');
4220
+ const p = urlObj.pathname || '';
4221
+ if (!p.startsWith('/ns/txn'))
4222
+ return next();
4223
+ let verStr = p.replace('/ns/txn', '').replace(/^\//, '');
4224
+ const ver = Number(verStr || urlObj.searchParams.get('v') || 0);
4225
+ let ids = txnBatches.get(ver) || [];
4226
+ if (!ids.length) {
4227
+ // Attempt to rebuild from any changed modules at this version if present in graph history is unavailable.
4228
+ // Fallback heuristic: use all modules with latest hash change equal to this version (we don't store per-module version, so use any changedIds from query 'ids' if provided)
4229
+ try {
4230
+ const q = (urlObj.searchParams.get('ids') || '')
4231
+ .split(',')
4232
+ .map((s) => s.trim())
4233
+ .filter(Boolean);
4234
+ if (q.length)
4235
+ ids = computeTxnOrderForChanged(q);
4236
+ }
4237
+ catch { }
4238
+ }
4239
+ const origin = getServerOrigin(server) || `${urlObj.protocol}//${urlObj.host}`;
4240
+ const lines = [];
4241
+ lines.push(`// [txn] version=${ver} count=${ids.length}`);
4242
+ if (!ids.length) {
4243
+ lines.push(`export default true;`);
4244
+ res.setHeader('Access-Control-Allow-Origin', '*');
4245
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4246
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4247
+ res.setHeader('Pragma', 'no-cache');
4248
+ res.setHeader('Expires', '0');
4249
+ res.statusCode = 200;
4250
+ res.end(lines.join('\n'));
4251
+ return;
4252
+ }
4253
+ for (const id of ids) {
4254
+ const isVue = /\.vue$/i.test(id);
4255
+ const safe = id.startsWith('/') ? id : '/' + id;
4256
+ const abs = isVue ? `/ns/asm/${ver}?path=${encodeURIComponent(safe)}` : `/ns/m${safe}`;
4257
+ lines.push(`await import(${JSON.stringify(abs)});`);
4258
+ }
4259
+ lines.push(`export default true;`);
4260
+ const code = lines.join('\n');
4261
+ res.setHeader('Access-Control-Allow-Origin', '*');
4262
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4263
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4264
+ res.setHeader('Pragma', 'no-cache');
4265
+ res.setHeader('Expires', '0');
4266
+ res.statusCode = 200;
4267
+ res.end(code);
4268
+ return;
4269
+ }
4270
+ catch (e) {
4271
+ /* fallthrough */
4272
+ }
4273
+ return next();
3348
4274
  });
3349
- registerVueSfcHandlers(server, {
3350
- verbose,
3351
- requireGuardSnippet: REQUIRE_GUARD_SNIPPET,
3352
- appVirtualWithSlash: APP_VIRTUAL_WITH_SLASH,
3353
- sfcFileMap,
3354
- depFileMap,
3355
- getGraphVersion: () => Number(graphVersion || 0),
3356
- getStrategy: () => ACTIVE_STRATEGY,
3357
- getServerOrigin,
3358
- cleanCode,
3359
- processCodeForDevice,
3360
- rewriteImports,
3361
- ensureVariableDynamicImportHelper,
3362
- ensureGuardPlainDynamicImports,
3363
- ensureVersionedRtImports,
3364
- ensureVersionedCoreImports,
3365
- ensureDestructureCoreImports,
3366
- extractExportMetadata,
4275
+ // 3) ESM endpoint for SFC modules: GET /ns/sfc?path=/src/Comp.vue[?vue&type=*] OR /ns/sfc/src/Comp.vue[?vue&type=*]
4276
+ // Also accept alias /ns/sfc
4277
+ // Preserves variant queries (?vue&type=script|template|style) and adds a diagnostic signature comment.
4278
+ server.middlewares.use(async (req, res, next) => {
4279
+ try {
4280
+ const urlObj = new URL(req.url || '', 'http://localhost');
4281
+ const p = urlObj.pathname;
4282
+ // Only match exactly "/ns/sfc" or paths under it.
4283
+ const isNs = p === '/ns/sfc' || p.startsWith('/ns/sfc/');
4284
+ if (!isNs)
4285
+ return next();
4286
+ if (p.startsWith('/ns/asm') || p.startsWith('/ns/sfc-meta'))
4287
+ return next();
4288
+ res.setHeader('Access-Control-Allow-Origin', '*');
4289
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4290
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4291
+ res.setHeader('Pragma', 'no-cache');
4292
+ res.setHeader('Expires', '0');
4293
+ const base = '/ns/sfc';
4294
+ // Determine request spec, preserving variant query when present and handling optional version in path
4295
+ let pathParam = urlObj.searchParams.get('path') || ''; // may include its own query
4296
+ const rawRemainder = urlObj.pathname.slice(base.length) || '';
4297
+ let verFromPath = null;
4298
+ let pathStyle = rawRemainder;
4299
+ if (rawRemainder && rawRemainder.startsWith('/')) {
4300
+ const parts = rawRemainder.split('/'); // ["", maybe "<ver>", ...]
4301
+ if (parts.length > 2 && /^[0-9]+$/.test(parts[1] || '')) {
4302
+ verFromPath = parts[1];
4303
+ pathStyle = '/' + parts.slice(2).join('/');
4304
+ }
4305
+ }
4306
+ if (pathStyle && pathStyle !== '/' && !pathParam) {
4307
+ if (!pathStyle.startsWith('/'))
4308
+ pathStyle = '/' + pathStyle;
4309
+ // Include endpoint query for variant-style requests (e.g. /ns/sfc/Comp.vue?vue&type=template)
4310
+ pathParam = pathStyle + (urlObj.search || '');
4311
+ }
4312
+ let fullSpec = pathParam || '';
4313
+ if (!fullSpec) {
4314
+ res.statusCode = 200;
4315
+ res.end('export {}\n');
4316
+ return;
4317
+ }
4318
+ if (fullSpec.startsWith('@/'))
4319
+ fullSpec = APP_VIRTUAL_WITH_SLASH + fullSpec.slice(2);
4320
+ if (!fullSpec.startsWith('/'))
4321
+ fullSpec = '/' + fullSpec;
4322
+ const isVariant = /[?&]vue&type=/.test(fullSpec);
4323
+ const variantTypeMatch = /[?&]type=([^&]+)/.exec(fullSpec);
4324
+ const variantType = variantTypeMatch?.[1] || null;
4325
+ const isStyleVariant = /[?&]type=style\b/.test(fullSpec);
4326
+ // Determine candidate for transformRequest
4327
+ // For full SFCs we prefer a clean base path + '?vue'; if that fails, try base without query as fallback.
4328
+ let candidate = fullSpec;
4329
+ let transformed = null;
4330
+ if (!isVariant) {
4331
+ const basePath = fullSpec.replace(/[?#].*$/, '');
4332
+ const candidates = [basePath + (basePath.includes('?') ? '&' : '?') + 'vue', basePath];
4333
+ for (const c of candidates) {
4334
+ try {
4335
+ const r = await server.transformRequest(c);
4336
+ if (r?.code) {
4337
+ transformed = r;
4338
+ candidate = c;
4339
+ break;
4340
+ }
4341
+ }
4342
+ catch { }
4343
+ }
4344
+ if (!transformed?.code) {
4345
+ if (verbose) {
4346
+ try {
4347
+ console.warn(`[sfc][serve] transform miss for`, fullSpec);
4348
+ }
4349
+ catch { }
4350
+ }
4351
+ // Emit an erroring module to surface the failure at import site with helpful hints
4352
+ try {
4353
+ const tried = candidates.slice(0, 8);
4354
+ const out = `// [sfc] transform miss kind=full path=${fullSpec.replace(/\n/g, '')} tried=${tried.length}\n` + `throw new Error(${JSON.stringify('[ns/sfc] transform failed for full SFC: ' + fullSpec + ' (tried ' + tried.length + ')')});\nexport {}\n`;
4355
+ res.statusCode = 404;
4356
+ res.end(out);
4357
+ return;
4358
+ }
4359
+ catch {
4360
+ res.statusCode = 404;
4361
+ res.end('export {}\n');
4362
+ return;
4363
+ }
4364
+ }
4365
+ }
4366
+ else {
4367
+ try {
4368
+ transformed = await server.transformRequest(candidate);
4369
+ }
4370
+ catch { }
4371
+ if (!transformed?.code) {
4372
+ try {
4373
+ const out = `// [sfc] transform miss kind=variant path=${fullSpec.replace(/\n/g, '')}\n` + `throw new Error(${JSON.stringify('[ns/sfc] transform failed for variant: ' + fullSpec)});\nexport {}\n`;
4374
+ res.statusCode = 404;
4375
+ res.end(out);
4376
+ return;
4377
+ }
4378
+ catch {
4379
+ res.statusCode = 404;
4380
+ res.end('export {}\n');
4381
+ return;
4382
+ }
4383
+ }
4384
+ }
4385
+ // For style variants, return an empty module immediately
4386
+ if (isStyleVariant) {
4387
+ const sig = `// [sfc] kind=variant:style path=${fullSpec.replace(/\n/g, '')} len=0 default=false\n`;
4388
+ res.statusCode = 200;
4389
+ res.end(`${sig}export {}\n`);
4390
+ return;
4391
+ }
4392
+ let code = transformed.code;
4393
+ // Prepend guard to capture any URL-based require attempts
4394
+ code = REQUIRE_GUARD_SNIPPET + code;
4395
+ const projectRoot = server.config?.root || process.cwd();
4396
+ // IMPORTANT: Do not run cleanCode() on template variant; it can strip required pieces.
4397
+ // We'll handle script/full SFC below, and treat template minimally right away.
4398
+ // Full SFCs delegate to deterministic assembler module; variants (script/template) still go through processing
4399
+ if (!isVariant) {
4400
+ const importerPath = fullSpec.replace(/[?#].*$/, '');
4401
+ const origin = getServerOrigin(server);
4402
+ const ver = verFromPath || '0';
4403
+ const asmPath = `/ns/asm/${ver}?path=${encodeURIComponent(importerPath)}`;
4404
+ const delegated = `// [sfc] kind=full (delegated to assembler) path=${importerPath}\nexport * from ${JSON.stringify(asmPath)};\nexport { default } from ${JSON.stringify(asmPath)};\n`;
4405
+ res.statusCode = 200;
4406
+ res.end(delegated);
4407
+ return;
4408
+ }
4409
+ else {
4410
+ // Variants
4411
+ if (variantType === 'template') {
4412
+ const preferSelfCompile = !!process.env.NS_HMR_SELF_COMPILE_TEMPLATE;
4413
+ // Compile the template ourselves to guarantee no Vite HMR code and stable output
4414
+ if (preferSelfCompile)
4415
+ try {
4416
+ const projectRootT = server.config?.root || process.cwd();
4417
+ const basePath = fullSpec.replace(/[?#].*$/, '');
4418
+ const abs = path.join(projectRootT, basePath.replace(/^\//, ''));
4419
+ let sfcSrc = '';
4420
+ try {
4421
+ sfcSrc = readFileSync(abs, 'utf-8');
4422
+ }
4423
+ catch { }
4424
+ if (sfcSrc) {
4425
+ const { descriptor } = parse(sfcSrc, { filename: abs });
4426
+ const id = createHash('md5').update(abs).digest('hex').slice(0, 8);
4427
+ let bindingMetadata = undefined;
4428
+ try {
4429
+ const s = compileScript(descriptor, {
4430
+ id,
4431
+ inlineTemplate: false,
4432
+ reactivityTransform: false,
4433
+ });
4434
+ bindingMetadata = s?.bindings;
4435
+ }
4436
+ catch { }
4437
+ const tpl = descriptor.template?.content || '';
4438
+ const ct = compileTemplate({
4439
+ source: tpl,
4440
+ id,
4441
+ filename: abs,
4442
+ isProd: false,
4443
+ ssr: false,
4444
+ compilerOptions: {
4445
+ bindingMetadata,
4446
+ isCustomElement: (tag) => NS_NATIVE_TAGS.has(tag),
4447
+ },
4448
+ });
4449
+ let out = (ct && (ct.code || '')) || '';
4450
+ // Map Vue helper imports to runtime bridge
4451
+ try {
4452
+ out = out.replace(/from\s+["'](?:nativescript-vue|vue)[^"']*["']/g, 'from "/ns/rt"');
4453
+ }
4454
+ catch { }
4455
+ // No import.meta.hot present when compiling ourselves, but keep minimal sanitizer just in case
4456
+ out = processTemplateVariantMinimal(out);
4457
+ code = out;
4458
+ }
4459
+ else {
4460
+ code = 'export {}\n';
4461
+ }
4462
+ }
4463
+ catch (eTplSelf) {
4464
+ if (verbose) {
4465
+ try {
4466
+ console.warn('[sfc][template][self-compile][fail]', fullSpec, eTplSelf?.message);
4467
+ }
4468
+ catch { }
4469
+ }
4470
+ code = transformed.code || 'export {}\n';
4471
+ code = processTemplateVariantMinimal(code);
4472
+ }
4473
+ else {
4474
+ // Prefer using Vite's template transform and apply minimal sanitization; avoids compiler mismatches and warnings
4475
+ code = transformed.code || 'export {}\n';
4476
+ code = processTemplateVariantMinimal(code);
4477
+ }
4478
+ // fall through to shared post-processing (versioning, signature, etc.)
4479
+ }
4480
+ // Script variants still need vendor mappings and general device processing (no SFC assembly)
4481
+ // IMPORTANT: Use a Babel AST transform to remove imports of the template variant and
4482
+ // neutralize their usage without brittle regex.
4483
+ try {
4484
+ const ast = babelParse(code, {
4485
+ sourceType: 'module',
4486
+ plugins: ['typescript'],
4487
+ });
4488
+ const templateBindings = new Set();
4489
+ const navToLocals = [];
4490
+ const navBackLocals = [];
4491
+ babelTraverse(ast, {
4492
+ ImportDeclaration(path) {
4493
+ const spec = path.node.source.value || '';
4494
+ // Remove template variant imports and collect their local identifiers for neutralization
4495
+ if (typeof spec === 'string' && /\.vue\?[^\n]*type=template/.test(spec)) {
4496
+ const ids = [];
4497
+ for (const s of path.node.specifiers) {
4498
+ if (t.isImportSpecifier(s)) {
4499
+ const imported = t.isIdentifier(s.imported) ? s.imported.name : undefined;
4500
+ const local = t.isIdentifier(s.local) ? s.local.name : undefined;
4501
+ if ((imported === 'render' || imported === undefined) && local)
4502
+ ids.push(local);
4503
+ }
4504
+ else if (t.isImportDefaultSpecifier(s) || t.isImportNamespaceSpecifier(s)) {
4505
+ if (t.isIdentifier(s.local))
4506
+ ids.push(s.local.name);
4507
+ }
4508
+ }
4509
+ ids.forEach((n) => templateBindings.add(n));
4510
+ path.remove();
4511
+ return;
4512
+ }
4513
+ // Rewrite $navigateTo/$navigateBack imports from nativescript-vue (or prebundle) to use globals
4514
+ const isNsVue = typeof spec === 'string' && (/nativescript-vue/.test(spec) || /vendor\.mjs$/.test(spec) || /\/node_modules\/\.vite\/deps\/nativescript-vue\.js/.test(spec));
4515
+ if (isNsVue) {
4516
+ const remain = [];
4517
+ for (const s of path.node.specifiers) {
4518
+ if (t.isImportSpecifier(s)) {
4519
+ const imported = t.isIdentifier(s.imported) ? s.imported.name : undefined;
4520
+ const local = t.isIdentifier(s.local) ? s.local.name : undefined;
4521
+ if (local && (imported === '$navigateTo' || imported === 'navigateTo')) {
4522
+ navToLocals.push(local);
4523
+ continue;
4524
+ }
4525
+ if (local && (imported === '$navigateBack' || imported === 'navigateBack')) {
4526
+ navBackLocals.push(local);
4527
+ continue;
4528
+ }
4529
+ }
4530
+ remain.push(s);
4531
+ }
4532
+ if (remain.length) {
4533
+ path.node.specifiers = remain;
4534
+ }
4535
+ else {
4536
+ path.remove();
4537
+ }
4538
+ }
4539
+ },
4540
+ });
4541
+ if (templateBindings.size) {
4542
+ babelTraverse(ast, {
4543
+ Identifier(path) {
4544
+ if (templateBindings.has(path.node.name)) {
4545
+ path.replaceWith(t.identifier('undefined'));
4546
+ }
4547
+ },
4548
+ AssignmentExpression(path) {
4549
+ // Guard component.render = <alias> to avoid TDZ when alias is undefined
4550
+ if (t.isMemberExpression(path.node.left) &&
4551
+ t.isIdentifier(path.node.left.property, {
4552
+ name: 'render',
4553
+ })) {
4554
+ const e = t.identifier('__e');
4555
+ const guarded = t.tryStatement(t.blockStatement([t.variableDeclaration('const', [t.variableDeclarator(e, path.node.right)]), t.ifStatement(t.logicalExpression('&&', t.binaryExpression('!==', t.unaryExpression('typeof', path.node.left.object, true), t.stringLiteral('undefined')), t.binaryExpression('!==', t.unaryExpression('typeof', e, true), t.stringLiteral('undefined'))), t.blockStatement([t.expressionStatement(t.assignmentExpression('=', path.node.left, e))]))]), t.catchClause(t.identifier('_e'), t.blockStatement([])));
4556
+ path.replaceWithMultiple([guarded]);
4557
+ }
4558
+ },
4559
+ });
4560
+ }
4561
+ let outCode = genCode(ast).code;
4562
+ if (navToLocals.length || navBackLocals.length) {
4563
+ const shimLines = [];
4564
+ for (const n of navToLocals)
4565
+ shimLines.push(`import __ns_rt_nav_to_mod from "/ns/rt";\nconst ${n} = (...args) => __ns_rt_nav_to_mod.$navigateTo(...args);`);
4566
+ for (const n of navBackLocals)
4567
+ shimLines.push(`import __ns_rt_nav_back_mod from "/ns/rt";\nconst ${n} = (...args) => __ns_rt_nav_back_mod.$navigateBack(...args);`);
4568
+ outCode = shimLines.join('\n') + '\n' + outCode;
4569
+ }
4570
+ code = outCode;
4571
+ }
4572
+ catch { }
4573
+ code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(fullSpec), fullSpec);
4574
+ // Transform static .vue imports into static imports from the assembler (no TLA) via AST
4575
+ try {
4576
+ const importerPath = fullSpec.replace(/[?#].*$/, '');
4577
+ const origin = getServerOrigin(server);
4578
+ const ver = verFromPath || '0';
4579
+ const ast2 = babelParse(code, {
4580
+ sourceType: 'module',
4581
+ plugins: ['typescript'],
4582
+ });
4583
+ babelTraverse(ast2, {
4584
+ ImportDeclaration(p) {
4585
+ const src = p.node.source.value || '';
4586
+ if (typeof src !== 'string')
4587
+ return;
4588
+ if (/^https?:\/\//.test(src))
4589
+ return; // leave absolute URLs
4590
+ if (/\.vue(?:$|\?)/.test(src)) {
4591
+ let spec = src;
4592
+ // Resolve to absolute project path
4593
+ if (spec.startsWith('./') || spec.startsWith('../')) {
4594
+ spec = path.posix.normalize(path.posix.join(path.posix.dirname(importerPath), spec));
4595
+ if (!spec.startsWith('/'))
4596
+ spec = '/' + spec;
4597
+ }
4598
+ else if (!spec.startsWith('/')) {
4599
+ // Handle '@/'
4600
+ if (spec.startsWith('@@/'))
4601
+ spec = '/' + spec.slice(2);
4602
+ if (spec.startsWith('@/'))
4603
+ spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
4604
+ }
4605
+ // Strip query for plain .vue (keep variant imports intact)
4606
+ if (!/\bvue&type=/.test(src)) {
4607
+ spec = spec.replace(/[?#].*$/, '');
4608
+ const asmUrl = `/ns/asm/${ver}?path=${encodeURIComponent(spec)}&mode=inline`;
4609
+ p.node.source = t.stringLiteral(asmUrl);
4610
+ }
4611
+ }
4612
+ },
4613
+ });
4614
+ code = genCode(ast2).code;
4615
+ }
4616
+ catch { }
4617
+ // After rewrites, strip any TypeScript syntax from the script variant to avoid device-side parse errors
4618
+ try {
4619
+ const importerPath = fullSpec.replace(/[?#].*$/, '');
4620
+ const tsRes = await babelCore.transformAsync(code, {
4621
+ plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
4622
+ sourceType: 'module',
4623
+ // Help Babel infer TS parsing even if the virtual filename isn't .ts
4624
+ filename: importerPath.endsWith('.vue') ? importerPath.replace(/\.vue$/, '.ts') : importerPath + '.ts',
4625
+ comments: true,
4626
+ configFile: false,
4627
+ babelrc: false,
4628
+ });
4629
+ if (tsRes?.code) {
4630
+ code = tsRes.code;
4631
+ }
4632
+ }
4633
+ catch (eTsVar) {
4634
+ if (verbose) {
4635
+ try {
4636
+ console.warn('[sfc][variant:script][babel-ts][fail]', fullSpec, eTsVar?.message);
4637
+ }
4638
+ catch { }
4639
+ }
4640
+ }
4641
+ }
4642
+ const importerPath = fullSpec.replace(/[?#].*$/, '');
4643
+ // Only run cleanCode for non-template cases (script/full). Template code must remain intact.
4644
+ if (!isVariant || variantType !== 'template') {
4645
+ code = cleanCode(code);
4646
+ }
4647
+ code = rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, getServerOrigin(server));
4648
+ code = ensureVariableDynamicImportHelper(code);
4649
+ try {
4650
+ // For variant requests under /ns/sfc, prefer the version from the path segment when present
4651
+ // so that any internal '/ns/rt', '/ns/core', or '/ns/sfc' imports are aligned with the same version.
4652
+ const verNum = Number(verFromPath || '0');
4653
+ if (Number.isFinite(verNum) && verNum > 0) {
4654
+ code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
4655
+ code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
4656
+ code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
4657
+ }
4658
+ else {
4659
+ code = ensureVersionedRtImports(code, getServerOrigin(server), graphVersion);
4660
+ code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), graphVersion);
4661
+ code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
4662
+ }
4663
+ }
4664
+ catch { }
4665
+ // Final guard for SFC variant output as well
4666
+ try {
4667
+ code = ensureDestructureCoreImports(code);
4668
+ }
4669
+ catch { }
4670
+ // CRITICAL: As a last step for script/template variants, re-run AST normalization and strip
4671
+ // any sentinel destructures that could cause duplicate locals, then re-apply core versioning.
4672
+ try {
4673
+ code = astNormalizeModuleImportsAndHelpers(code);
4674
+ }
4675
+ catch { }
4676
+ try {
4677
+ // Remove any rt->core sentinel destructures that slipped in late
4678
+ code = stripRtCoreSentinel(code);
4679
+ }
4680
+ catch { }
4681
+ try {
4682
+ const verNum = Number(verFromPath || '0');
4683
+ if (Number.isFinite(verNum) && verNum > 0) {
4684
+ code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
4685
+ code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
4686
+ }
4687
+ else {
4688
+ code = ensureVersionedRtImports(code, getServerOrigin(server), graphVersion);
4689
+ code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
4690
+ }
4691
+ }
4692
+ catch { }
4693
+ // Last-chance sanitizer for dangling Vite CJS import helper usages that may surface after late transforms
4694
+ try {
4695
+ code = stripDanglingViteCjsImports(code);
4696
+ }
4697
+ catch { }
4698
+ const hasDefault = /\bexport\s+default\b/.test(code);
4699
+ const kind = isVariant ? `variant:${variantType || 'unknown'}` : 'full';
4700
+ const sig = `// [sfc] kind=${kind} path=${importerPath} len=${code.length} default=${hasDefault} wrapped=${false}\n`;
4701
+ if (verbose) {
4702
+ try {
4703
+ console.log(`[sfc][serve] ${fullSpec} kind=${kind} default=${hasDefault} bytes=${code.length}`);
4704
+ }
4705
+ catch { }
4706
+ }
4707
+ // Ensure script variants always provide a default export if they declare a component
4708
+ if (!hasDefault) {
4709
+ // Prefer an explicit identifier if present
4710
+ const m = code.match(/\b(?:const|let|var)\s+(__ns_sfc__|_sfc_main)\b/);
4711
+ if (m && m[1]) {
4712
+ code += `\nexport default ${m[1]};`;
4713
+ }
4714
+ else if (/\b_defineComponent\s*\(|\bdefineComponent\s*\(/.test(code)) {
4715
+ // Fallback: export whichever is defined at runtime without throwing on missing identifiers
4716
+ code += `\nexport default (typeof __ns_sfc__ !== "undefined" ? __ns_sfc__ : (typeof _sfc_main !== "undefined" ? _sfc_main : undefined));`;
4717
+ }
4718
+ }
4719
+ res.statusCode = 200;
4720
+ res.end(sig + code);
4721
+ }
4722
+ catch (e) {
4723
+ res.statusCode = 500;
4724
+ res.end('export {}\n');
4725
+ }
4726
+ });
4727
+ // 4) JSON metadata endpoint for SFCs: GET /ns/sfc-meta?path=/src/Comp.vue OR /ns/sfc-meta/<ver>?path=/src/Comp.vue
4728
+ server.middlewares.use(async (req, res, next) => {
4729
+ try {
4730
+ const urlObj = new URL(req.url || '', 'http://localhost');
4731
+ if (!urlObj.pathname.startsWith('/ns/sfc-meta'))
4732
+ return next();
4733
+ res.setHeader('Access-Control-Allow-Origin', '*');
4734
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
4735
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4736
+ res.setHeader('Pragma', 'no-cache');
4737
+ res.setHeader('Expires', '0');
4738
+ // Accept optional version segment similar to /ns/sfc
4739
+ {
4740
+ const metaBase = '/ns/sfc-meta';
4741
+ if (urlObj.pathname.startsWith(metaBase + '/')) {
4742
+ const rawRemainder = urlObj.pathname.slice(metaBase.length);
4743
+ const parts = rawRemainder.split('/');
4744
+ if (parts.length > 2 && /^[0-9]+$/.test(parts[1] || '')) {
4745
+ // consume version but we don't need it server-side
4746
+ }
4747
+ }
4748
+ }
4749
+ let spec = urlObj.searchParams.get('path') || '';
4750
+ if (!spec) {
4751
+ res.statusCode = 400;
4752
+ res.end(JSON.stringify({ error: 'missing path' }));
4753
+ return;
4754
+ }
4755
+ if (spec.startsWith('@/'))
4756
+ spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
4757
+ if (!spec.startsWith('/'))
4758
+ spec = '/' + spec;
4759
+ const base = spec.replace(/[?#].*$/, '');
4760
+ // Transform variants to inspect exports
4761
+ const [scriptR, templateR] = await Promise.all([server.transformRequest(base + '?vue&type=script'), server.transformRequest(base + '?vue&type=template')]);
4762
+ const scriptCode = scriptR?.code || '';
4763
+ const templateCode = templateR?.code || '';
4764
+ const scriptMeta = extractExportMetadata(scriptCode);
4765
+ // Robust render detection: Vue compiler may emit several shapes:
4766
+ // 1) export function render(_ctx, _cache) { ... }
4767
+ // 2) function render(_ctx,_cache) { ... } (later exported)
4768
+ // 3) export const render = (_ctx,_cache) => { ... }
4769
+ // 4) const render = (...) => { ... } (later exported)
4770
+ // 5) export { render } or export { render as render }
4771
+ // 6) Object property forms (rare in template output) render: (...) => {}
4772
+ const hasRender = /export\s+function\s+render\s*\(/.test(templateCode) || /(?:^|\n)\s*function\s+render\s*\(/.test(templateCode) || /export\s+(?:const|let|var)\s+render\s*=/.test(templateCode) || /(?:^|\n)\s*(?:const|let|var)\s+render\s*=/.test(templateCode) || /\brender\s*[:=]\s*/.test(templateCode) || /export\s*\{\s*render\s*(?:as\s*render)?\s*\}/.test(templateCode);
4773
+ if (hasRender && verbose) {
4774
+ try {
4775
+ console.log('[sfc-meta] detected render for', base);
4776
+ }
4777
+ catch { }
4778
+ }
4779
+ else if (!hasRender && verbose) {
4780
+ try {
4781
+ console.warn('[sfc-meta] render NOT detected for', base);
4782
+ }
4783
+ catch { }
4784
+ }
4785
+ const hash = createHash('md5').update(base).digest('hex').slice(0, 8);
4786
+ const payload = {
4787
+ path: base,
4788
+ hasScript: !!scriptCode,
4789
+ hasTemplate: !!templateCode,
4790
+ hasStyle: false,
4791
+ scriptExports: scriptMeta.named,
4792
+ scriptHasDefault: scriptMeta.hasDefault,
4793
+ templateHasRender: hasRender,
4794
+ hmrId: hash,
4795
+ };
4796
+ res.statusCode = 200;
4797
+ res.end(JSON.stringify(payload));
4798
+ }
4799
+ catch (e) {
4800
+ res.statusCode = 500;
4801
+ res.end(JSON.stringify({ error: e?.message || String(e) }));
4802
+ }
4803
+ });
4804
+ // 5) Deterministic SFC assembler: GET /ns/asm?path=/src/Comp.vue
4805
+ // Place BEFORE any broader /ns/sfc* handlers that might accidentally match and delegate.
4806
+ server.middlewares.use(async (req, res, next) => {
4807
+ try {
4808
+ const urlObj = new URL(req.url || '', 'http://localhost');
4809
+ if (!urlObj.pathname.startsWith('/ns/asm'))
4810
+ return next();
4811
+ res.setHeader('Access-Control-Allow-Origin', '*');
4812
+ res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4813
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4814
+ res.setHeader('Pragma', 'no-cache');
4815
+ res.setHeader('Expires', '0');
4816
+ // Optional version segment as first path component after /ns/asm
4817
+ const asmBase = '/ns/asm';
4818
+ const asmRemainder = urlObj.pathname.slice(asmBase.length) || '';
4819
+ let verFromPath = null;
4820
+ if (asmRemainder && asmRemainder.startsWith('/')) {
4821
+ const p = asmRemainder.split('/');
4822
+ if (p.length > 1 && /^[0-9]+$/.test(p[1] || '')) {
4823
+ verFromPath = p[1];
4824
+ }
4825
+ }
4826
+ let spec = urlObj.searchParams.get('path') || '';
4827
+ const diag = urlObj.searchParams.get('diag') === '1';
4828
+ if (!spec) {
4829
+ res.statusCode = 400;
4830
+ res.end('export {}\n');
4831
+ return;
4832
+ }
4833
+ if (spec.startsWith('@/'))
4834
+ spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
4835
+ if (!spec.startsWith('/'))
4836
+ spec = '/' + spec;
4837
+ const base = spec.replace(/[?#].*$/, '');
4838
+ if (diag) {
4839
+ const code = `// [sfc-asm] ${base} (diag)\n` + `// vue shim for diag-only instantiation\n` + `var _createElementVNode = globalThis.createElementVNode || globalThis._createElementVNode;\n` + `const __ns_sfc__ = { name: ${JSON.stringify(base.split('/').pop() || 'DiagComp')}, render(){ return _createElementVNode ? _createElementVNode('StackLayout') : (globalThis.createElementVNode ? globalThis.createElementVNode('StackLayout') : {}); } };\nexport default __ns_sfc__;\n`;
4840
+ res.statusCode = 200;
4841
+ res.end(code);
4842
+ return;
4843
+ }
4844
+ const projectRoot = server.config?.root || process.cwd();
4845
+ // Ensure variant transforms exist so imports resolve (avoid Promise.all short-circuit on single failure)
4846
+ const safeTransform = async (cand) => {
4847
+ try {
4848
+ return await server.transformRequest(cand);
4849
+ }
4850
+ catch {
4851
+ return null;
4852
+ }
4853
+ };
4854
+ const scriptR = await safeTransform(base + '?vue&type=script');
4855
+ const templateR = await safeTransform(base + '?vue&type=template');
4856
+ const fullR = await safeTransform(base + '?vue');
4857
+ const hasScript = !!scriptR?.code;
4858
+ const hasTemplate = !!templateR?.code;
4859
+ const origin = getServerOrigin(server);
4860
+ const ver = String(verFromPath || graphVersion || Date.now());
4861
+ const scriptUrl = `${origin}/ns/sfc/${ver}${base}?vue&type=script`;
4862
+ const templateCode = templateR?.code || '';
4863
+ // INLINE-FIRST assembler: compile SFC source into a self-contained ESM module (enhanced diagnostics)
4864
+ try {
4865
+ const root = server.config?.root || process.cwd();
4866
+ const abs = path.join(root, base.replace(/^\//, ''));
4867
+ let sfcSrc = '';
4868
+ try {
4869
+ sfcSrc = readFileSync(abs, 'utf-8');
4870
+ }
4871
+ catch { }
4872
+ if (sfcSrc) {
4873
+ const { descriptor } = parse(sfcSrc, { filename: abs });
4874
+ const id = createHash('md5').update(abs).digest('hex').slice(0, 8);
4875
+ // 1) Compile script (prefer inlineTemplate for a complete module)
4876
+ let compiledScript = '';
4877
+ let bindingMetadata = undefined;
4878
+ let triedInlineTemplate = false;
4879
+ let hadScriptDefaultPre = false;
4880
+ let usedInlineScript = false;
4881
+ try {
4882
+ // First try inlineTemplate for a holistic, self-contained module with render + hoists
4883
+ // Use a strict NativeScript native element detector for inlineTemplate that does NOT treat generic PascalCase as native.
4884
+ // This ensures imported components like PageWrapper remain true components and get referenced via bindings.
4885
+ const isNSNative = (tag) => NS_NATIVE_TAGS.has(tag);
4886
+ const sInline = compileScript(descriptor, {
4887
+ id,
4888
+ inlineTemplate: true,
4889
+ reactivityTransform: false,
4890
+ // Pass only strict NS native element predicate; avoid broad PascalCase heuristic here.
4891
+ templateOptions: {
4892
+ compilerOptions: { isCustomElement: isNSNative },
4893
+ },
4894
+ });
4895
+ triedInlineTemplate = true;
4896
+ if (/export\s+default/.test(sInline?.content || '')) {
4897
+ compiledScript = sInline.content;
4898
+ bindingMetadata = sInline?.bindings;
4899
+ hadScriptDefaultPre = true;
4900
+ usedInlineScript = true;
4901
+ }
4902
+ else {
4903
+ // Fallback to standard script (no inline) and attempt separate template compile
4904
+ const s = compileScript(descriptor, {
4905
+ id,
4906
+ inlineTemplate: false,
4907
+ reactivityTransform: false,
4908
+ });
4909
+ compiledScript = s?.content || '';
4910
+ bindingMetadata = s?.bindings;
4911
+ hadScriptDefaultPre = /export\s+default/.test(compiledScript);
4912
+ usedInlineScript = false;
4913
+ }
4914
+ }
4915
+ catch (eScript) {
4916
+ if (verbose) {
4917
+ try {
4918
+ console.warn('[sfc-asm][compileScript] failed', base, eScript?.message);
4919
+ }
4920
+ catch { }
4921
+ }
4922
+ // Retry without inlineTemplate
4923
+ try {
4924
+ const s = compileScript(descriptor, {
4925
+ id,
4926
+ inlineTemplate: false,
4927
+ reactivityTransform: false,
4928
+ });
4929
+ compiledScript = s?.content || '';
4930
+ bindingMetadata = s?.bindings;
4931
+ hadScriptDefaultPre = /export\s+default/.test(compiledScript);
4932
+ usedInlineScript = false;
4933
+ }
4934
+ catch (eNoInline) {
4935
+ if (verbose) {
4936
+ try {
4937
+ console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
4938
+ }
4939
+ catch { }
4940
+ }
4941
+ }
4942
+ }
4943
+ // Final fallback: if script compile yielded nothing, use the variant-transformed script
4944
+ if (!compiledScript && scriptR?.code) {
4945
+ try {
4946
+ compiledScript = scriptR.code;
4947
+ hadScriptDefaultPre = /export\s+default/.test(compiledScript);
4948
+ }
4949
+ catch { }
4950
+ }
4951
+ // If inlineTemplate produced a default export AND visibly contains a render, allow early-return.
4952
+ // Visible render forms we accept:
4953
+ // - export function render(...) { ... }
4954
+ // - setup(...) { ... return (_ctx, _cache) => { ... } }
4955
+ const hasInlineRender = /(^|\n)\s*export\s+function\s+render\s*\(/.test(compiledScript || '') || /\breturn\s*\(\s*_ctx\s*,\s*_cache\s*\)\s*=>\s*\{/.test(compiledScript || '');
4956
+ // Always use canonical assembler path; avoid inlineTemplate early-return which can miss render attachment
4957
+ // If we reached here, we are going to assemble canonically. Ensure the script we use does NOT include inlineTemplate render.
4958
+ if (usedInlineScript) {
4959
+ try {
4960
+ const sNoInline = compileScript(descriptor, {
4961
+ id,
4962
+ inlineTemplate: false,
4963
+ reactivityTransform: false,
4964
+ });
4965
+ compiledScript = sNoInline?.content || compiledScript;
4966
+ bindingMetadata = sNoInline?.bindings || bindingMetadata;
4967
+ }
4968
+ catch (eNoInline) {
4969
+ if (verbose) {
4970
+ try {
4971
+ console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
4972
+ }
4973
+ catch { }
4974
+ }
4975
+ }
4976
+ }
4977
+ // 2) Compile template
4978
+ let compiledTplCode = '';
4979
+ let templateErr = null;
4980
+ try {
4981
+ const tplSrc = descriptor.template?.content || '';
4982
+ if (tplSrc) {
4983
+ const ct = compileTemplate({
4984
+ source: tplSrc,
4985
+ id,
4986
+ filename: abs,
4987
+ isProd: false,
4988
+ ssr: false,
4989
+ compilerOptions: {
4990
+ bindingMetadata,
4991
+ isCustomElement: (tag) => NS_NATIVE_TAGS.has(tag),
4992
+ },
4993
+ });
4994
+ compiledTplCode = (ct && (ct.code || '')) || '';
4995
+ if (ct?.errors?.length && verbose) {
4996
+ try {
4997
+ console.warn('[sfc-asm][compileTemplate][errors]', base, ct.errors);
4998
+ }
4999
+ catch { }
5000
+ }
5001
+ }
5002
+ }
5003
+ catch (eTpl) {
5004
+ templateErr = eTpl;
5005
+ if (verbose) {
5006
+ try {
5007
+ console.warn('[sfc-asm][compileTemplate] failed', base, eTpl?.message);
5008
+ }
5009
+ catch { }
5010
+ }
5011
+ // Fallback: use the variant-transformed template code if available
5012
+ try {
5013
+ if (templateR?.code)
5014
+ compiledTplCode = templateR.code;
5015
+ }
5016
+ catch { }
5017
+ }
5018
+ // If still no template code, synthesize a minimal render stub so the module is valid
5019
+ if (!compiledTplCode) {
5020
+ try {
5021
+ compiledTplCode = "export function render(){ const _ = (globalThis.createElementVNode||globalThis._createElementVNode); return _? _('StackLayout') : {}; }\n";
5022
+ }
5023
+ catch { }
5024
+ }
5025
+ // 3) Sanitize script and rewrite .vue imports to inline assembler
5026
+ let scriptBody = compiledScript || '';
5027
+ if (scriptBody) {
5028
+ // Do NOT strip Vue/nativescript-vue imports; retarget them to the runtime bridge so helpers (e.g., onMounted) are bound.
5029
+ // Preserve the import clause and only rewrite the source to '/ns/rt'.
5030
+ scriptBody = scriptBody.replace(/(^|\n)\s*import\s+([^;\n]+)\s+from\s+["'](?:vue|nativescript-vue|~\/vendor\.mjs)(?:\/[^"]*)?["'];?/g, (_m, pfx, clause) => `${pfx}import ${clause} from "/ns/rt";`);
5031
+ try {
5032
+ const importerDir = path.posix.dirname(base);
5033
+ scriptBody = scriptBody.replace(/(^|\n)\s*import\s+([^;\n]+)\s+from\s+["']([^"'\n]+\.vue)(?:\?[^"'\n]*)?["'];?/g, (_m, pfx, clause, spec) => {
5034
+ let absImp = spec;
5035
+ if (spec.startsWith('./') || spec.startsWith('../')) {
5036
+ absImp = path.posix.normalize(path.posix.join(importerDir, spec));
5037
+ if (!absImp.startsWith('/'))
5038
+ absImp = '/' + absImp;
5039
+ }
5040
+ else if (!spec.startsWith('/')) {
5041
+ if (absImp.startsWith('@/'))
5042
+ absImp = APP_VIRTUAL_WITH_SLASH + absImp.slice(2);
5043
+ }
5044
+ const asmUrl = `/ns/asm/${ver}?path=${encodeURIComponent(absImp)}&mode=inline`;
5045
+ return `${pfx}import ${clause} from ${JSON.stringify(asmUrl)};`;
5046
+ });
5047
+ }
5048
+ catch { }
5049
+ }
5050
+ // 4) Extract render from compiled template and prepare a full inline template block
5051
+ let helperBindings = '';
5052
+ let renderDecl = '';
5053
+ let inlineBlock = undefined;
5054
+ let renderOk = false;
5055
+ if (compiledTplCode) {
5056
+ try {
5057
+ // Build a full inline template block to preserve hoists where possible
5058
+ inlineBlock = buildInlineTemplateBlock(compiledTplCode) || undefined;
5059
+ if (!inlineBlock) {
5060
+ const extracted = extractTemplateRender(compiledTplCode);
5061
+ helperBindings = extracted.helperBindings;
5062
+ renderDecl = extracted.renderDecl;
5063
+ inlineBlock = extracted.inlineBlock;
5064
+ renderOk = extracted.ok;
5065
+ }
5066
+ else {
5067
+ renderOk = true;
5068
+ }
5069
+ }
5070
+ catch (eExtract) {
5071
+ if (verbose) {
5072
+ try {
5073
+ console.warn('[sfc-asm][extractTemplateRender] failed', base, eExtract?.message);
5074
+ }
5075
+ catch { }
5076
+ }
5077
+ }
5078
+ }
5079
+ // Final guard: if no inline render extracted, attempt to import template variant or synthesize a no-op render
5080
+ if (!renderOk && !inlineBlock) {
5081
+ try {
5082
+ const templateUrl = `${origin}/ns/sfc/${ver}${base}?vue&type=template`;
5083
+ const importLine = `import * as __template from ${JSON.stringify(templateUrl)};`;
5084
+ // Attach only if scriptTransformed produces __ns_sfc__ later
5085
+ helperBindings += `\n${importLine}`;
5086
+ renderDecl += `\nfunction __ns_getRender(){\n try {\n if (__template && __template.render) return __template.render;\n } catch (_e) {}\n try {\n const _ = globalThis.createElementVNode || globalThis._createElementVNode;\n return _ ? function(){ return _('StackLayout'); } : function(){ return {}; };\n } catch (_e) { return function(){ return {}; }; }\n}\n`;
5087
+ renderOk = true;
5088
+ }
5089
+ catch { }
5090
+ }
5091
+ // 5) Convert default export to const __ns_sfc__
5092
+ let scriptTransformed = scriptBody;
5093
+ if (scriptTransformed) {
5094
+ scriptTransformed = scriptTransformed.replace(/(^|\n)\s*export\s+default\s+/g, '$1const __ns_sfc__ = ').replace(/(^|\n)\s*export\s*\{[^}]*\}\s*;?\s*/g, '\n/* removed named exports for inline asm */\n');
5095
+ // Normalize any prior declaration of __ns_sfc__ to a plain assignment to avoid redeclare
5096
+ // Accept a semicolon before the declaration too
5097
+ scriptTransformed = scriptTransformed.replace(/(^|[\n;])\s*(?:const|let|var)\s+__ns_sfc__\s*=\s*/g, '$1__ns_sfc__ = ');
5098
+ // Ensure a single declaration appears once before first assignment
5099
+ if (!/(^|[\n;])\s*(?:const|let|var)\s+__ns_sfc__\b/.test(scriptTransformed)) {
5100
+ scriptTransformed = `let __ns_sfc__;\n` + scriptTransformed;
5101
+ }
5102
+ // Remove stray leading braces (artifact defense)
5103
+ scriptTransformed = scriptTransformed.replace(/^\s*\}+(?=\s*[^}])/, (m) => `/* [asm-fix] removed ${m.length} stray leading braces */\n`);
5104
+ }
5105
+ else {
5106
+ try {
5107
+ const compName = (base.split('/').pop() || 'Component').replace(/\.vue$/i, '') || 'Component';
5108
+ scriptTransformed = `import { defineComponent as _defineComponent } from "/ns/rt";\nlet __ns_sfc__;\n__ns_sfc__ = /*@__PURE__*/_defineComponent({ __name: ${JSON.stringify(compName)} });`;
5109
+ }
5110
+ catch {
5111
+ scriptTransformed = `import { defineComponent as _defineComponent } from "/ns/rt";\nlet __ns_sfc__;\n__ns_sfc__ = /*@__PURE__*/_defineComponent({});`;
5112
+ }
5113
+ }
5114
+ // 6) Emit final inline module with diagnostics comment
5115
+ const parts = [];
5116
+ parts.push(`// [sfc-asm] ${base} (inline-compiled)`);
5117
+ // Deterministic path: always use extracted helperBindings + renderDecl + scriptTransformed (ignore inlineBlock)
5118
+ // Emit hoisted template bindings first
5119
+ if (helperBindings)
5120
+ parts.push(helperBindings);
5121
+ // IMPORTANT: place script (with its imports) BEFORE renderDecl so imports never appear inside the render function.
5122
+ parts.push(scriptTransformed);
5123
+ parts.push(renderDecl);
5124
+ 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){}`);
5125
+ 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; }`);
5126
+ parts.push(`export default __ns_sfc__`);
5127
+ let inlineCode = parts.filter(Boolean).join('\n');
5128
+ inlineCode = processCodeForDevice(inlineCode, false, true);
5129
+ try {
5130
+ inlineCode = ensureVersionedCoreImports(inlineCode, getServerOrigin(server), Number(ver));
5131
+ }
5132
+ catch { }
5133
+ try {
5134
+ inlineCode = ensureDestructureCoreImports(inlineCode);
5135
+ }
5136
+ catch { }
5137
+ // Replace legacy mutation pipeline with canonical assembler for reliability
5138
+ {
5139
+ // First: strip TypeScript robustly using Babel transform
5140
+ try {
5141
+ const tsRes = await babelCore.transformAsync(scriptTransformed, {
5142
+ plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
5143
+ ast: false,
5144
+ sourceType: 'module',
5145
+ configFile: false,
5146
+ babelrc: false,
5147
+ });
5148
+ if (tsRes?.code)
5149
+ scriptTransformed = tsRes.code;
5150
+ }
5151
+ catch (eTs) {
5152
+ if (verbose) {
5153
+ try {
5154
+ console.warn('[sfc-asm][babel-ts][fail]', base, eTs?.message);
5155
+ }
5156
+ catch { }
5157
+ }
5158
+ }
5159
+ // Hoist imports + strip residual TS via AST
5160
+ let importLines = [];
5161
+ try {
5162
+ const astRes = astExtractImportsAndStripTypes(scriptTransformed);
5163
+ importLines = astRes.imports;
5164
+ scriptTransformed = astRes.body;
5165
+ if (astRes.diagnostics.length && verbose) {
5166
+ try {
5167
+ console.warn('[sfc-asm][ast]', base, astRes.diagnostics.join('; '));
5168
+ }
5169
+ catch { }
5170
+ }
5171
+ }
5172
+ catch (eAst) {
5173
+ if (verbose) {
5174
+ try {
5175
+ console.warn('[sfc-asm][ast][fail]', base, eAst?.message);
5176
+ }
5177
+ catch { }
5178
+ }
5179
+ }
5180
+ // Ensure renderDecl ends with closing brace ONLY for function declaration forms
5181
+ // Avoid appending to const-assignment forms like: const __ns_render = (function(){ ... })();
5182
+ if (renderDecl && /(^|\n)\s*(?:export\s+)?function\s+__ns_render\s*\(/.test(renderDecl) && !/\}\s*$/.test(renderDecl)) {
5183
+ renderDecl = renderDecl.trimEnd() + '\n}';
5184
+ }
5185
+ const outParts = [];
5186
+ outParts.push(`// [sfc-asm] ${base} (inline-compiled)`);
5187
+ outParts.push('// [sfc-asm][canonical]');
5188
+ if (importLines.length)
5189
+ outParts.push(Array.from(new Set(importLines)).join('\n'));
5190
+ // Place component script first so the component object exists before we attach render.
5191
+ outParts.push(scriptTransformed);
5192
+ // Prefer full template block to guarantee presence of all hoisted constants.
5193
+ if (inlineBlock) {
5194
+ outParts.push(inlineBlock);
5195
+ }
5196
+ else {
5197
+ if (helperBindings)
5198
+ outParts.push(helperBindings);
5199
+ if (renderDecl && renderDecl.trim())
5200
+ outParts.push(renderDecl);
5201
+ }
5202
+ 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){}`);
5203
+ // Export named render as a function that resolves lazily
5204
+ 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; }');
5205
+ outParts.push('export default __ns_sfc__');
5206
+ let inlineCode2 = outParts.filter(Boolean).join('\n');
5207
+ inlineCode2 = processCodeForDevice(inlineCode2, false, true);
5208
+ try {
5209
+ inlineCode2 = ensureVersionedCoreImports(inlineCode2, getServerOrigin(server), Number(ver));
5210
+ }
5211
+ catch { }
5212
+ try {
5213
+ inlineCode2 = ensureDestructureCoreImports(inlineCode2);
5214
+ }
5215
+ catch { }
5216
+ // Hoist any late imports that accidentally landed after render or script assembly
5217
+ try {
5218
+ const lateImportRe = /^(?!\/\/).*^\s*import\s+[^;]+;?$/gm;
5219
+ const allImports = [];
5220
+ inlineCode2 = inlineCode2.replace(lateImportRe, (imp) => {
5221
+ allImports.push(imp);
5222
+ return '';
5223
+ });
5224
+ if (allImports.length) {
5225
+ // Place after helperBindings sentinel
5226
+ inlineCode2 = inlineCode2.replace(/(\/\/ \[sfc-asm\]\[canonical\]\n)/, `$1${Array.from(new Set(allImports)).join('\n')}\n/* [asm-fix] re-hoisted ${allImports.length} imports */\n`);
5227
+ }
5228
+ }
5229
+ catch { }
5230
+ // After hoisting, re-run AST normalization and duplicate-binding verification.
5231
+ // This guards against freshly hoisted imports reintroducing identifiers that collide
5232
+ // with earlier destructures (e.g., __ns_core_ns_1), which would otherwise surface at device runtime.
5233
+ try {
5234
+ inlineCode2 = astNormalizeModuleImportsAndHelpers(inlineCode2);
5235
+ }
5236
+ catch { }
5237
+ try {
5238
+ inlineCode2 = astVerifyAndAnnotateDuplicates(inlineCode2);
5239
+ if (/^\s*\/\/ \[ast-verify\]\[duplicate-bindings\]/m.test(inlineCode2)) {
5240
+ const diagnosticLine = (inlineCode2.match(/^\s*\/\/ \[ast-verify\]\[duplicate-bindings\][^\n]*/m) || [])[0] || '// [ast-verify][duplicate-bindings]';
5241
+ const brief = diagnosticLine.replace(/^[^:]*:?\s?/, '');
5242
+ const escaped = brief.replace(/["\\]/g, '\\$&');
5243
+ const thrower = `throw new Error("[nsv-hmr] Duplicate top-level bindings detected post-hoist: ${escaped}");`;
5244
+ inlineCode2 = `${thrower}\n` + inlineCode2;
5245
+ }
5246
+ }
5247
+ catch { }
5248
+ // Minimal cleanup only (avoid destructive type stripping breaking object literal property defaults)
5249
+ try {
5250
+ // Heal cases where a TS type strip earlier removed initializer: plain 'default' inside props objects
5251
+ // becomes 'default: undefined'. We only match when followed by ',' or '}' or newline to avoid 'export default'.
5252
+ inlineCode2 = inlineCode2.replace(/\bdefault\b\s*(?=\}|,|\n)/g, 'default: undefined');
5253
+ // Remove obvious leftover angle generic markers
5254
+ inlineCode2 = inlineCode2.replace(/<unknown>/g, '');
5255
+ // Fix accidental '}=> {' sequences
5256
+ inlineCode2 = inlineCode2.replace(/}\s*=>\s*\{/g, '');
5257
+ // No-op: removed prior broken normalization. Handlers are fixed in the dedicated passes below.
5258
+ }
5259
+ catch { }
5260
+ // Removed redundant render closure heal that could inject an extra '}' before component script.
5261
+ // Rewrite any remaining imports (e.g., relative app paths) to HTTP ESM endpoints
5262
+ try {
5263
+ inlineCode2 = rewriteImports(inlineCode2, base, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, getServerOrigin(server));
5264
+ }
5265
+ catch { }
5266
+ // Final TS strip on the whole assembled module (safety net)
5267
+ try {
5268
+ const tsFinal = await babelCore.transformAsync(inlineCode2, {
5269
+ plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
5270
+ ast: false,
5271
+ sourceType: 'module',
5272
+ configFile: false,
5273
+ babelrc: false,
5274
+ });
5275
+ if (tsFinal?.code)
5276
+ inlineCode2 = tsFinal.code;
5277
+ }
5278
+ catch { }
5279
+ // Heal Vue v-model update handlers that lost the ": else" branch during transforms:
5280
+ // "onUpdate:modelValue": _cache[N] || (_cache[N] = $event => _isRef(name) ? name.value = $event)
5281
+ // → add else branch to keep syntax valid: : (name = $event)
5282
+ try {
5283
+ // Fix missing else branch on v-model handlers: support dotted expressions (e.g., $setup.acceptTerms)
5284
+ const reMissingElse = /\"onUpdate:modelValue\"\s*:\s*_cache\[(\d+)\]\s*\|\|\s*\(_cache\[\1\]\s*=\s*\$event\s*=>\s*_isRef\(\s*([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\)\s*\?\s*\2\.value\s*=\s*\$event\s*\)/g;
5285
+ inlineCode2 = inlineCode2.replace(reMissingElse, (_m, idx, expr) => {
5286
+ return `\"onUpdate:modelValue\": _cache[${idx}] || (_cache[${idx}] = $event => (_isRef(${expr}) ? (${expr}.value = $event) : (${expr} = $event)))`;
5287
+ });
5288
+ // Repair malformed handlers without an arrow (introduced by previous transforms):
5289
+ // Convert pattern assigning to $event without an arrow into a proper arrow using the same target expression.
5290
+ const reMalformed = /\"onUpdate:modelValue\"\s*:\s*_cache\[(\d+)\]\s*\|\|\s*\(_cache\[\1\]\s*=\s*[^=]*\(\s*([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\)[^=]*=\s*\$event\s*\)\s*\)/g;
5291
+ inlineCode2 = inlineCode2.replace(reMalformed, (_m, idx, expr) => {
5292
+ return `\"onUpdate:modelValue\": _cache[${idx}] || (_cache[${idx}] = $event => (_isRef(${expr}) ? (${expr}.value = $event) : (${expr} = $event)))`;
5293
+ });
5294
+ }
5295
+ catch { }
5296
+ // Structural heal: ensure balanced braces before the first import statement
5297
+ try {
5298
+ const idx = inlineCode2.search(/^[\t ]*import\b/m);
5299
+ if (idx > 0) {
5300
+ const prefix = inlineCode2.slice(0, idx);
5301
+ let open = 0, close = 0;
5302
+ let inS = false, inD = false, inT = false, inLC = false, inBC = false;
5303
+ for (let i = 0; i < prefix.length; i++) {
5304
+ const ch = prefix[i], nx = prefix[i + 1];
5305
+ if (inLC) {
5306
+ if (ch === '\n')
5307
+ inLC = false;
5308
+ continue;
5309
+ }
5310
+ if (inBC) {
5311
+ if (ch === '*' && nx === '/') {
5312
+ inBC = false;
5313
+ i++;
5314
+ }
5315
+ continue;
5316
+ }
5317
+ if (inS) {
5318
+ if (ch === '\\') {
5319
+ i++;
5320
+ continue;
5321
+ }
5322
+ if (ch === "'")
5323
+ inS = false;
5324
+ continue;
5325
+ }
5326
+ if (inD) {
5327
+ if (ch === '\\') {
5328
+ i++;
5329
+ continue;
5330
+ }
5331
+ if (ch === '"')
5332
+ inD = false;
5333
+ continue;
5334
+ }
5335
+ if (inT) {
5336
+ if (ch === '\\') {
5337
+ i++;
5338
+ continue;
5339
+ }
5340
+ if (ch === '`')
5341
+ inT = false;
5342
+ continue;
5343
+ }
5344
+ if (ch === '/' && nx === '/') {
5345
+ inLC = true;
5346
+ i++;
5347
+ continue;
5348
+ }
5349
+ if (ch === '/' && nx === '*') {
5350
+ inBC = true;
5351
+ i++;
5352
+ continue;
5353
+ }
5354
+ if (ch === "'") {
5355
+ inS = true;
5356
+ continue;
5357
+ }
5358
+ if (ch === '"') {
5359
+ inD = true;
5360
+ continue;
5361
+ }
5362
+ if (ch === '`') {
5363
+ inT = true;
5364
+ continue;
5365
+ }
5366
+ if (ch === '{')
5367
+ open++;
5368
+ else if (ch === '}')
5369
+ close++;
5370
+ }
5371
+ const missing = open - close;
5372
+ if (missing > 0) {
5373
+ inlineCode2 = inlineCode2.slice(0, idx) + '}'.repeat(missing) + '\n' + inlineCode2.slice(idx);
5374
+ }
5375
+ }
5376
+ }
5377
+ catch { }
5378
+ // Final TS strip on the whole assembled module (safety net)
5379
+ try {
5380
+ const tsFinal = await babelCore.transformAsync(inlineCode2, {
5381
+ plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
5382
+ ast: false,
5383
+ sourceType: 'module',
5384
+ configFile: false,
5385
+ babelrc: false,
5386
+ });
5387
+ if (tsFinal?.code)
5388
+ inlineCode2 = tsFinal.code;
5389
+ }
5390
+ catch { }
5391
+ inlineCode2 = ensureVariableDynamicImportHelper(inlineCode2);
5392
+ inlineCode2 = ensureGuardPlainDynamicImports(inlineCode2, origin);
5393
+ inlineCode2 = REQUIRE_GUARD_SNIPPET + inlineCode2;
5394
+ // If no render materialized, return a clear error module for deterministic failure
5395
+ try {
5396
+ const lacksRender = !/__ns_render\b/.test(inlineCode2) && !/__ns_sfc__\.render\s*=/.test(inlineCode2);
5397
+ if (lacksRender) {
5398
+ const err = `throw new Error(\"[sfc-asm] ${base}: no render generated by assembler\");\nexport default {};`;
5399
+ res.statusCode = 200;
5400
+ res.end(err);
5401
+ return;
5402
+ }
5403
+ }
5404
+ catch { }
5405
+ // Cosmetic and parser-friendly: ensure a newline after the canonical banner
5406
+ try {
5407
+ inlineCode2 = inlineCode2.replace(/(\/\/ \[sfc-asm\]\[canonical\])(?!\n)/, '$1\n');
5408
+ }
5409
+ catch { }
5410
+ // Bust device cache for runtime bridge so helpers are always current for this graph version
5411
+ try {
5412
+ const origin = getServerOrigin(server);
5413
+ inlineCode2 = ensureVersionedRtImports(inlineCode2, origin, Number(ver));
5414
+ inlineCode2 = ACTIVE_STRATEGY.ensureVersionedImports(inlineCode2, origin, Number(ver));
5415
+ inlineCode2 = ensureVersionedCoreImports(inlineCode2, origin, Number(ver));
5416
+ }
5417
+ catch { }
5418
+ // Normalize imports/helpers via AST to ensure _defineComponent and other helpers are bound once
5419
+ try {
5420
+ inlineCode2 = astNormalizeModuleImportsAndHelpers(inlineCode2);
5421
+ }
5422
+ catch { }
5423
+ // Guarantee a concrete component object exists before exporting default.
5424
+ try {
5425
+ // Detect an existing declaration of __ns_sfc__ even if it's appended after a semicolon on the same line
5426
+ // e.g., "import ...;let __ns_sfc__;" (no newline). Accept start-of-string, newline, or semicolon as anchors.
5427
+ const hasDecl = /(^|[\n;])\s*(?:const|let|var)\s+__ns_sfc__\b/.test(inlineCode2);
5428
+ if (!hasDecl) {
5429
+ inlineCode2 = inlineCode2.replace(/(\/\/ \[sfc-asm\]\[canonical\]\n)/, `$1let __ns_sfc__ = {};\n`);
5430
+ }
5431
+ // Heal empty declarations (e.g., "let __ns_sfc__;" → initialize to {}), also when preceded by a semicolon
5432
+ inlineCode2 = inlineCode2.replace(/(^|[\n;])\s*let\s+__ns_sfc__\s*;?/g, '$1let __ns_sfc__ = {};');
5433
+ inlineCode2 = inlineCode2.replace(/(^|[\n;])\s*var\s+__ns_sfc__\s*;?/g, '$1var __ns_sfc__ = {};');
5434
+ }
5435
+ catch { }
5436
+ if (!/export\s+default\s+__ns_sfc__/.test(inlineCode2) && /__ns_sfc__/.test(inlineCode2))
5437
+ inlineCode2 += '\nexport default __ns_sfc__';
5438
+ res.statusCode = 200;
5439
+ res.end(inlineCode2);
5440
+ return;
5441
+ }
5442
+ }
5443
+ }
5444
+ catch { }
5445
+ // Do not use compiled ?vue or variant fallbacks; assembler must succeed or emit an error
5446
+ // Prefer compiling template from source via compiler-sfc; fallback to variant extraction
5447
+ let inlineOk = false;
5448
+ let helperBindings = '';
5449
+ let renderDecl = '';
5450
+ let inlineBlock = undefined;
5451
+ try {
5452
+ const root = server.config?.root || process.cwd();
5453
+ const abs = path.join(root, base.replace(/^\//, ''));
5454
+ let sfcSrc = '';
5455
+ try {
5456
+ sfcSrc = readFileSync(abs, 'utf-8');
5457
+ }
5458
+ catch { }
5459
+ if (sfcSrc) {
5460
+ const { descriptor } = parse(sfcSrc, { filename: abs });
5461
+ const tpl = descriptor.template?.content || '';
5462
+ if (tpl) {
5463
+ const id = createHash('md5').update(abs).digest('hex').slice(0, 8);
5464
+ const ct = compileTemplate({
5465
+ source: tpl,
5466
+ id,
5467
+ filename: abs,
5468
+ isProd: false,
5469
+ ssr: false,
5470
+ compilerOptions: {
5471
+ isCustomElement: (tag) => NS_NATIVE_TAGS.has(tag),
5472
+ },
5473
+ });
5474
+ let compiled = (ct && (ct.code || '')) || '';
5475
+ if (compiled) {
5476
+ // Prefer a full inline template block preserving hoists
5477
+ inlineBlock = buildInlineTemplateBlock(compiled) || undefined;
5478
+ if (inlineBlock) {
5479
+ inlineOk = true;
5480
+ }
5481
+ else {
5482
+ const extracted = extractTemplateRender(compiled);
5483
+ inlineOk = extracted.ok;
5484
+ helperBindings = extracted.helperBindings;
5485
+ renderDecl = extracted.renderDecl;
5486
+ inlineBlock = extracted.inlineBlock;
5487
+ }
5488
+ }
5489
+ }
5490
+ }
5491
+ }
5492
+ catch { }
5493
+ // If compiler-sfc path didn't succeed, attempt variant extraction once
5494
+ if (!inlineOk) {
5495
+ const extracted = extractTemplateRender(templateCode);
5496
+ inlineOk = extracted.ok;
5497
+ helperBindings = extracted.helperBindings;
5498
+ renderDecl = extracted.renderDecl;
5499
+ inlineBlock = extracted.inlineBlock;
5500
+ }
5501
+ let asm;
5502
+ if (inlineOk) {
5503
+ if (inlineBlock && inlineBlock.trim()) {
5504
+ 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');
5505
+ }
5506
+ else {
5507
+ 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');
5508
+ }
5509
+ }
5510
+ else {
5511
+ // Deterministic error path when template extraction failed
5512
+ res.statusCode = 500;
5513
+ res.end(`throw new Error('[sfc-asm] ${base}: template extraction failed');\nexport default {};`);
5514
+ return;
5515
+ }
5516
+ // Run full device processing so helper aliasing and globals are consistent in this path too
5517
+ let code = REQUIRE_GUARD_SNIPPET + asm;
5518
+ code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(base), base);
5519
+ try {
5520
+ code = ensureVersionedCoreImports(code, getServerOrigin(server), Number(ver));
5521
+ }
5522
+ catch { }
5523
+ code = rewriteImports(code, base, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, getServerOrigin(server));
5524
+ try {
5525
+ code = ensureDestructureCoreImports(code);
5526
+ }
5527
+ catch { }
5528
+ code = ensureVariableDynamicImportHelper(code);
5529
+ code = ensureGuardPlainDynamicImports(code, origin);
5530
+ try {
5531
+ const origin = getServerOrigin(server);
5532
+ code = ensureVersionedRtImports(code, origin, Number(ver));
5533
+ code = ACTIVE_STRATEGY.ensureVersionedImports(code, origin, Number(ver));
5534
+ code = ensureVersionedCoreImports(code, origin, Number(ver));
5535
+ }
5536
+ catch { }
5537
+ // Inline-template body path already runs processCodeForDevice (AST + sanitizers); no additional _defineComponent fix needed
5538
+ res.statusCode = 200;
5539
+ res.end(code);
5540
+ }
5541
+ catch (e) {
5542
+ res.statusCode = 500;
5543
+ res.end('export {}\n');
5544
+ }
3367
5545
  });
3368
5546
  wss.on('connection', async (ws) => {
3369
5547
  if (verbose)