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

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.
@@ -3,31 +3,27 @@ import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCore
3
3
  // AST tooling for robust transformations
4
4
  import { parse as babelParse } from '@babel/parser';
5
5
  import { genCode } from '../helpers/babel.js';
6
- import babelCore from '@babel/core';
7
6
  import traverse from '@babel/traverse';
8
7
  // Ensure traverse callable across CJS/ESM builds
9
8
  const babelTraverse = traverse?.default || traverse;
10
9
  import * as t from '@babel/types';
11
10
  import { existsSync, readFileSync, statSync } from 'fs';
12
11
  import { astNormalizeModuleImportsAndHelpers, astVerifyAndAnnotateDuplicates } from '../helpers/ast-normalizer.js';
13
- import { stripRtCoreSentinel, stripDanglingViteCjsImports } from '../helpers/sanitize.js';
12
+ import { stripDanglingViteCjsImports } from '../helpers/sanitize.js';
14
13
  import { WebSocketServer } from 'ws';
15
14
  import * as path from 'path';
16
15
  import { createHash } from 'crypto';
17
16
  import * as PAT from './constants.js';
18
17
  import { getVendorManifest, resolveVendorSpecifier } from '../shared/vendor/registry.js';
19
- import { getPackageJson, getProjectFilePath, getProjectRootPath } from '../../helpers/project.js';
18
+ import { getProjectRootPath } from '../../helpers/project.js';
20
19
  import { loadPrebuiltVendorManifest } from '../shared/vendor/manifest-loader.js';
21
20
  import '../vendor-bootstrap.js';
22
- import { NS_NATIVE_TAGS } from './compiler.js';
23
- import { vueSfcCompiler } from '../frameworks/vue/server/compiler.js';
24
21
  import { linkAngularPartialsIfNeeded } from '../frameworks/angular/server/linker.js';
25
22
  import { vueServerStrategy } from '../frameworks/vue/server/strategy.js';
26
23
  import { angularServerStrategy } from '../frameworks/angular/server/strategy.js';
27
24
  import { solidServerStrategy } from '../frameworks/solid/server/strategy.js';
28
25
  import { typescriptServerStrategy } from '../frameworks/typescript/server/strategy.js';
29
- import { buildInlineTemplateBlock, createProcessSfcCode, extractTemplateRender, processTemplateVariantMinimal } from '../frameworks/vue/server/sfc-transforms.js';
30
- import { astExtractImportsAndStripTypes } from '../helpers/ast-extract.js';
26
+ import { createProcessSfcCode } from '../frameworks/vue/server/sfc-transforms.js';
31
27
  import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
32
28
  import { buildRuntimeConfig, generateImportMap } from './import-map.js';
33
29
  import { getCliFlags } from '../../helpers/cli-flags.js';
@@ -38,17 +34,20 @@ import { classifyGraphUpsert, shouldBroadcastGraphUpsertDelta } from './websocke
38
34
  import { extractVitePrebundleId, filterExistingNodeModulesTransformCandidates, getBlockedDeviceNodeModulesReason, getFlattenedManifestMap, isCoreGlobalsReference, isEsmFrameworkPackageSpecifier, isLikelyNativeScriptPluginSpecifier, isLikelyNativeScriptRuntimePluginSpecifier, isNativeScriptCoreModule, isNativeScriptPluginModule, normalizeNativeScriptCoreSpecifier, normalizeNodeModulesSpecifier, resolveCandidateFilePath, resolveInternalRuntimePluginBareSpecifier, resolveNodeModulesPackageBoundary, resolveVendorFromCandidate, resolveVendorRouting, shouldPreserveBareRuntimePluginSubpathImport, stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule, viteDepsPathToBareSpecifier, } from './websocket-module-specifiers.js';
39
35
  import { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
40
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';
41
44
  import { createSharedTransformRequestRunner } from './shared-transform-request.js';
42
45
  export { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
43
46
  export { stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
44
47
  export { buildVersionedCoreMainBridgeModule, buildVersionedCoreSubpathAliasModule, collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest } from './websocket-core-bridge.js';
48
+ export { rewriteNsMImportPathForHmr } from './websocket-ns-m-paths.js';
45
49
  export { rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
46
50
  export { canonicalizeTransformRequestCacheKey, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, createSharedTransformRequestRunner, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload, classifyGraphUpsert, shouldBroadcastGraphUpsertDelta };
47
- const pluginTransformTypescript = (() => {
48
- const requireFromHere = createRequire(import.meta.url);
49
- const loaded = requireFromHere('@babel/plugin-transform-typescript');
50
- return loaded?.default || loaded;
51
- })();
52
51
  // Build a serialized process.env object from CLI --env.* flags.
53
52
  // This is injected into every HTTP-served module so app code referencing
54
53
  // process.env.TEST_ENV (etc.) works on device in HMR dev mode.
@@ -64,7 +63,6 @@ try {
64
63
  }
65
64
  catch { }
66
65
  const __processEnvJson = JSON.stringify(__processEnvEntries);
67
- const { parse, compileTemplate, compileScript } = vueSfcCompiler;
68
66
  const APP_ROOT_DIR = getProjectAppPath();
69
67
  const APP_VIRTUAL_PREFIX = getProjectAppVirtualPath();
70
68
  const APP_VIRTUAL_WITH_SLASH = `${APP_VIRTUAL_PREFIX}/`;
@@ -892,52 +890,6 @@ function toNodeModulesHttpModuleId(importPath) {
892
890
  }
893
891
  return `/ns/m/node_modules/${nodeModulesSpecifier}`;
894
892
  }
895
- export function rewriteNsMImportPathForHmr(p, ver, bootTaggedRequest) {
896
- const toHmrServeTag = (value) => {
897
- const raw = String(value ?? '').trim();
898
- if (!raw) {
899
- return 'v0';
900
- }
901
- if (raw === 'live' || /^n\d+$/i.test(raw) || /^v[^/]+$/i.test(raw)) {
902
- return raw;
903
- }
904
- if (/^\d+$/.test(raw)) {
905
- return `v${raw}`;
906
- }
907
- return raw;
908
- };
909
- if (!p || !p.startsWith('/ns/m/')) {
910
- return p;
911
- }
912
- const canonicalNodeModulesPath = p.replace(/^\/ns\/m\/__ns_boot__\/b1\/__ns_hmr__\/[^/]+\/node_modules\//, '/ns/m/node_modules/').replace(/^\/ns\/m\/__ns_hmr__\/[^/]+\/node_modules\//, '/ns/m/node_modules/');
913
- if (canonicalNodeModulesPath.startsWith('/ns/m/node_modules/')) {
914
- return canonicalNodeModulesPath;
915
- }
916
- if (canonicalNodeModulesPath.startsWith('/ns/m/__ns_boot__/')) {
917
- return canonicalNodeModulesPath;
918
- }
919
- if (canonicalNodeModulesPath.startsWith('/ns/m/__ns_hmr__/')) {
920
- return bootTaggedRequest ? `/ns/m/__ns_boot__/b1${canonicalNodeModulesPath.slice('/ns/m'.length)}` : canonicalNodeModulesPath;
921
- }
922
- const tag = toHmrServeTag(ver);
923
- const hmrPrefix = `/ns/m/__ns_hmr__/${tag}`;
924
- const bootHmrPrefix = `/ns/m/__ns_boot__/b1/__ns_hmr__/${tag}`;
925
- return (bootTaggedRequest ? bootHmrPrefix : hmrPrefix) + canonicalNodeModulesPath.slice('/ns/m'.length);
926
- }
927
- function getNumericServeVersionTag(tag, fallback) {
928
- const raw = String(tag || '').trim();
929
- if (!raw) {
930
- return fallback;
931
- }
932
- const versionMatch = raw.match(/^v(\d+)$/);
933
- if (versionMatch?.[1]) {
934
- return Number(versionMatch[1]);
935
- }
936
- if (/^\d+$/.test(raw)) {
937
- return Number(raw);
938
- }
939
- return fallback;
940
- }
941
893
  function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
942
894
  if (!spec || typeof spec !== 'string') {
943
895
  return null;
@@ -2870,114 +2822,21 @@ function createHmrWebSocketPlugin(opts) {
2870
2822
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
2871
2823
  res.setHeader('Pragma', 'no-cache');
2872
2824
  res.setHeader('Expires', '0');
2873
- // Support both query (?path=/abs) and path-style (/ns/m/abs)
2874
- let spec = urlObj.searchParams.get('path') || '';
2875
- // Optional graph version pin for deterministic boot
2876
- let forcedVer = urlObj.searchParams.get('v');
2877
- let bootTaggedRequest = false;
2878
- if (!spec) {
2879
- const base = '/ns/m';
2880
- let rest = urlObj.pathname.slice(base.length);
2881
- if (rest && rest !== '/')
2882
- spec = rest;
2883
- }
2884
- // Special-case stub for anomalous '@' imports emitted as '/__invalid_at__.mjs'
2885
- if (spec === '/__invalid_at__.mjs' || spec === '__invalid_at__.mjs') {
2886
- res.statusCode = 200;
2887
- res.end("// invalid '@' import stub\nexport {}\n");
2888
- return;
2889
- }
2890
- if (!spec) {
2891
- res.statusCode = 200;
2892
- res.end('export {}\n');
2893
- return;
2894
- }
2895
2825
  const serverRoot = (server.config?.root || process.cwd());
2896
- spec = spec.replace(/[?#].*$/, '');
2897
- // Accept path-based boot/HMR prefixes:
2898
- // /ns/m/__ns_boot__/b1/<real-spec>
2899
- // /ns/m/__ns_hmr__/<tag>/<real-spec>
2900
- // /ns/m/__ns_boot__/b1/__ns_hmr__/<tag>/<real-spec>
2901
- // The iOS HTTP ESM loader canonicalizes cache keys by stripping query params,
2902
- // so we must carry the cache-buster in the path.
2903
- try {
2904
- const decorated = stripDecoratedServePrefixes(spec);
2905
- spec = decorated.cleanedSpec;
2906
- bootTaggedRequest = decorated.bootTaggedRequest;
2907
- forcedVer || (forcedVer = decorated.forcedVer);
2908
- }
2909
- catch { }
2910
- // Normalize absolute filesystem paths back to project-relative ids (e.g. /src/app.ts)
2911
- try {
2912
- const toPosix = (p) => p.replace(/\\/g, '/');
2913
- const rootPosix = toPosix(serverRoot);
2914
- const specPosix = toPosix(spec);
2915
- // If spec is an absolute path under the project root, convert to '/'+relative
2916
- const isAbsFs = /^\//.test(specPosix) || /^[A-Za-z]:\//.test(spec); // posix or win drive
2917
- if (isAbsFs) {
2918
- let rel = specPosix.startsWith(rootPosix) ? specPosix.slice(rootPosix.length) : require('path').posix.relative(rootPosix, specPosix);
2919
- if (!rel.startsWith('..')) {
2920
- if (!rel.startsWith('/'))
2921
- rel = '/' + rel;
2922
- // Ensure leading '/src' style when path maps into src
2923
- spec = rel;
2924
- }
2925
- }
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);
2832
+ return;
2926
2833
  }
2927
- catch { }
2834
+ let { spec, forcedVer, bootTaggedRequest, transformCandidates, candidates } = requestContextResult.value;
2928
2835
  // Serve Vite virtual modules (/@id/ prefix). These are internal
2929
2836
  // virtual modules (e.g., \0nsvite:nsconfig-json for ~/package.json)
2930
2837
  // that don't exist on disk. Decode the ID and load via plugin container.
2931
- if (spec.startsWith('/@id/')) {
2932
- try {
2933
- // First try Vite's transform pipeline directly
2934
- const vr = await sharedTransformRequest(spec);
2935
- if (vr?.code) {
2936
- res.statusCode = 200;
2937
- res.end(vr.code);
2938
- return;
2939
- }
2940
- }
2941
- catch { }
2942
- try {
2943
- // Fallback: decode the virtual module ID (__x00__ → \0) and
2944
- // load through the plugin container directly
2945
- const rawId = spec.slice('/@id/'.length).replace(/__x00__/g, '\0');
2946
- const loadResult = await server.pluginContainer.load(rawId);
2947
- if (loadResult) {
2948
- const code = typeof loadResult === 'string' ? loadResult : loadResult.code;
2949
- if (code) {
2950
- res.statusCode = 200;
2951
- res.end(code);
2952
- return;
2953
- }
2954
- }
2955
- }
2956
- catch { }
2957
- }
2958
- if (spec.startsWith('@/'))
2959
- spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
2960
- if (spec.startsWith('./'))
2961
- spec = spec.slice(1);
2962
- const blockedNodeModulesReason = getBlockedDeviceNodeModulesReason(spec);
2963
- if (blockedNodeModulesReason) {
2964
- res.statusCode = 404;
2965
- res.end(`// [ns:m] blocked device import\nthrow new Error(${JSON.stringify(`[ns/m] ${blockedNodeModulesReason}`)});\nexport {};\n`);
2966
- return;
2967
- }
2968
- if (!spec.startsWith('/'))
2969
- spec = '/' + spec;
2970
- const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
2971
- const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
2972
- 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'];
2973
- const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, serverRoot);
2974
2838
  let transformed = null;
2975
2839
  let resolvedCandidate = null;
2976
- const rawExplicitModule = tryReadRawExplicitJavaScriptModule(spec, serverRoot);
2977
- if (rawExplicitModule) {
2978
- transformed = { code: rawExplicitModule.code };
2979
- resolvedCandidate = rawExplicitModule.resolvedId;
2980
- }
2981
2840
  // Queue and dedupe transformRequest calls so heavy app graphs do not
2982
2841
  // overwhelm Vite with concurrent work. Slow-transform warnings start only
2983
2842
  // when the transform actually begins executing, and requests stay pending
@@ -2985,86 +2844,15 @@ function createHmrWebSocketPlugin(opts) {
2985
2844
  const transformWithTimeout = (url, timeoutMs = 120000) => {
2986
2845
  return sharedTransformRequest(url, timeoutMs);
2987
2846
  };
2988
- if (!transformed?.code) {
2989
- for (const cand of transformCandidates) {
2990
- try {
2991
- const r = await transformWithTimeout(cand);
2992
- if (r?.code) {
2993
- transformed = r;
2994
- resolvedCandidate = cand;
2995
- break;
2996
- }
2997
- }
2998
- catch { }
2999
- }
3000
- }
3001
- // Fallback 1: ask Vite to resolve the id, then transform the resolved id (handles aliases and virtual ids)
3002
- if (!transformed?.code) {
3003
- try {
3004
- const rid = await server.pluginContainer?.resolveId?.(spec, undefined);
3005
- const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
3006
- if (ridStr) {
3007
- const r = await transformWithTimeout(ridStr);
3008
- if (r?.code) {
3009
- transformed = r;
3010
- resolvedCandidate = ridStr;
3011
- }
3012
- }
3013
- }
3014
- catch { }
3015
- }
3016
- // Fallback 1b: if spec is a /node_modules/ path, extract bare specifier
3017
- // and try resolveId with that. This handles package.json "exports" field
3018
- // resolution (e.g., solid-js/jsx-runtime → solid-js/dist/solid.js).
3019
- if (!transformed?.code && spec.includes('/node_modules/')) {
3020
- try {
3021
- const nmIdx = spec.lastIndexOf('/node_modules/');
3022
- const bare = spec.slice(nmIdx + '/node_modules/'.length);
3023
- if (bare && !bare.startsWith('.')) {
3024
- const rid = await server.pluginContainer?.resolveId?.(bare, undefined);
3025
- const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
3026
- if (ridStr) {
3027
- const r = await sharedTransformRequest(ridStr);
3028
- if (r?.code) {
3029
- transformed = r;
3030
- resolvedCandidate = ridStr;
3031
- }
3032
- }
3033
- }
3034
- }
3035
- catch { }
3036
- }
3037
- // Fallback 2: try /@fs absolute path under project root (Vite file system alias)
3038
- if (!transformed?.code) {
3039
- try {
3040
- const toPosix = (p) => p.replace(/\\/g, '/');
3041
- const rootPosix = toPosix(serverRoot).replace(/\/$/, '');
3042
- const absPosix = `${rootPosix}${spec.startsWith('/') ? '' : '/'}${spec}`;
3043
- const fsId = `/@fs${absPosix}`;
3044
- if (resolveCandidateFilePath(fsId, serverRoot)) {
3045
- const r = await transformWithTimeout(fsId);
3046
- if (r?.code) {
3047
- transformed = r;
3048
- resolvedCandidate = fsId;
3049
- }
3050
- }
3051
- }
3052
- catch { }
3053
- }
3054
- // Fallback 3: try adding ?import to hint Vite's transform pipeline
3055
- if (!transformed?.code) {
3056
- for (const cand of transformCandidates) {
3057
- try {
3058
- const r = await transformWithTimeout(`${cand}${cand.includes('?') ? '&' : '?'}import`);
3059
- if (r?.code) {
3060
- transformed = r;
3061
- resolvedCandidate = `${cand}?import`;
3062
- break;
3063
- }
3064
- }
3065
- catch { }
3066
- }
3067
- }
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
+ }));
3068
2856
  // Solid HMR: patch @@solid-refresh's $$refreshESM to do inline patching
3069
2857
  // during module re-evaluation instead of deferring to hot.accept() callback.
3070
2858
  // In NativeScript's HTTP ESM environment, accept callbacks are registered
@@ -3267,169 +3055,47 @@ export const piniaSymbol = p.piniaSymbol;
3267
3055
  }
3268
3056
  }
3269
3057
  }
3270
- let code = transformed.code;
3271
- // Prepend guard to capture any URL-based require attempts
3272
- code = REQUIRE_GUARD_SNIPPET + code;
3273
- code = cleanCode(code);
3274
- const isNodeMod = /(?:^|\/)node_modules\//.test(resolvedCandidate || spec || '');
3275
- code = processCodeForDevice(code, false, true, isNodeMod, resolvedCandidate || spec);
3276
- // Solid HMR: The NativeScript iOS/Android runtime provides import.meta.hot
3277
- // natively (via InitializeImportMetaHot in HMRSupport.mm) with C++-backed
3278
- // persistent hot.data that survives across module re-evaluations.
3279
- // cleanCode() strips Vite's __vite__createHotContext assignment, which is
3280
- // correct — the runtime's native hot context is better.
3281
3058
  const projectRoot = server.config?.root || process.cwd();
3282
3059
  const serverOrigin = getServerOrigin(server);
3283
- if (ACTIVE_STRATEGY?.flavor === 'angular') {
3284
- code = prepareAngularEntryForDevice(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
3285
- }
3286
- else {
3287
- code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
3288
- }
3289
- // Expand `export * from "url"` into explicit named re-exports.
3290
- // NativeScript's HTTP ESM loader may not propagate star-re-exports across
3291
- // HTTP module boundaries (the namespace object gets direct exports but
3292
- // misses re-exported names). By expanding to `export { a, b } from "url"`,
3293
- // the engine sees explicit named exports and resolves them correctly.
3294
- try {
3295
- code = await expandStarExports(code, server, server.config?.root || process.cwd(), verbose);
3296
- }
3297
- catch (e) {
3298
- if (verbose)
3299
- console.warn('[ns/m] export* expansion failed:', e?.message);
3300
- }
3301
- // Dedupe any /ns/rt named imports that duplicate destructured bindings off default /ns/rt
3302
- try {
3303
- code = dedupeRtNamedImportsAgainstDestructures(code);
3304
- }
3305
- catch { }
3306
- code = ensureVariableDynamicImportHelper(code);
3307
- // Final safety: guard any plain dynamic import(...) occurrences to reroute anomalous '@' specs
3308
- try {
3309
- code = ensureGuardPlainDynamicImports(code, getServerOrigin(server));
3310
- }
3311
- catch { }
3312
- // Extra hardening: normalize any remaining core references to the unified bridge
3313
- // - Stray string-literals
3314
- // - Dangling `from` merges
3315
- // - Any spec (including /node_modules resolves) that still references '@nativescript/core'
3316
- // Do this right before the final fast-fail assertion. If a rewrite occurred, add a small marker for diagnostics.
3317
- try {
3318
- const __before = code;
3319
- code = normalizeStrayCoreStringLiterals(code);
3320
- code = fixDanglingCoreFrom(code);
3321
- code = normalizeAnyCoreSpecToBridge(code);
3322
- if (code !== __before) {
3323
- code = `// [hmr-sanitize] core-literal->bridge\n` + code;
3324
- }
3325
- }
3326
- catch { }
3327
- // Final pass: deduplicate/resolve any bare-specifier imports that slipped
3328
- // through the pipeline (e.g., extracted from JSDoc comments by import-splitting
3329
- // regexes, or injected by the Angular linker on already-resolved code).
3060
+ let code;
3330
3061
  try {
3331
- code = deduplicateLinkerImports(code);
3332
- }
3333
- catch { }
3334
- // CJS/UMD wrapping: if a module uses module.exports but has no ESM export default,
3335
- // wrap it with CJS shims so the device HTTP ESM loader can consume it.
3336
- // This handles npm packages that use CommonJS but aren't pre-bundled by Vite.
3337
- //
3338
- // Key constraints this must handle:
3339
- // - CJS modules often declare local vars with the same names as their exports
3340
- // (e.g. `function createLTTB() {...}; exports.createLTTB = createLTTB;`)
3341
- // so `export var { createLTTB }` would cause a duplicate declaration.
3342
- // - UMD modules reference `this` at top level (undefined in ESM) but
3343
- // typically fall back to `self` or `globalThis`.
3344
- // - `module`, `exports` must be shims since they don't exist in ESM.
3345
- try {
3346
- code = wrapCommonJsModuleForDevice(code);
3347
- }
3348
- catch { }
3349
- try {
3350
- assertNoOptimizedArtifacts(code, `NS M ${resolvedCandidate || spec}`);
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
+ });
3351
3094
  }
3352
3095
  catch (e) {
3353
3096
  res.statusCode = 500;
3354
3097
  return void res.end(`throw new Error(${JSON.stringify(e?.message || String(e))});\nexport {};`);
3355
3098
  }
3356
- // Defensive export normalization: if a module defines `routes` and only exports it named,
3357
- // add a default export alias so both `import { routes }` and `import routes` work.
3358
- try {
3359
- if (!/\bexport\s+default\b/.test(code)) {
3360
- const hasNamedRoutes = /\bexport\s*\{\s*routes\s*\}/.test(code);
3361
- const hasConstRoutes = /\bconst\s+routes\s*=/.test(code) || /\bvar\s+routes\s*=/.test(code) || /\blet\s+routes\s*=/.test(code);
3362
- if (hasNamedRoutes && hasConstRoutes) {
3363
- code += `\nexport default routes;\n`;
3364
- }
3365
- }
3366
- }
3367
- catch { }
3368
- try {
3369
- const verNum = getNumericServeVersionTag(forcedVer, Number(graphVersion || 0));
3370
- code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
3371
- code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
3372
- code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
3373
- }
3374
- catch { }
3375
- // Finalize: stamp all internal /ns/m imports with PATH-based cache busting.
3376
- // IMPORTANT: use path prefix (not ?v= query) because the iOS HTTP ESM loader
3377
- // strips query params when computing module cache keys, so ?v= doesn't bust the V8 cache.
3378
- try {
3379
- const ver = (() => {
3380
- const raw = String(forcedVer || '').trim();
3381
- if (raw) {
3382
- if (raw === 'live' || /^n\d+$/i.test(raw) || /^v[^/]+$/i.test(raw)) {
3383
- return raw;
3384
- }
3385
- if (/^\d+$/.test(raw)) {
3386
- return `v${raw}`;
3387
- }
3388
- }
3389
- return `v${String(graphVersion || 0)}`;
3390
- })();
3391
- const origin = getServerOrigin(server);
3392
- const rewritePath = (p) => rewriteNsMImportPathForHmr(p, ver, bootTaggedRequest);
3393
- // 1) Static imports: import ... from "/ns/m/..."
3394
- code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3395
- // 2) Side-effect imports: import "/ns/m/..."
3396
- code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3397
- // 3) Dynamic imports: import("/ns/m/...")
3398
- code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3399
- // 4) new URL("/ns/m/...", import.meta.url)
3400
- code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
3401
- // 5) __ns_import(new URL('/ns/m/...', import.meta.url).href)
3402
- 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}`);
3403
- // 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href → "${origin}/ns/m/__ns_hmr__/..."
3404
- try {
3405
- code = code.replace(/new\s+URL\(\s*["'](\/ns\/m\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}${rewritePath(p1)}`)}`);
3406
- }
3407
- catch { }
3408
- // 7) Also fix SFC new URL('/ns/sfc/...', import.meta.url).href → "${origin}/ns/sfc/<ver>/..."
3409
- try {
3410
- 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}`)}`);
3411
- }
3412
- catch { }
3413
- }
3414
- catch { }
3415
- // Final guard: eliminate any lingering named imports from /ns/core to avoid
3416
- // evaluation-time "does not provide an export named ..." in the device runtime.
3417
- try {
3418
- code = ensureDestructureCoreImports(code);
3419
- }
3420
- catch { }
3421
- // Boot-time module graph progress: while the app is still replacing the
3422
- // placeholder, emit lightweight progress updates as /ns/m modules begin
3423
- // evaluating. This keeps the overlay moving during large initial graphs.
3424
- try {
3425
- if (bootTaggedRequest) {
3426
- const bootModuleLabel = String(spec || '').replace(/\\/g, '/');
3427
- const bootProgressSnippet = buildBootProgressSnippet(bootModuleLabel);
3428
- code = bootProgressSnippet + code;
3429
- code = hoistTopLevelStaticImports(code);
3430
- }
3431
- }
3432
- catch { }
3433
3099
  // Dev-only: link-check static imports to surface missing bindings early
3434
3100
  try {
3435
3101
  const devCheck = process.env.NODE_ENV !== 'production';
@@ -3558,188 +3224,21 @@ export const piniaSymbol = p.piniaSymbol;
3558
3224
  res.end('export {}\n');
3559
3225
  }
3560
3226
  });
3561
- // 2.5) ESM runtime bridge for NativeScript-Vue: GET /ns/rt
3562
- // Provides a single authoritative source of Vue helpers bound to the NativeScript renderer.
3563
- // V2.1: Lazy ensure bridge — does not statically import vue. It lazily resolves helpers from
3564
- // globalThis or vendor registry/require on first evaluation, then exports references so SFCs
3565
- // can immediately call them during module evaluation.
3566
- server.middlewares.use(async (req, res, next) => {
3567
- try {
3568
- const urlObj = new URL(req.url || '', 'http://localhost');
3569
- // Accept only /ns/rt and /ns/rt/<ver> for cache-busting semantics
3570
- if (!(urlObj.pathname === '/ns/rt' || /^\/ns\/rt\/[\d]+$/.test(urlObj.pathname)))
3571
- return next();
3572
- res.setHeader('Access-Control-Allow-Origin', '*');
3573
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
3574
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3575
- res.setHeader('Pragma', 'no-cache');
3576
- res.setHeader('Expires', '0');
3577
- const rtVerSeg = urlObj.pathname.replace(/^\/ns\/rt\/?/, '');
3578
- const rtVer = /^[0-9]+$/.test(rtVerSeg) ? rtVerSeg : String(graphVersion || 0);
3579
- const origin = getServerOrigin(server);
3580
- let code = `// [ns-rt][v2.3] NativeScript-Vue runtime bridge (module-scoped cache, no globals)\n` +
3581
- `const __origin = ((typeof globalThis !== 'undefined' && globalThis && globalThis.__NS_HTTP_ORIGIN__) || (new URL(import.meta.url)).origin);\n` +
3582
- `let __ns_core_bridge = null; try { import(__origin + "/ns/core/${rtVer}").then(m => { __ns_core_bridge = m; }).catch(() => {}); } catch {}\n` +
3583
- `const g = globalThis;\n` +
3584
- `const reg = (g.__nsVendorRegistry ||= new Map());\n` +
3585
- `const req = reg && reg.get ? (g.__nsVendorRequire || g.__nsRequire || g.require) : (g.__nsRequire || g.require);\n` +
3586
- `let __cached_rt = null;\n` +
3587
- `let __cached_vm = null;\n` +
3588
- `const __RT_REALM_TAG = (globalThis.__NS_RT_REALM__ ||= Math.random().toString(36).slice(2));\n` +
3589
- // Unconditional one-shot evaluation marker to confirm bridge is executed on device
3590
- `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` +
3591
- `function __ensure(){\n` +
3592
- ` if (__cached_rt) return __cached_rt;\n` +
3593
- ` let vm = null;\n` +
3594
- ` try { vm = reg && reg.has && reg.has('nativescript-vue') ? reg.get('nativescript-vue') : (typeof req==='function' ? req('nativescript-vue') : null); } catch {}\n` +
3595
- ` if (!vm) { try { vm = reg && reg.has && reg.has('vue') ? reg.get('vue') : (typeof req==='function' ? req('vue') : null); } catch {} }\n` +
3596
- ` const rt = (vm && (vm.default ?? vm)) || {};\n` +
3597
- ` __cached_vm = vm;\n` +
3598
- ` __cached_rt = rt;\n` +
3599
- ` return rt;\n` +
3600
- `}\n` +
3601
- `// Soft-globals for @nativescript/core when missing (dev-only safety)\n` +
3602
- `try {\n` +
3603
- ` const dev = typeof __DEV__ !== 'undefined' ? __DEV__ : true;\n` +
3604
- ` if (dev) {\n` +
3605
- ` 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` +
3606
- ` if (ns) {\n` +
3607
- ` if (!g.Frame && ns.Frame) g.Frame = ns.Frame;\n` +
3608
- ` if (!g.Page && ns.Page) g.Page = ns.Page;\n` +
3609
- ` if (!g.Application && (ns.Application||ns.app||ns.application)) g.Application = (ns.Application||ns.app||ns.application);\n` +
3610
- ` }\n` +
3611
- ` }\n` +
3612
- `} catch {}\n` +
3613
- `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` +
3614
- `export const __realm = __RT_REALM_TAG;\n` +
3615
- `export const defineComponent = (...a) => (__get('defineComponent'))(...a);\n` +
3616
- `export const resolveComponent = (...a) => (__ensure().resolveComponent)(...a);\n` +
3617
- `export const createVNode = (...a) => (__ensure().createVNode)(...a);\n` +
3618
- `export const createTextVNode = (...a) => (__ensure().createTextVNode)(...a);\n` +
3619
- `export const createCommentVNode = (...a) => (__ensure().createCommentVNode)(...a);\n` +
3620
- `export const Fragment = (__ensure().Fragment);\n` +
3621
- `export const Teleport = (__ensure().Teleport);\n` +
3622
- `export const Transition = (__ensure().Transition);\n` +
3623
- `export const TransitionGroup = (__ensure().TransitionGroup);\n` +
3624
- `export const KeepAlive = (__ensure().KeepAlive);\n` +
3625
- `export const Suspense = (__ensure().Suspense);\n` +
3626
- `export const withCtx = (...a) => (__ensure().withCtx)(...a);\n` +
3627
- `export const openBlock = (...a) => (__ensure().openBlock)(...a);\n` +
3628
- `export const createBlock = (...a) => (__ensure().createBlock)(...a);\n` +
3629
- `export const createElementVNode = (...a) => (__ensure().createElementVNode)(...a);\n` +
3630
- `export const createElementBlock = (...a) => (__ensure().createElementBlock)(...a);\n` +
3631
- `export const renderSlot = (...a) => (__ensure().renderSlot)(...a);\n` +
3632
- `export const mergeProps = (...a) => (__ensure().mergeProps)(...a);\n` +
3633
- `export const toHandlers = (...a) => (__ensure().toHandlers)(...a);\n` +
3634
- `export const renderList = (...a) => (__ensure().renderList)(...a);\n` +
3635
- `export const normalizeProps = (...a) => (__ensure().normalizeProps)(...a);\n` +
3636
- `export const guardReactiveProps = (...a) => (__ensure().guardReactiveProps)(...a);\n` +
3637
- `export const normalizeClass = (...a) => (__ensure().normalizeClass)(...a);\n` +
3638
- `export const normalizeStyle = (...a) => (__ensure().normalizeStyle)(...a);\n` +
3639
- `export const toDisplayString = (...a) => (__ensure().toDisplayString)(...a);\n` +
3640
- `export const withDirectives = (...a) => (__ensure().withDirectives)(...a);\n` +
3641
- `export const resolveDirective = (...a) => (__ensure().resolveDirective)(...a);\n` +
3642
- `export const withModifiers = (...a) => (__ensure().withModifiers)(...a);\n` +
3643
- `export const withKeys = (...a) => (__ensure().withKeys)(...a);\n` +
3644
- `export const resolveDynamicComponent = (...a) => (__ensure().resolveDynamicComponent)(...a);\n` +
3645
- `export const isVNode = (...a) => (__ensure().isVNode)(...a);\n` +
3646
- `export const cloneVNode = (...a) => (__ensure().cloneVNode)(...a);\n` +
3647
- `export const isRef = (...a) => (__ensure().isRef)(...a);\n` +
3648
- `export const ref = (...a) => (__ensure().ref)(...a);\n` +
3649
- `export const shallowRef = (...a) => (__ensure().shallowRef)(...a);\n` +
3650
- `export const unref = (...a) => (__ensure().unref)(...a);\n` +
3651
- `export const computed = (...a) => (__ensure().computed)(...a);\n` +
3652
- `export const reactive = (...a) => (__ensure().reactive)(...a);\n` +
3653
- `export const readonly = (...a) => (__ensure().readonly)(...a);\n` +
3654
- `export const isReactive = (...a) => (__ensure().isReactive)(...a);\n` +
3655
- `export const isReadonly = (...a) => (__ensure().isReadonly)(...a);\n` +
3656
- `export const toRaw = (...a) => (__ensure().toRaw)(...a);\n` +
3657
- `export const markRaw = (...a) => (__ensure().markRaw)(...a);\n` +
3658
- `export const shallowReactive = (...a) => (__ensure().shallowReactive)(...a);\n` +
3659
- `export const shallowReadonly = (...a) => (__ensure().shallowReadonly)(...a);\n` +
3660
- `export const watch = (...a) => (__ensure().watch)(...a);\n` +
3661
- `export const watchEffect = (...a) => (__ensure().watchEffect)(...a);\n` +
3662
- `export const watchPostEffect = (...a) => (__ensure().watchPostEffect)(...a);\n` +
3663
- `export const watchSyncEffect = (...a) => (__ensure().watchSyncEffect)(...a);\n` +
3664
- `export const onBeforeMount = (...a) => (__ensure().onBeforeMount)(...a);\n` +
3665
- `export const onMounted = (...a) => (__ensure().onMounted)(...a);\n` +
3666
- `export const onBeforeUpdate = (...a) => (__ensure().onBeforeUpdate)(...a);\n` +
3667
- `export const onUpdated = (...a) => (__ensure().onUpdated)(...a);\n` +
3668
- `export const onBeforeUnmount = (...a) => (__ensure().onBeforeUnmount)(...a);\n` +
3669
- `export const onUnmounted = (...a) => (__ensure().onUnmounted)(...a);\n` +
3670
- `export const onActivated = (...a) => (__ensure().onActivated)(...a);\n` +
3671
- `export const onDeactivated = (...a) => (__ensure().onDeactivated)(...a);\n` +
3672
- `export const onErrorCaptured = (...a) => (__ensure().onErrorCaptured)(...a);\n` +
3673
- `export const onRenderTracked = (...a) => (__ensure().onRenderTracked)(...a);\n` +
3674
- `export const onRenderTriggered = (...a) => (__ensure().onRenderTriggered)(...a);\n` +
3675
- `export const nextTick = (...a) => (__ensure().nextTick)(...a);\n` +
3676
- `export const h = (...a) => (__ensure().h)(...a);\n` +
3677
- `export const provide = (...a) => (__ensure().provide)(...a);\n` +
3678
- `export const inject = (...a) => (__ensure().inject)(...a);\n` +
3679
- `export const vShow = (__ensure().vShow);\n` +
3680
- `export const createApp = (...a) => (__ensure().createApp)(...a);\n` +
3681
- `export const registerElement = (...a) => (__ensure().registerElement)(...a);\n` +
3682
- `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` +
3683
- `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` +
3684
- `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` +
3685
- `export default {\n` +
3686
- ` defineComponent, resolveComponent, createVNode, createTextVNode, createCommentVNode,\n` +
3687
- ` Fragment, Teleport, Transition, TransitionGroup, KeepAlive, Suspense, withCtx, openBlock,\n` +
3688
- ` createBlock, createElementVNode, createElementBlock, renderSlot, mergeProps, toHandlers,\n` +
3689
- ` renderList, normalizeProps, guardReactiveProps, normalizeClass, normalizeStyle, toDisplayString,\n` +
3690
- ` withDirectives, resolveDirective, withModifiers, withKeys, resolveDynamicComponent,\n` +
3691
- ` isVNode, cloneVNode, isRef, ref, shallowRef, unref, computed, reactive, readonly, isReactive, isReadonly, toRaw, markRaw, shallowReactive, shallowReadonly,\n` +
3692
- ` watch, watchEffect, watchPostEffect, watchSyncEffect, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,\n` +
3693
- ` onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured, onRenderTracked, onRenderTriggered, nextTick, h, provide, inject, vShow, createApp, registerElement,\n` +
3694
- ` $navigateTo, $navigateBack, $showModal\n` +
3695
- `};\n`;
3696
- // Prepend guard and ship (harmless, keeps diagnostics consistent)
3697
- code = REQUIRE_GUARD_SNIPPET + code;
3698
- res.statusCode = 200;
3699
- res.end(code);
3700
- }
3701
- catch (e) {
3702
- res.statusCode = 500;
3703
- res.end('export {}\n');
3704
- }
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,
3705
3235
  });
3706
3236
  // 2.55) Dev-only vendor import unifier: rewrite 'vue'/'nativescript-vue' to /ns/rt/<ver>
3707
3237
  // This ensures plugins and app share a single Vue/NativeScript-Vue instance/realm.
3708
- server.middlewares.use(async (req, res, next) => {
3709
- try {
3710
- const urlObj = new URL(req.url || '', 'http://localhost');
3711
- const p = urlObj.pathname || '';
3712
- // Ignore our own core/rt bridge endpoints and non-JS assets, but DO allow /ns/m/* through
3713
- if (/^\/ns\/(?:rt|core)(?:\/|$)/.test(p))
3714
- return next();
3715
- if (!/(\.m?js$|\.ts$|\/node_modules\/|\/\.vite\/deps\/|^\/@id\/|^\/@fs\/)/.test(p))
3716
- return next();
3717
- if (/\.css($|\?)/.test(p))
3718
- return next();
3719
- const reqUrl = req.url || '';
3720
- const transformed = await server.transformRequest(reqUrl);
3721
- if (!transformed?.code)
3722
- return next();
3723
- const origin = getServerOrigin(server);
3724
- const ver = Number(graphVersion || 0);
3725
- const rewrite = ACTIVE_STRATEGY.rewriteVendorSpec;
3726
- if (!rewrite)
3727
- return next();
3728
- const before = transformed.code;
3729
- const code = rewrite(before, origin, ver);
3730
- if (code === before)
3731
- return next();
3732
- res.setHeader('Access-Control-Allow-Origin', '*');
3733
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
3734
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3735
- res.setHeader('Pragma', 'no-cache');
3736
- res.setHeader('Expires', '0');
3737
- res.statusCode = 200;
3738
- res.end(code);
3739
- }
3740
- catch {
3741
- return next();
3742
- }
3238
+ registerVendorUnifierHandler(server, {
3239
+ getGraphVersion: () => Number(graphVersion || 0),
3240
+ getServerOrigin,
3241
+ getStrategy: () => ACTIVE_STRATEGY,
3743
3242
  });
3744
3243
  // 2.6) ESM bridge for @nativescript/core: GET /ns/core[/<ver>][?p=sub/path]
3745
3244
  server.middlewares.use(async (req, res, next) => {
@@ -3838,1462 +3337,33 @@ export const piniaSymbol = p.piniaSymbol;
3838
3337
  next();
3839
3338
  }
3840
3339
  });
3841
- // 2.6a) Serve compiled entry runtime module: GET /ns/entry-rt[?v=<ver>]
3842
- server.middlewares.use(async (req, res, next) => {
3843
- try {
3844
- const urlObj = new URL(req.url || '', 'http://localhost');
3845
- if (!(urlObj.pathname === '/ns/entry-rt'))
3846
- return next();
3847
- try {
3848
- if (verbose) {
3849
- const ra = req.socket?.remoteAddress;
3850
- const rp = req.socket?.remotePort;
3851
- console.log('[hmr-http] GET /ns/entry-rt from', ra + (rp ? ':' + rp : ''));
3852
- }
3853
- }
3854
- catch { }
3855
- res.setHeader('Access-Control-Allow-Origin', '*');
3856
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
3857
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3858
- res.setHeader('Pragma', 'no-cache');
3859
- res.setHeader('Expires', '0');
3860
- let content = '';
3861
- try {
3862
- const _req = createRequire(import.meta.url);
3863
- const entryRtPath = _req.resolve('@nativescript/vite/hmr/entry-runtime.js');
3864
- content = readFileSync(entryRtPath, 'utf-8');
3865
- }
3866
- catch (e) {
3867
- // .js not found (source tree without build) — transform .ts on the fly
3868
- try {
3869
- const tsPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'entry-runtime.ts');
3870
- if (existsSync(tsPath)) {
3871
- const tsSource = readFileSync(tsPath, 'utf-8');
3872
- const result = babelCore.transformSync(tsSource, {
3873
- filename: tsPath,
3874
- plugins: [[pluginTransformTypescript, { isTSX: false, allowDeclareFields: true }]],
3875
- sourceType: 'module',
3876
- });
3877
- if (result?.code) {
3878
- content = result.code;
3879
- }
3880
- }
3881
- }
3882
- catch (e2) {
3883
- if (verbose)
3884
- console.warn('[hmr-http] entry-runtime.ts transform failed', e2);
3885
- }
3886
- if (!content) {
3887
- content = 'export default async function start(){ console.error("[/ns/entry-rt] not found"); }\n';
3888
- }
3889
- }
3890
- console.log('[hmr-http] /ns/entry-rt serving', content.length, 'bytes');
3891
- res.statusCode = 200;
3892
- res.end(content);
3893
- }
3894
- catch (e) {
3895
- console.warn('[hmr-http] /ns/entry-rt error', e);
3896
- next();
3897
- }
3898
- });
3899
- // 2.6b) HTTP-only app entry endpoint: GET /ns/entry[/<ver>]
3900
- // Thin wrapper that imports the compiled entry runtime and starts it with parameters.
3901
- server.middlewares.use(async (req, res, next) => {
3902
- try {
3903
- const urlObj = new URL(req.url || '', 'http://localhost');
3904
- if (!(urlObj.pathname === '/ns/entry' || /^\/ns\/entry\/[\d]+$/.test(urlObj.pathname)))
3905
- return next();
3906
- try {
3907
- if (verbose) {
3908
- const ra = req.socket?.remoteAddress;
3909
- const rp = req.socket?.remotePort;
3910
- console.log('[hmr-http] GET /ns/entry from', ra + (rp ? ':' + rp : ''));
3911
- }
3912
- }
3913
- catch { }
3914
- const verSeg = urlObj.pathname.replace(/^\/ns\/entry\/?/, '');
3915
- // Resolve app main entry to an absolute path-like key used by /ns/m
3916
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3917
- res.setHeader('Pragma', 'no-cache');
3918
- res.setHeader('Expires', '0');
3919
- const ver = /^[0-9]+$/.test(verSeg) ? verSeg : String(graphVersion || 0);
3920
- const origin = getServerOrigin(server) || `${urlObj.protocol}//${urlObj.host}`;
3921
- // Resolve app main entry to an absolute path-like key used by /ns/m
3922
- let mainEntry = '/';
3923
- try {
3924
- const pkg = getPackageJson();
3925
- const main = pkg?.main || DEFAULT_MAIN_ENTRY;
3926
- const abs = getProjectFilePath(main).replace(/\\/g, '/');
3927
- // Normalize to '/app/...'
3928
- const marker = `/${APP_ROOT_DIR}/`;
3929
- const idx = abs.indexOf(marker);
3930
- mainEntry = idx >= 0 ? abs.substring(idx) : DEFAULT_MAIN_ENTRY_VIRTUAL;
3340
+ registerTxnHandler(server, {
3341
+ resolveTxnIds: (version, fallbackChangedIds) => {
3342
+ const ids = txnBatches.get(version) || [];
3343
+ if (ids.length) {
3344
+ return ids;
3931
3345
  }
3932
- catch { }
3933
- // Build a tiny wrapper that imports the compiled entry runtime from the dev server
3934
- let code = REQUIRE_GUARD_SNIPPET +
3935
- `// [ns-entry][v${ver}] wrapper (script-safe) bytes will follow\n` +
3936
- `(async function(){\n` +
3937
- ` let origin = ${JSON.stringify(origin)}; const main = ${JSON.stringify(mainEntry)}; const __ns_graph_ver = ${JSON.stringify(ver)};\n` +
3938
- ` 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` +
3939
- ` 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` +
3940
- ` if (__VERBOSE__) console.info('[ns-entry][wrapper] start', { origin, main, ver: __ns_graph_ver });\n` +
3941
- ` async function __ns_import_entry_rt(u){\n` +
3942
- ` // Prefer fetch+eval script transformation to avoid module import limitations on device\n` +
3943
- ` 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` +
3944
- ` // Transform 'export default function' or 'export default async function' into global assignment\n` +
3945
- ` let s = t.replace(/export\\s+default\\s+async\\s+function\\s+([A-Za-z0-9_$]+)?/,'globalThis.__NS_START_ENTRY__=async function $1')\n` +
3946
- ` .replace(/export\\s+default\\s+function\\s+([A-Za-z0-9_$]+)?/,'globalThis.__NS_START_ENTRY__=function $1');\n` +
3947
- ` // Fallback: if function-form replacements didn't run, handle expression default export too\n` +
3948
- ` if (String(s).indexOf('__NS_START_ENTRY__') === -1) { s = 'globalThis.__NS_START_ENTRY__=' + s.replace(/export\\s+default\\s*/,''); }\n` +
3949
- ` try { (0,eval)(s); } catch (ee) { console.error('[ns-entry][wrapper] eval entry-rt failed', ee && (ee.message||ee)); throw ee; }\n` +
3950
- ` const fn = globalThis.__NS_START_ENTRY__; if (!fn) { throw new Error('entry-rt missing __NS_START_ENTRY__'); }\n` +
3951
- ` return { default: fn };\n` +
3952
- ` } catch(e) { console.error('[ns-entry][wrapper] entry-rt fetch/eval failed', e && (e.message||e)); throw e; }\n` +
3953
- ` }\n` +
3954
- ` const __entryRtUrl = '/ns/entry-rt?v=' + String(__ns_graph_ver);\n` +
3955
- ` 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` +
3956
- ` const startEntry = (__mod && (__mod.default || __mod));\n` +
3957
- ` 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` +
3958
- `})();\n`;
3959
- code = code + `\n//# sourceURL=${origin}/ns/entry`;
3960
- res.statusCode = 200;
3961
- res.end(code);
3962
- }
3963
- catch (e) {
3964
- next();
3965
- }
3346
+ return fallbackChangedIds.length ? computeTxnOrderForChanged(fallbackChangedIds) : [];
3347
+ },
3966
3348
  });
3967
- // 2.6) Transactional HMR endpoint: GET /ns/txn/<ver>
3968
- // Returns a single ESM that sequentially imports all changed modules for the given graphVersion.
3969
- server.middlewares.use(async (req, res, next) => {
3970
- try {
3971
- const urlObj = new URL(req.url || '', 'http://localhost');
3972
- const p = urlObj.pathname || '';
3973
- if (!p.startsWith('/ns/txn'))
3974
- return next();
3975
- let verStr = p.replace('/ns/txn', '').replace(/^\//, '');
3976
- const ver = Number(verStr || urlObj.searchParams.get('v') || 0);
3977
- let ids = txnBatches.get(ver) || [];
3978
- if (!ids.length) {
3979
- // Attempt to rebuild from any changed modules at this version if present in graph history is unavailable.
3980
- // 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)
3981
- try {
3982
- const q = (urlObj.searchParams.get('ids') || '')
3983
- .split(',')
3984
- .map((s) => s.trim())
3985
- .filter(Boolean);
3986
- if (q.length)
3987
- ids = computeTxnOrderForChanged(q);
3988
- }
3989
- catch { }
3990
- }
3991
- const origin = getServerOrigin(server) || `${urlObj.protocol}//${urlObj.host}`;
3992
- const lines = [];
3993
- lines.push(`// [txn] version=${ver} count=${ids.length}`);
3994
- if (!ids.length) {
3995
- lines.push(`export default true;`);
3996
- res.setHeader('Access-Control-Allow-Origin', '*');
3997
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
3998
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
3999
- res.setHeader('Pragma', 'no-cache');
4000
- res.setHeader('Expires', '0');
4001
- res.statusCode = 200;
4002
- res.end(lines.join('\n'));
4003
- return;
4004
- }
4005
- for (const id of ids) {
4006
- const isVue = /\.vue$/i.test(id);
4007
- const safe = id.startsWith('/') ? id : '/' + id;
4008
- const abs = isVue ? `/ns/asm/${ver}?path=${encodeURIComponent(safe)}` : `/ns/m${safe}`;
4009
- lines.push(`await import(${JSON.stringify(abs)});`);
4010
- }
4011
- lines.push(`export default true;`);
4012
- const code = lines.join('\n');
4013
- res.setHeader('Access-Control-Allow-Origin', '*');
4014
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4015
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4016
- res.setHeader('Pragma', 'no-cache');
4017
- res.setHeader('Expires', '0');
4018
- res.statusCode = 200;
4019
- res.end(code);
4020
- return;
4021
- }
4022
- catch (e) {
4023
- /* fallthrough */
4024
- }
4025
- return next();
4026
- });
4027
- // 3) ESM endpoint for SFC modules: GET /ns/sfc?path=/src/Comp.vue[?vue&type=*] OR /ns/sfc/src/Comp.vue[?vue&type=*]
4028
- // Also accept alias /ns/sfc
4029
- // Preserves variant queries (?vue&type=script|template|style) and adds a diagnostic signature comment.
4030
- server.middlewares.use(async (req, res, next) => {
4031
- try {
4032
- const urlObj = new URL(req.url || '', 'http://localhost');
4033
- const p = urlObj.pathname;
4034
- // Only match exactly "/ns/sfc" or paths under it.
4035
- const isNs = p === '/ns/sfc' || p.startsWith('/ns/sfc/');
4036
- if (!isNs)
4037
- return next();
4038
- if (p.startsWith('/ns/asm') || p.startsWith('/ns/sfc-meta'))
4039
- return next();
4040
- res.setHeader('Access-Control-Allow-Origin', '*');
4041
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4042
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4043
- res.setHeader('Pragma', 'no-cache');
4044
- res.setHeader('Expires', '0');
4045
- const base = '/ns/sfc';
4046
- // Determine request spec, preserving variant query when present and handling optional version in path
4047
- let pathParam = urlObj.searchParams.get('path') || ''; // may include its own query
4048
- const rawRemainder = urlObj.pathname.slice(base.length) || '';
4049
- let verFromPath = null;
4050
- let pathStyle = rawRemainder;
4051
- if (rawRemainder && rawRemainder.startsWith('/')) {
4052
- const parts = rawRemainder.split('/'); // ["", maybe "<ver>", ...]
4053
- if (parts.length > 2 && /^[0-9]+$/.test(parts[1] || '')) {
4054
- verFromPath = parts[1];
4055
- pathStyle = '/' + parts.slice(2).join('/');
4056
- }
4057
- }
4058
- if (pathStyle && pathStyle !== '/' && !pathParam) {
4059
- if (!pathStyle.startsWith('/'))
4060
- pathStyle = '/' + pathStyle;
4061
- // Include endpoint query for variant-style requests (e.g. /ns/sfc/Comp.vue?vue&type=template)
4062
- pathParam = pathStyle + (urlObj.search || '');
4063
- }
4064
- let fullSpec = pathParam || '';
4065
- if (!fullSpec) {
4066
- res.statusCode = 200;
4067
- res.end('export {}\n');
4068
- return;
4069
- }
4070
- if (fullSpec.startsWith('@/'))
4071
- fullSpec = APP_VIRTUAL_WITH_SLASH + fullSpec.slice(2);
4072
- if (!fullSpec.startsWith('/'))
4073
- fullSpec = '/' + fullSpec;
4074
- const isVariant = /[?&]vue&type=/.test(fullSpec);
4075
- const variantTypeMatch = /[?&]type=([^&]+)/.exec(fullSpec);
4076
- const variantType = variantTypeMatch?.[1] || null;
4077
- const isStyleVariant = /[?&]type=style\b/.test(fullSpec);
4078
- // Determine candidate for transformRequest
4079
- // For full SFCs we prefer a clean base path + '?vue'; if that fails, try base without query as fallback.
4080
- let candidate = fullSpec;
4081
- let transformed = null;
4082
- if (!isVariant) {
4083
- const basePath = fullSpec.replace(/[?#].*$/, '');
4084
- const candidates = [basePath + (basePath.includes('?') ? '&' : '?') + 'vue', basePath];
4085
- for (const c of candidates) {
4086
- try {
4087
- const r = await server.transformRequest(c);
4088
- if (r?.code) {
4089
- transformed = r;
4090
- candidate = c;
4091
- break;
4092
- }
4093
- }
4094
- catch { }
4095
- }
4096
- if (!transformed?.code) {
4097
- if (verbose) {
4098
- try {
4099
- console.warn(`[sfc][serve] transform miss for`, fullSpec);
4100
- }
4101
- catch { }
4102
- }
4103
- // Emit an erroring module to surface the failure at import site with helpful hints
4104
- try {
4105
- const tried = candidates.slice(0, 8);
4106
- 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`;
4107
- res.statusCode = 404;
4108
- res.end(out);
4109
- return;
4110
- }
4111
- catch {
4112
- res.statusCode = 404;
4113
- res.end('export {}\n');
4114
- return;
4115
- }
4116
- }
4117
- }
4118
- else {
4119
- try {
4120
- transformed = await server.transformRequest(candidate);
4121
- }
4122
- catch { }
4123
- if (!transformed?.code) {
4124
- try {
4125
- 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`;
4126
- res.statusCode = 404;
4127
- res.end(out);
4128
- return;
4129
- }
4130
- catch {
4131
- res.statusCode = 404;
4132
- res.end('export {}\n');
4133
- return;
4134
- }
4135
- }
4136
- }
4137
- // For style variants, return an empty module immediately
4138
- if (isStyleVariant) {
4139
- const sig = `// [sfc] kind=variant:style path=${fullSpec.replace(/\n/g, '')} len=0 default=false\n`;
4140
- res.statusCode = 200;
4141
- res.end(`${sig}export {}\n`);
4142
- return;
4143
- }
4144
- let code = transformed.code;
4145
- // Prepend guard to capture any URL-based require attempts
4146
- code = REQUIRE_GUARD_SNIPPET + code;
4147
- const projectRoot = server.config?.root || process.cwd();
4148
- // IMPORTANT: Do not run cleanCode() on template variant; it can strip required pieces.
4149
- // We'll handle script/full SFC below, and treat template minimally right away.
4150
- // Full SFCs delegate to deterministic assembler module; variants (script/template) still go through processing
4151
- if (!isVariant) {
4152
- const importerPath = fullSpec.replace(/[?#].*$/, '');
4153
- const origin = getServerOrigin(server);
4154
- const ver = verFromPath || '0';
4155
- const asmPath = `/ns/asm/${ver}?path=${encodeURIComponent(importerPath)}`;
4156
- const delegated = `// [sfc] kind=full (delegated to assembler) path=${importerPath}\nexport * from ${JSON.stringify(asmPath)};\nexport { default } from ${JSON.stringify(asmPath)};\n`;
4157
- res.statusCode = 200;
4158
- res.end(delegated);
4159
- return;
4160
- }
4161
- else {
4162
- // Variants
4163
- if (variantType === 'template') {
4164
- const preferSelfCompile = !!process.env.NS_HMR_SELF_COMPILE_TEMPLATE;
4165
- // Compile the template ourselves to guarantee no Vite HMR code and stable output
4166
- if (preferSelfCompile)
4167
- try {
4168
- const projectRootT = server.config?.root || process.cwd();
4169
- const basePath = fullSpec.replace(/[?#].*$/, '');
4170
- const abs = path.join(projectRootT, basePath.replace(/^\//, ''));
4171
- let sfcSrc = '';
4172
- try {
4173
- sfcSrc = readFileSync(abs, 'utf-8');
4174
- }
4175
- catch { }
4176
- if (sfcSrc) {
4177
- const { descriptor } = parse(sfcSrc, { filename: abs });
4178
- const id = createHash('md5').update(abs).digest('hex').slice(0, 8);
4179
- let bindingMetadata = undefined;
4180
- try {
4181
- const s = compileScript(descriptor, {
4182
- id,
4183
- inlineTemplate: false,
4184
- reactivityTransform: false,
4185
- });
4186
- bindingMetadata = s?.bindings;
4187
- }
4188
- catch { }
4189
- const tpl = descriptor.template?.content || '';
4190
- const ct = compileTemplate({
4191
- source: tpl,
4192
- id,
4193
- filename: abs,
4194
- isProd: false,
4195
- ssr: false,
4196
- compilerOptions: {
4197
- bindingMetadata,
4198
- isCustomElement: (tag) => NS_NATIVE_TAGS.has(tag),
4199
- },
4200
- });
4201
- let out = (ct && (ct.code || '')) || '';
4202
- // Map Vue helper imports to runtime bridge
4203
- try {
4204
- out = out.replace(/from\s+["'](?:nativescript-vue|vue)[^"']*["']/g, 'from "/ns/rt"');
4205
- }
4206
- catch { }
4207
- // No import.meta.hot present when compiling ourselves, but keep minimal sanitizer just in case
4208
- out = processTemplateVariantMinimal(out);
4209
- code = out;
4210
- }
4211
- else {
4212
- code = 'export {}\n';
4213
- }
4214
- }
4215
- catch (eTplSelf) {
4216
- if (verbose) {
4217
- try {
4218
- console.warn('[sfc][template][self-compile][fail]', fullSpec, eTplSelf?.message);
4219
- }
4220
- catch { }
4221
- }
4222
- code = transformed.code || 'export {}\n';
4223
- code = processTemplateVariantMinimal(code);
4224
- }
4225
- else {
4226
- // Prefer using Vite's template transform and apply minimal sanitization; avoids compiler mismatches and warnings
4227
- code = transformed.code || 'export {}\n';
4228
- code = processTemplateVariantMinimal(code);
4229
- }
4230
- // fall through to shared post-processing (versioning, signature, etc.)
4231
- }
4232
- // Script variants still need vendor mappings and general device processing (no SFC assembly)
4233
- // IMPORTANT: Use a Babel AST transform to remove imports of the template variant and
4234
- // neutralize their usage without brittle regex.
4235
- try {
4236
- const ast = babelParse(code, {
4237
- sourceType: 'module',
4238
- plugins: ['typescript'],
4239
- });
4240
- const templateBindings = new Set();
4241
- const navToLocals = [];
4242
- const navBackLocals = [];
4243
- babelTraverse(ast, {
4244
- ImportDeclaration(path) {
4245
- const spec = path.node.source.value || '';
4246
- // Remove template variant imports and collect their local identifiers for neutralization
4247
- if (typeof spec === 'string' && /\.vue\?[^\n]*type=template/.test(spec)) {
4248
- const ids = [];
4249
- for (const s of path.node.specifiers) {
4250
- if (t.isImportSpecifier(s)) {
4251
- const imported = t.isIdentifier(s.imported) ? s.imported.name : undefined;
4252
- const local = t.isIdentifier(s.local) ? s.local.name : undefined;
4253
- if ((imported === 'render' || imported === undefined) && local)
4254
- ids.push(local);
4255
- }
4256
- else if (t.isImportDefaultSpecifier(s) || t.isImportNamespaceSpecifier(s)) {
4257
- if (t.isIdentifier(s.local))
4258
- ids.push(s.local.name);
4259
- }
4260
- }
4261
- ids.forEach((n) => templateBindings.add(n));
4262
- path.remove();
4263
- return;
4264
- }
4265
- // Rewrite $navigateTo/$navigateBack imports from nativescript-vue (or prebundle) to use globals
4266
- const isNsVue = typeof spec === 'string' && (/nativescript-vue/.test(spec) || /vendor\.mjs$/.test(spec) || /\/node_modules\/\.vite\/deps\/nativescript-vue\.js/.test(spec));
4267
- if (isNsVue) {
4268
- const remain = [];
4269
- for (const s of path.node.specifiers) {
4270
- if (t.isImportSpecifier(s)) {
4271
- const imported = t.isIdentifier(s.imported) ? s.imported.name : undefined;
4272
- const local = t.isIdentifier(s.local) ? s.local.name : undefined;
4273
- if (local && (imported === '$navigateTo' || imported === 'navigateTo')) {
4274
- navToLocals.push(local);
4275
- continue;
4276
- }
4277
- if (local && (imported === '$navigateBack' || imported === 'navigateBack')) {
4278
- navBackLocals.push(local);
4279
- continue;
4280
- }
4281
- }
4282
- remain.push(s);
4283
- }
4284
- if (remain.length) {
4285
- path.node.specifiers = remain;
4286
- }
4287
- else {
4288
- path.remove();
4289
- }
4290
- }
4291
- },
4292
- });
4293
- if (templateBindings.size) {
4294
- babelTraverse(ast, {
4295
- Identifier(path) {
4296
- if (templateBindings.has(path.node.name)) {
4297
- path.replaceWith(t.identifier('undefined'));
4298
- }
4299
- },
4300
- AssignmentExpression(path) {
4301
- // Guard component.render = <alias> to avoid TDZ when alias is undefined
4302
- if (t.isMemberExpression(path.node.left) &&
4303
- t.isIdentifier(path.node.left.property, {
4304
- name: 'render',
4305
- })) {
4306
- const e = t.identifier('__e');
4307
- 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([])));
4308
- path.replaceWithMultiple([guarded]);
4309
- }
4310
- },
4311
- });
4312
- }
4313
- let outCode = genCode(ast).code;
4314
- if (navToLocals.length || navBackLocals.length) {
4315
- const shimLines = [];
4316
- for (const n of navToLocals)
4317
- shimLines.push(`import __ns_rt_nav_to_mod from "/ns/rt";\nconst ${n} = (...args) => __ns_rt_nav_to_mod.$navigateTo(...args);`);
4318
- for (const n of navBackLocals)
4319
- shimLines.push(`import __ns_rt_nav_back_mod from "/ns/rt";\nconst ${n} = (...args) => __ns_rt_nav_back_mod.$navigateBack(...args);`);
4320
- outCode = shimLines.join('\n') + '\n' + outCode;
4321
- }
4322
- code = outCode;
4323
- }
4324
- catch { }
4325
- code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(fullSpec), fullSpec);
4326
- // Transform static .vue imports into static imports from the assembler (no TLA) via AST
4327
- try {
4328
- const importerPath = fullSpec.replace(/[?#].*$/, '');
4329
- const origin = getServerOrigin(server);
4330
- const ver = verFromPath || '0';
4331
- const ast2 = babelParse(code, {
4332
- sourceType: 'module',
4333
- plugins: ['typescript'],
4334
- });
4335
- babelTraverse(ast2, {
4336
- ImportDeclaration(p) {
4337
- const src = p.node.source.value || '';
4338
- if (typeof src !== 'string')
4339
- return;
4340
- if (/^https?:\/\//.test(src))
4341
- return; // leave absolute URLs
4342
- if (/\.vue(?:$|\?)/.test(src)) {
4343
- let spec = src;
4344
- // Resolve to absolute project path
4345
- if (spec.startsWith('./') || spec.startsWith('../')) {
4346
- spec = path.posix.normalize(path.posix.join(path.posix.dirname(importerPath), spec));
4347
- if (!spec.startsWith('/'))
4348
- spec = '/' + spec;
4349
- }
4350
- else if (!spec.startsWith('/')) {
4351
- // Handle '@/'
4352
- if (spec.startsWith('@@/'))
4353
- spec = '/' + spec.slice(2);
4354
- if (spec.startsWith('@/'))
4355
- spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
4356
- }
4357
- // Strip query for plain .vue (keep variant imports intact)
4358
- if (!/\bvue&type=/.test(src)) {
4359
- spec = spec.replace(/[?#].*$/, '');
4360
- const asmUrl = `/ns/asm/${ver}?path=${encodeURIComponent(spec)}&mode=inline`;
4361
- p.node.source = t.stringLiteral(asmUrl);
4362
- }
4363
- }
4364
- },
4365
- });
4366
- code = genCode(ast2).code;
4367
- }
4368
- catch { }
4369
- // After rewrites, strip any TypeScript syntax from the script variant to avoid device-side parse errors
4370
- try {
4371
- const importerPath = fullSpec.replace(/[?#].*$/, '');
4372
- const tsRes = await babelCore.transformAsync(code, {
4373
- plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
4374
- sourceType: 'module',
4375
- // Help Babel infer TS parsing even if the virtual filename isn't .ts
4376
- filename: importerPath.endsWith('.vue') ? importerPath.replace(/\.vue$/, '.ts') : importerPath + '.ts',
4377
- comments: true,
4378
- configFile: false,
4379
- babelrc: false,
4380
- });
4381
- if (tsRes?.code) {
4382
- code = tsRes.code;
4383
- }
4384
- }
4385
- catch (eTsVar) {
4386
- if (verbose) {
4387
- try {
4388
- console.warn('[sfc][variant:script][babel-ts][fail]', fullSpec, eTsVar?.message);
4389
- }
4390
- catch { }
4391
- }
4392
- }
4393
- }
4394
- const importerPath = fullSpec.replace(/[?#].*$/, '');
4395
- // Only run cleanCode for non-template cases (script/full). Template code must remain intact.
4396
- if (!isVariant || variantType !== 'template') {
4397
- code = cleanCode(code);
4398
- }
4399
- code = rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, getServerOrigin(server));
4400
- code = ensureVariableDynamicImportHelper(code);
4401
- try {
4402
- // For variant requests under /ns/sfc, prefer the version from the path segment when present
4403
- // so that any internal '/ns/rt', '/ns/core', or '/ns/sfc' imports are aligned with the same version.
4404
- const verNum = Number(verFromPath || '0');
4405
- if (Number.isFinite(verNum) && verNum > 0) {
4406
- code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
4407
- code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
4408
- code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
4409
- }
4410
- else {
4411
- code = ensureVersionedRtImports(code, getServerOrigin(server), graphVersion);
4412
- code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), graphVersion);
4413
- code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
4414
- }
4415
- }
4416
- catch { }
4417
- // Final guard for SFC variant output as well
4418
- try {
4419
- code = ensureDestructureCoreImports(code);
4420
- }
4421
- catch { }
4422
- // CRITICAL: As a last step for script/template variants, re-run AST normalization and strip
4423
- // any sentinel destructures that could cause duplicate locals, then re-apply core versioning.
4424
- try {
4425
- code = astNormalizeModuleImportsAndHelpers(code);
4426
- }
4427
- catch { }
4428
- try {
4429
- // Remove any rt->core sentinel destructures that slipped in late
4430
- code = stripRtCoreSentinel(code);
4431
- }
4432
- catch { }
4433
- try {
4434
- const verNum = Number(verFromPath || '0');
4435
- if (Number.isFinite(verNum) && verNum > 0) {
4436
- code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
4437
- code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
4438
- }
4439
- else {
4440
- code = ensureVersionedRtImports(code, getServerOrigin(server), graphVersion);
4441
- code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
4442
- }
4443
- }
4444
- catch { }
4445
- // Last-chance sanitizer for dangling Vite CJS import helper usages that may surface after late transforms
4446
- try {
4447
- code = stripDanglingViteCjsImports(code);
4448
- }
4449
- catch { }
4450
- const hasDefault = /\bexport\s+default\b/.test(code);
4451
- const kind = isVariant ? `variant:${variantType || 'unknown'}` : 'full';
4452
- const sig = `// [sfc] kind=${kind} path=${importerPath} len=${code.length} default=${hasDefault} wrapped=${false}\n`;
4453
- if (verbose) {
4454
- try {
4455
- console.log(`[sfc][serve] ${fullSpec} kind=${kind} default=${hasDefault} bytes=${code.length}`);
4456
- }
4457
- catch { }
4458
- }
4459
- // Ensure script variants always provide a default export if they declare a component
4460
- if (!hasDefault) {
4461
- // Prefer an explicit identifier if present
4462
- const m = code.match(/\b(?:const|let|var)\s+(__ns_sfc__|_sfc_main)\b/);
4463
- if (m && m[1]) {
4464
- code += `\nexport default ${m[1]};`;
4465
- }
4466
- else if (/\b_defineComponent\s*\(|\bdefineComponent\s*\(/.test(code)) {
4467
- // Fallback: export whichever is defined at runtime without throwing on missing identifiers
4468
- code += `\nexport default (typeof __ns_sfc__ !== "undefined" ? __ns_sfc__ : (typeof _sfc_main !== "undefined" ? _sfc_main : undefined));`;
4469
- }
4470
- }
4471
- res.statusCode = 200;
4472
- res.end(sig + code);
4473
- }
4474
- catch (e) {
4475
- res.statusCode = 500;
4476
- res.end('export {}\n');
4477
- }
4478
- });
4479
- // 4) JSON metadata endpoint for SFCs: GET /ns/sfc-meta?path=/src/Comp.vue OR /ns/sfc-meta/<ver>?path=/src/Comp.vue
4480
- server.middlewares.use(async (req, res, next) => {
4481
- try {
4482
- const urlObj = new URL(req.url || '', 'http://localhost');
4483
- if (!urlObj.pathname.startsWith('/ns/sfc-meta'))
4484
- return next();
4485
- res.setHeader('Access-Control-Allow-Origin', '*');
4486
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
4487
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4488
- res.setHeader('Pragma', 'no-cache');
4489
- res.setHeader('Expires', '0');
4490
- // Accept optional version segment similar to /ns/sfc
4491
- {
4492
- const metaBase = '/ns/sfc-meta';
4493
- if (urlObj.pathname.startsWith(metaBase + '/')) {
4494
- const rawRemainder = urlObj.pathname.slice(metaBase.length);
4495
- const parts = rawRemainder.split('/');
4496
- if (parts.length > 2 && /^[0-9]+$/.test(parts[1] || '')) {
4497
- // consume version but we don't need it server-side
4498
- }
4499
- }
4500
- }
4501
- let spec = urlObj.searchParams.get('path') || '';
4502
- if (!spec) {
4503
- res.statusCode = 400;
4504
- res.end(JSON.stringify({ error: 'missing path' }));
4505
- return;
4506
- }
4507
- if (spec.startsWith('@/'))
4508
- spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
4509
- if (!spec.startsWith('/'))
4510
- spec = '/' + spec;
4511
- const base = spec.replace(/[?#].*$/, '');
4512
- // Transform variants to inspect exports
4513
- const [scriptR, templateR] = await Promise.all([server.transformRequest(base + '?vue&type=script'), server.transformRequest(base + '?vue&type=template')]);
4514
- const scriptCode = scriptR?.code || '';
4515
- const templateCode = templateR?.code || '';
4516
- const scriptMeta = extractExportMetadata(scriptCode);
4517
- // Robust render detection: Vue compiler may emit several shapes:
4518
- // 1) export function render(_ctx, _cache) { ... }
4519
- // 2) function render(_ctx,_cache) { ... } (later exported)
4520
- // 3) export const render = (_ctx,_cache) => { ... }
4521
- // 4) const render = (...) => { ... } (later exported)
4522
- // 5) export { render } or export { render as render }
4523
- // 6) Object property forms (rare in template output) render: (...) => {}
4524
- 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);
4525
- if (hasRender && verbose) {
4526
- try {
4527
- console.log('[sfc-meta] detected render for', base);
4528
- }
4529
- catch { }
4530
- }
4531
- else if (!hasRender && verbose) {
4532
- try {
4533
- console.warn('[sfc-meta] render NOT detected for', base);
4534
- }
4535
- catch { }
4536
- }
4537
- const hash = createHash('md5').update(base).digest('hex').slice(0, 8);
4538
- const payload = {
4539
- path: base,
4540
- hasScript: !!scriptCode,
4541
- hasTemplate: !!templateCode,
4542
- hasStyle: false,
4543
- scriptExports: scriptMeta.named,
4544
- scriptHasDefault: scriptMeta.hasDefault,
4545
- templateHasRender: hasRender,
4546
- hmrId: hash,
4547
- };
4548
- res.statusCode = 200;
4549
- res.end(JSON.stringify(payload));
4550
- }
4551
- catch (e) {
4552
- res.statusCode = 500;
4553
- res.end(JSON.stringify({ error: e?.message || String(e) }));
4554
- }
4555
- });
4556
- // 5) Deterministic SFC assembler: GET /ns/asm?path=/src/Comp.vue
4557
- // Place BEFORE any broader /ns/sfc* handlers that might accidentally match and delegate.
4558
- server.middlewares.use(async (req, res, next) => {
4559
- try {
4560
- const urlObj = new URL(req.url || '', 'http://localhost');
4561
- if (!urlObj.pathname.startsWith('/ns/asm'))
4562
- return next();
4563
- res.setHeader('Access-Control-Allow-Origin', '*');
4564
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4565
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4566
- res.setHeader('Pragma', 'no-cache');
4567
- res.setHeader('Expires', '0');
4568
- // Optional version segment as first path component after /ns/asm
4569
- const asmBase = '/ns/asm';
4570
- const asmRemainder = urlObj.pathname.slice(asmBase.length) || '';
4571
- let verFromPath = null;
4572
- if (asmRemainder && asmRemainder.startsWith('/')) {
4573
- const p = asmRemainder.split('/');
4574
- if (p.length > 1 && /^[0-9]+$/.test(p[1] || '')) {
4575
- verFromPath = p[1];
4576
- }
4577
- }
4578
- let spec = urlObj.searchParams.get('path') || '';
4579
- const diag = urlObj.searchParams.get('diag') === '1';
4580
- if (!spec) {
4581
- res.statusCode = 400;
4582
- res.end('export {}\n');
4583
- return;
4584
- }
4585
- if (spec.startsWith('@/'))
4586
- spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
4587
- if (!spec.startsWith('/'))
4588
- spec = '/' + spec;
4589
- const base = spec.replace(/[?#].*$/, '');
4590
- if (diag) {
4591
- 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`;
4592
- res.statusCode = 200;
4593
- res.end(code);
4594
- return;
4595
- }
4596
- const projectRoot = server.config?.root || process.cwd();
4597
- // Ensure variant transforms exist so imports resolve (avoid Promise.all short-circuit on single failure)
4598
- const safeTransform = async (cand) => {
4599
- try {
4600
- return await server.transformRequest(cand);
4601
- }
4602
- catch {
4603
- return null;
4604
- }
4605
- };
4606
- const scriptR = await safeTransform(base + '?vue&type=script');
4607
- const templateR = await safeTransform(base + '?vue&type=template');
4608
- const fullR = await safeTransform(base + '?vue');
4609
- const hasScript = !!scriptR?.code;
4610
- const hasTemplate = !!templateR?.code;
4611
- const origin = getServerOrigin(server);
4612
- const ver = String(verFromPath || graphVersion || Date.now());
4613
- const scriptUrl = `${origin}/ns/sfc/${ver}${base}?vue&type=script`;
4614
- const templateCode = templateR?.code || '';
4615
- // INLINE-FIRST assembler: compile SFC source into a self-contained ESM module (enhanced diagnostics)
4616
- try {
4617
- const root = server.config?.root || process.cwd();
4618
- const abs = path.join(root, base.replace(/^\//, ''));
4619
- let sfcSrc = '';
4620
- try {
4621
- sfcSrc = readFileSync(abs, 'utf-8');
4622
- }
4623
- catch { }
4624
- if (sfcSrc) {
4625
- const { descriptor } = parse(sfcSrc, { filename: abs });
4626
- const id = createHash('md5').update(abs).digest('hex').slice(0, 8);
4627
- // 1) Compile script (prefer inlineTemplate for a complete module)
4628
- let compiledScript = '';
4629
- let bindingMetadata = undefined;
4630
- let triedInlineTemplate = false;
4631
- let hadScriptDefaultPre = false;
4632
- let usedInlineScript = false;
4633
- try {
4634
- // First try inlineTemplate for a holistic, self-contained module with render + hoists
4635
- // Use a strict NativeScript native element detector for inlineTemplate that does NOT treat generic PascalCase as native.
4636
- // This ensures imported components like PageWrapper remain true components and get referenced via bindings.
4637
- const isNSNative = (tag) => NS_NATIVE_TAGS.has(tag);
4638
- const sInline = compileScript(descriptor, {
4639
- id,
4640
- inlineTemplate: true,
4641
- reactivityTransform: false,
4642
- // Pass only strict NS native element predicate; avoid broad PascalCase heuristic here.
4643
- templateOptions: {
4644
- compilerOptions: { isCustomElement: isNSNative },
4645
- },
4646
- });
4647
- triedInlineTemplate = true;
4648
- if (/export\s+default/.test(sInline?.content || '')) {
4649
- compiledScript = sInline.content;
4650
- bindingMetadata = sInline?.bindings;
4651
- hadScriptDefaultPre = true;
4652
- usedInlineScript = true;
4653
- }
4654
- else {
4655
- // Fallback to standard script (no inline) and attempt separate template compile
4656
- const s = compileScript(descriptor, {
4657
- id,
4658
- inlineTemplate: false,
4659
- reactivityTransform: false,
4660
- });
4661
- compiledScript = s?.content || '';
4662
- bindingMetadata = s?.bindings;
4663
- hadScriptDefaultPre = /export\s+default/.test(compiledScript);
4664
- usedInlineScript = false;
4665
- }
4666
- }
4667
- catch (eScript) {
4668
- if (verbose) {
4669
- try {
4670
- console.warn('[sfc-asm][compileScript] failed', base, eScript?.message);
4671
- }
4672
- catch { }
4673
- }
4674
- // Retry without inlineTemplate
4675
- try {
4676
- const s = compileScript(descriptor, {
4677
- id,
4678
- inlineTemplate: false,
4679
- reactivityTransform: false,
4680
- });
4681
- compiledScript = s?.content || '';
4682
- bindingMetadata = s?.bindings;
4683
- hadScriptDefaultPre = /export\s+default/.test(compiledScript);
4684
- usedInlineScript = false;
4685
- }
4686
- catch (eNoInline) {
4687
- if (verbose) {
4688
- try {
4689
- console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
4690
- }
4691
- catch { }
4692
- }
4693
- }
4694
- }
4695
- // Final fallback: if script compile yielded nothing, use the variant-transformed script
4696
- if (!compiledScript && scriptR?.code) {
4697
- try {
4698
- compiledScript = scriptR.code;
4699
- hadScriptDefaultPre = /export\s+default/.test(compiledScript);
4700
- }
4701
- catch { }
4702
- }
4703
- // If inlineTemplate produced a default export AND visibly contains a render, allow early-return.
4704
- // Visible render forms we accept:
4705
- // - export function render(...) { ... }
4706
- // - setup(...) { ... return (_ctx, _cache) => { ... } }
4707
- const hasInlineRender = /(^|\n)\s*export\s+function\s+render\s*\(/.test(compiledScript || '') || /\breturn\s*\(\s*_ctx\s*,\s*_cache\s*\)\s*=>\s*\{/.test(compiledScript || '');
4708
- // Always use canonical assembler path; avoid inlineTemplate early-return which can miss render attachment
4709
- // If we reached here, we are going to assemble canonically. Ensure the script we use does NOT include inlineTemplate render.
4710
- if (usedInlineScript) {
4711
- try {
4712
- const sNoInline = compileScript(descriptor, {
4713
- id,
4714
- inlineTemplate: false,
4715
- reactivityTransform: false,
4716
- });
4717
- compiledScript = sNoInline?.content || compiledScript;
4718
- bindingMetadata = sNoInline?.bindings || bindingMetadata;
4719
- }
4720
- catch (eNoInline) {
4721
- if (verbose) {
4722
- try {
4723
- console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
4724
- }
4725
- catch { }
4726
- }
4727
- }
4728
- }
4729
- // 2) Compile template
4730
- let compiledTplCode = '';
4731
- let templateErr = null;
4732
- try {
4733
- const tplSrc = descriptor.template?.content || '';
4734
- if (tplSrc) {
4735
- const ct = compileTemplate({
4736
- source: tplSrc,
4737
- id,
4738
- filename: abs,
4739
- isProd: false,
4740
- ssr: false,
4741
- compilerOptions: {
4742
- bindingMetadata,
4743
- isCustomElement: (tag) => NS_NATIVE_TAGS.has(tag),
4744
- },
4745
- });
4746
- compiledTplCode = (ct && (ct.code || '')) || '';
4747
- if (ct?.errors?.length && verbose) {
4748
- try {
4749
- console.warn('[sfc-asm][compileTemplate][errors]', base, ct.errors);
4750
- }
4751
- catch { }
4752
- }
4753
- }
4754
- }
4755
- catch (eTpl) {
4756
- templateErr = eTpl;
4757
- if (verbose) {
4758
- try {
4759
- console.warn('[sfc-asm][compileTemplate] failed', base, eTpl?.message);
4760
- }
4761
- catch { }
4762
- }
4763
- // Fallback: use the variant-transformed template code if available
4764
- try {
4765
- if (templateR?.code)
4766
- compiledTplCode = templateR.code;
4767
- }
4768
- catch { }
4769
- }
4770
- // If still no template code, synthesize a minimal render stub so the module is valid
4771
- if (!compiledTplCode) {
4772
- try {
4773
- compiledTplCode = "export function render(){ const _ = (globalThis.createElementVNode||globalThis._createElementVNode); return _? _('StackLayout') : {}; }\n";
4774
- }
4775
- catch { }
4776
- }
4777
- // 3) Sanitize script and rewrite .vue imports to inline assembler
4778
- let scriptBody = compiledScript || '';
4779
- if (scriptBody) {
4780
- // Do NOT strip Vue/nativescript-vue imports; retarget them to the runtime bridge so helpers (e.g., onMounted) are bound.
4781
- // Preserve the import clause and only rewrite the source to '/ns/rt'.
4782
- 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";`);
4783
- try {
4784
- const importerDir = path.posix.dirname(base);
4785
- scriptBody = scriptBody.replace(/(^|\n)\s*import\s+([^;\n]+)\s+from\s+["']([^"'\n]+\.vue)(?:\?[^"'\n]*)?["'];?/g, (_m, pfx, clause, spec) => {
4786
- let absImp = spec;
4787
- if (spec.startsWith('./') || spec.startsWith('../')) {
4788
- absImp = path.posix.normalize(path.posix.join(importerDir, spec));
4789
- if (!absImp.startsWith('/'))
4790
- absImp = '/' + absImp;
4791
- }
4792
- else if (!spec.startsWith('/')) {
4793
- if (absImp.startsWith('@/'))
4794
- absImp = APP_VIRTUAL_WITH_SLASH + absImp.slice(2);
4795
- }
4796
- const asmUrl = `/ns/asm/${ver}?path=${encodeURIComponent(absImp)}&mode=inline`;
4797
- return `${pfx}import ${clause} from ${JSON.stringify(asmUrl)};`;
4798
- });
4799
- }
4800
- catch { }
4801
- }
4802
- // 4) Extract render from compiled template and prepare a full inline template block
4803
- let helperBindings = '';
4804
- let renderDecl = '';
4805
- let inlineBlock = undefined;
4806
- let renderOk = false;
4807
- if (compiledTplCode) {
4808
- try {
4809
- // Build a full inline template block to preserve hoists where possible
4810
- inlineBlock = buildInlineTemplateBlock(compiledTplCode) || undefined;
4811
- if (!inlineBlock) {
4812
- const extracted = extractTemplateRender(compiledTplCode);
4813
- helperBindings = extracted.helperBindings;
4814
- renderDecl = extracted.renderDecl;
4815
- inlineBlock = extracted.inlineBlock;
4816
- renderOk = extracted.ok;
4817
- }
4818
- else {
4819
- renderOk = true;
4820
- }
4821
- }
4822
- catch (eExtract) {
4823
- if (verbose) {
4824
- try {
4825
- console.warn('[sfc-asm][extractTemplateRender] failed', base, eExtract?.message);
4826
- }
4827
- catch { }
4828
- }
4829
- }
4830
- }
4831
- // Final guard: if no inline render extracted, attempt to import template variant or synthesize a no-op render
4832
- if (!renderOk && !inlineBlock) {
4833
- try {
4834
- const templateUrl = `${origin}/ns/sfc/${ver}${base}?vue&type=template`;
4835
- const importLine = `import * as __template from ${JSON.stringify(templateUrl)};`;
4836
- // Attach only if scriptTransformed produces __ns_sfc__ later
4837
- helperBindings += `\n${importLine}`;
4838
- 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`;
4839
- renderOk = true;
4840
- }
4841
- catch { }
4842
- }
4843
- // 5) Convert default export to const __ns_sfc__
4844
- let scriptTransformed = scriptBody;
4845
- if (scriptTransformed) {
4846
- 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');
4847
- // Normalize any prior declaration of __ns_sfc__ to a plain assignment to avoid redeclare
4848
- // Accept a semicolon before the declaration too
4849
- scriptTransformed = scriptTransformed.replace(/(^|[\n;])\s*(?:const|let|var)\s+__ns_sfc__\s*=\s*/g, '$1__ns_sfc__ = ');
4850
- // Ensure a single declaration appears once before first assignment
4851
- if (!/(^|[\n;])\s*(?:const|let|var)\s+__ns_sfc__\b/.test(scriptTransformed)) {
4852
- scriptTransformed = `let __ns_sfc__;\n` + scriptTransformed;
4853
- }
4854
- // Remove stray leading braces (artifact defense)
4855
- scriptTransformed = scriptTransformed.replace(/^\s*\}+(?=\s*[^}])/, (m) => `/* [asm-fix] removed ${m.length} stray leading braces */\n`);
4856
- }
4857
- else {
4858
- try {
4859
- const compName = (base.split('/').pop() || 'Component').replace(/\.vue$/i, '') || 'Component';
4860
- scriptTransformed = `import { defineComponent as _defineComponent } from "/ns/rt";\nlet __ns_sfc__;\n__ns_sfc__ = /*@__PURE__*/_defineComponent({ __name: ${JSON.stringify(compName)} });`;
4861
- }
4862
- catch {
4863
- scriptTransformed = `import { defineComponent as _defineComponent } from "/ns/rt";\nlet __ns_sfc__;\n__ns_sfc__ = /*@__PURE__*/_defineComponent({});`;
4864
- }
4865
- }
4866
- // 6) Emit final inline module with diagnostics comment
4867
- const parts = [];
4868
- parts.push(`// [sfc-asm] ${base} (inline-compiled)`);
4869
- // Deterministic path: always use extracted helperBindings + renderDecl + scriptTransformed (ignore inlineBlock)
4870
- // Emit hoisted template bindings first
4871
- if (helperBindings)
4872
- parts.push(helperBindings);
4873
- // IMPORTANT: place script (with its imports) BEFORE renderDecl so imports never appear inside the render function.
4874
- parts.push(scriptTransformed);
4875
- parts.push(renderDecl);
4876
- parts.push(`try { if (!__ns_sfc__.render) Object.defineProperty(__ns_sfc__, 'render', { configurable: true, enumerable: true, get(){ const r = (typeof __ns_getRender==='function' ? __ns_getRender() : undefined); Object.defineProperty(__ns_sfc__, 'render', { value: r, writable: true, configurable: true, enumerable: true }); return r; }, set(v){ Object.defineProperty(__ns_sfc__, 'render', { value: v, writable: true, configurable: true, enumerable: true }); } }); } catch(_e){}`);
4877
- parts.push(`export function render(){ const f = (typeof __ns_getRender==='function' ? __ns_getRender() : (__ns_sfc__ && __ns_sfc__.render)); return typeof f==='function' ? f.apply(this, arguments) : undefined; }`);
4878
- parts.push(`export default __ns_sfc__`);
4879
- let inlineCode = parts.filter(Boolean).join('\n');
4880
- inlineCode = processCodeForDevice(inlineCode, false, true);
4881
- try {
4882
- inlineCode = ensureVersionedCoreImports(inlineCode, getServerOrigin(server), Number(ver));
4883
- }
4884
- catch { }
4885
- try {
4886
- inlineCode = ensureDestructureCoreImports(inlineCode);
4887
- }
4888
- catch { }
4889
- // Replace legacy mutation pipeline with canonical assembler for reliability
4890
- {
4891
- // First: strip TypeScript robustly using Babel transform
4892
- try {
4893
- const tsRes = await babelCore.transformAsync(scriptTransformed, {
4894
- plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
4895
- ast: false,
4896
- sourceType: 'module',
4897
- configFile: false,
4898
- babelrc: false,
4899
- });
4900
- if (tsRes?.code)
4901
- scriptTransformed = tsRes.code;
4902
- }
4903
- catch (eTs) {
4904
- if (verbose) {
4905
- try {
4906
- console.warn('[sfc-asm][babel-ts][fail]', base, eTs?.message);
4907
- }
4908
- catch { }
4909
- }
4910
- }
4911
- // Hoist imports + strip residual TS via AST
4912
- let importLines = [];
4913
- try {
4914
- const astRes = astExtractImportsAndStripTypes(scriptTransformed);
4915
- importLines = astRes.imports;
4916
- scriptTransformed = astRes.body;
4917
- if (astRes.diagnostics.length && verbose) {
4918
- try {
4919
- console.warn('[sfc-asm][ast]', base, astRes.diagnostics.join('; '));
4920
- }
4921
- catch { }
4922
- }
4923
- }
4924
- catch (eAst) {
4925
- if (verbose) {
4926
- try {
4927
- console.warn('[sfc-asm][ast][fail]', base, eAst?.message);
4928
- }
4929
- catch { }
4930
- }
4931
- }
4932
- // Ensure renderDecl ends with closing brace ONLY for function declaration forms
4933
- // Avoid appending to const-assignment forms like: const __ns_render = (function(){ ... })();
4934
- if (renderDecl && /(^|\n)\s*(?:export\s+)?function\s+__ns_render\s*\(/.test(renderDecl) && !/\}\s*$/.test(renderDecl)) {
4935
- renderDecl = renderDecl.trimEnd() + '\n}';
4936
- }
4937
- const outParts = [];
4938
- outParts.push(`// [sfc-asm] ${base} (inline-compiled)`);
4939
- outParts.push('// [sfc-asm][canonical]');
4940
- if (importLines.length)
4941
- outParts.push(Array.from(new Set(importLines)).join('\n'));
4942
- // Place component script first so the component object exists before we attach render.
4943
- outParts.push(scriptTransformed);
4944
- // Prefer full template block to guarantee presence of all hoisted constants.
4945
- if (inlineBlock) {
4946
- outParts.push(inlineBlock);
4947
- }
4948
- else {
4949
- if (helperBindings)
4950
- outParts.push(helperBindings);
4951
- if (renderDecl && renderDecl.trim())
4952
- outParts.push(renderDecl);
4953
- }
4954
- outParts.push(`try { if (!__ns_sfc__.render) Object.defineProperty(__ns_sfc__, 'render', { configurable: true, enumerable: true, get(){ const r = (typeof __ns_getRender==='function' ? __ns_getRender() : (typeof __ns_render==='function' ? __ns_render : undefined)); Object.defineProperty(__ns_sfc__, 'render', { value: r, writable: true, configurable: true, enumerable: true }); return r; }, set(v){ Object.defineProperty(__ns_sfc__, 'render', { value: v, writable: true, configurable: true, enumerable: true }); } }); } catch(_e){}`);
4955
- // Export named render as a function that resolves lazily
4956
- outParts.push('export function render(){ const f = (typeof __ns_getRender==="function" ? __ns_getRender() : (typeof __ns_render==="function" ? __ns_render : (__ns_sfc__ && __ns_sfc__.render))); return typeof f === "function" ? f.apply(this, arguments) : undefined; }');
4957
- outParts.push('export default __ns_sfc__');
4958
- let inlineCode2 = outParts.filter(Boolean).join('\n');
4959
- inlineCode2 = processCodeForDevice(inlineCode2, false, true);
4960
- try {
4961
- inlineCode2 = ensureVersionedCoreImports(inlineCode2, getServerOrigin(server), Number(ver));
4962
- }
4963
- catch { }
4964
- try {
4965
- inlineCode2 = ensureDestructureCoreImports(inlineCode2);
4966
- }
4967
- catch { }
4968
- // Hoist any late imports that accidentally landed after render or script assembly
4969
- try {
4970
- const lateImportRe = /^(?!\/\/).*^\s*import\s+[^;]+;?$/gm;
4971
- const allImports = [];
4972
- inlineCode2 = inlineCode2.replace(lateImportRe, (imp) => {
4973
- allImports.push(imp);
4974
- return '';
4975
- });
4976
- if (allImports.length) {
4977
- // Place after helperBindings sentinel
4978
- inlineCode2 = inlineCode2.replace(/(\/\/ \[sfc-asm\]\[canonical\]\n)/, `$1${Array.from(new Set(allImports)).join('\n')}\n/* [asm-fix] re-hoisted ${allImports.length} imports */\n`);
4979
- }
4980
- }
4981
- catch { }
4982
- // After hoisting, re-run AST normalization and duplicate-binding verification.
4983
- // This guards against freshly hoisted imports reintroducing identifiers that collide
4984
- // with earlier destructures (e.g., __ns_core_ns_1), which would otherwise surface at device runtime.
4985
- try {
4986
- inlineCode2 = astNormalizeModuleImportsAndHelpers(inlineCode2);
4987
- }
4988
- catch { }
4989
- try {
4990
- inlineCode2 = astVerifyAndAnnotateDuplicates(inlineCode2);
4991
- if (/^\s*\/\/ \[ast-verify\]\[duplicate-bindings\]/m.test(inlineCode2)) {
4992
- const diagnosticLine = (inlineCode2.match(/^\s*\/\/ \[ast-verify\]\[duplicate-bindings\][^\n]*/m) || [])[0] || '// [ast-verify][duplicate-bindings]';
4993
- const brief = diagnosticLine.replace(/^[^:]*:?\s?/, '');
4994
- const escaped = brief.replace(/["\\]/g, '\\$&');
4995
- const thrower = `throw new Error("[nsv-hmr] Duplicate top-level bindings detected post-hoist: ${escaped}");`;
4996
- inlineCode2 = `${thrower}\n` + inlineCode2;
4997
- }
4998
- }
4999
- catch { }
5000
- // Minimal cleanup only (avoid destructive type stripping breaking object literal property defaults)
5001
- try {
5002
- // Heal cases where a TS type strip earlier removed initializer: plain 'default' inside props objects
5003
- // becomes 'default: undefined'. We only match when followed by ',' or '}' or newline to avoid 'export default'.
5004
- inlineCode2 = inlineCode2.replace(/\bdefault\b\s*(?=\}|,|\n)/g, 'default: undefined');
5005
- // Remove obvious leftover angle generic markers
5006
- inlineCode2 = inlineCode2.replace(/<unknown>/g, '');
5007
- // Fix accidental '}=> {' sequences
5008
- inlineCode2 = inlineCode2.replace(/}\s*=>\s*\{/g, '');
5009
- // No-op: removed prior broken normalization. Handlers are fixed in the dedicated passes below.
5010
- }
5011
- catch { }
5012
- // Removed redundant render closure heal that could inject an extra '}' before component script.
5013
- // Rewrite any remaining imports (e.g., relative app paths) to HTTP ESM endpoints
5014
- try {
5015
- inlineCode2 = rewriteImports(inlineCode2, base, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, getServerOrigin(server));
5016
- }
5017
- catch { }
5018
- // Final TS strip on the whole assembled module (safety net)
5019
- try {
5020
- const tsFinal = await babelCore.transformAsync(inlineCode2, {
5021
- plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
5022
- ast: false,
5023
- sourceType: 'module',
5024
- configFile: false,
5025
- babelrc: false,
5026
- });
5027
- if (tsFinal?.code)
5028
- inlineCode2 = tsFinal.code;
5029
- }
5030
- catch { }
5031
- // Heal Vue v-model update handlers that lost the ": else" branch during transforms:
5032
- // "onUpdate:modelValue": _cache[N] || (_cache[N] = $event => _isRef(name) ? name.value = $event)
5033
- // → add else branch to keep syntax valid: : (name = $event)
5034
- try {
5035
- // Fix missing else branch on v-model handlers: support dotted expressions (e.g., $setup.acceptTerms)
5036
- 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;
5037
- inlineCode2 = inlineCode2.replace(reMissingElse, (_m, idx, expr) => {
5038
- return `\"onUpdate:modelValue\": _cache[${idx}] || (_cache[${idx}] = $event => (_isRef(${expr}) ? (${expr}.value = $event) : (${expr} = $event)))`;
5039
- });
5040
- // Repair malformed handlers without an arrow (introduced by previous transforms):
5041
- // Convert pattern assigning to $event without an arrow into a proper arrow using the same target expression.
5042
- 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;
5043
- inlineCode2 = inlineCode2.replace(reMalformed, (_m, idx, expr) => {
5044
- return `\"onUpdate:modelValue\": _cache[${idx}] || (_cache[${idx}] = $event => (_isRef(${expr}) ? (${expr}.value = $event) : (${expr} = $event)))`;
5045
- });
5046
- }
5047
- catch { }
5048
- // Structural heal: ensure balanced braces before the first import statement
5049
- try {
5050
- const idx = inlineCode2.search(/^[\t ]*import\b/m);
5051
- if (idx > 0) {
5052
- const prefix = inlineCode2.slice(0, idx);
5053
- let open = 0, close = 0;
5054
- let inS = false, inD = false, inT = false, inLC = false, inBC = false;
5055
- for (let i = 0; i < prefix.length; i++) {
5056
- const ch = prefix[i], nx = prefix[i + 1];
5057
- if (inLC) {
5058
- if (ch === '\n')
5059
- inLC = false;
5060
- continue;
5061
- }
5062
- if (inBC) {
5063
- if (ch === '*' && nx === '/') {
5064
- inBC = false;
5065
- i++;
5066
- }
5067
- continue;
5068
- }
5069
- if (inS) {
5070
- if (ch === '\\') {
5071
- i++;
5072
- continue;
5073
- }
5074
- if (ch === "'")
5075
- inS = false;
5076
- continue;
5077
- }
5078
- if (inD) {
5079
- if (ch === '\\') {
5080
- i++;
5081
- continue;
5082
- }
5083
- if (ch === '"')
5084
- inD = false;
5085
- continue;
5086
- }
5087
- if (inT) {
5088
- if (ch === '\\') {
5089
- i++;
5090
- continue;
5091
- }
5092
- if (ch === '`')
5093
- inT = false;
5094
- continue;
5095
- }
5096
- if (ch === '/' && nx === '/') {
5097
- inLC = true;
5098
- i++;
5099
- continue;
5100
- }
5101
- if (ch === '/' && nx === '*') {
5102
- inBC = true;
5103
- i++;
5104
- continue;
5105
- }
5106
- if (ch === "'") {
5107
- inS = true;
5108
- continue;
5109
- }
5110
- if (ch === '"') {
5111
- inD = true;
5112
- continue;
5113
- }
5114
- if (ch === '`') {
5115
- inT = true;
5116
- continue;
5117
- }
5118
- if (ch === '{')
5119
- open++;
5120
- else if (ch === '}')
5121
- close++;
5122
- }
5123
- const missing = open - close;
5124
- if (missing > 0) {
5125
- inlineCode2 = inlineCode2.slice(0, idx) + '}'.repeat(missing) + '\n' + inlineCode2.slice(idx);
5126
- }
5127
- }
5128
- }
5129
- catch { }
5130
- // Final TS strip on the whole assembled module (safety net)
5131
- try {
5132
- const tsFinal = await babelCore.transformAsync(inlineCode2, {
5133
- plugins: [[pluginTransformTypescript, { allowDeclareFields: true }]],
5134
- ast: false,
5135
- sourceType: 'module',
5136
- configFile: false,
5137
- babelrc: false,
5138
- });
5139
- if (tsFinal?.code)
5140
- inlineCode2 = tsFinal.code;
5141
- }
5142
- catch { }
5143
- inlineCode2 = ensureVariableDynamicImportHelper(inlineCode2);
5144
- inlineCode2 = ensureGuardPlainDynamicImports(inlineCode2, origin);
5145
- inlineCode2 = REQUIRE_GUARD_SNIPPET + inlineCode2;
5146
- // If no render materialized, return a clear error module for deterministic failure
5147
- try {
5148
- const lacksRender = !/__ns_render\b/.test(inlineCode2) && !/__ns_sfc__\.render\s*=/.test(inlineCode2);
5149
- if (lacksRender) {
5150
- const err = `throw new Error(\"[sfc-asm] ${base}: no render generated by assembler\");\nexport default {};`;
5151
- res.statusCode = 200;
5152
- res.end(err);
5153
- return;
5154
- }
5155
- }
5156
- catch { }
5157
- // Cosmetic and parser-friendly: ensure a newline after the canonical banner
5158
- try {
5159
- inlineCode2 = inlineCode2.replace(/(\/\/ \[sfc-asm\]\[canonical\])(?!\n)/, '$1\n');
5160
- }
5161
- catch { }
5162
- // Bust device cache for runtime bridge so helpers are always current for this graph version
5163
- try {
5164
- const origin = getServerOrigin(server);
5165
- inlineCode2 = ensureVersionedRtImports(inlineCode2, origin, Number(ver));
5166
- inlineCode2 = ACTIVE_STRATEGY.ensureVersionedImports(inlineCode2, origin, Number(ver));
5167
- inlineCode2 = ensureVersionedCoreImports(inlineCode2, origin, Number(ver));
5168
- }
5169
- catch { }
5170
- // Normalize imports/helpers via AST to ensure _defineComponent and other helpers are bound once
5171
- try {
5172
- inlineCode2 = astNormalizeModuleImportsAndHelpers(inlineCode2);
5173
- }
5174
- catch { }
5175
- // Guarantee a concrete component object exists before exporting default.
5176
- try {
5177
- // Detect an existing declaration of __ns_sfc__ even if it's appended after a semicolon on the same line
5178
- // e.g., "import ...;let __ns_sfc__;" (no newline). Accept start-of-string, newline, or semicolon as anchors.
5179
- const hasDecl = /(^|[\n;])\s*(?:const|let|var)\s+__ns_sfc__\b/.test(inlineCode2);
5180
- if (!hasDecl) {
5181
- inlineCode2 = inlineCode2.replace(/(\/\/ \[sfc-asm\]\[canonical\]\n)/, `$1let __ns_sfc__ = {};\n`);
5182
- }
5183
- // Heal empty declarations (e.g., "let __ns_sfc__;" → initialize to {}), also when preceded by a semicolon
5184
- inlineCode2 = inlineCode2.replace(/(^|[\n;])\s*let\s+__ns_sfc__\s*;?/g, '$1let __ns_sfc__ = {};');
5185
- inlineCode2 = inlineCode2.replace(/(^|[\n;])\s*var\s+__ns_sfc__\s*;?/g, '$1var __ns_sfc__ = {};');
5186
- }
5187
- catch { }
5188
- if (!/export\s+default\s+__ns_sfc__/.test(inlineCode2) && /__ns_sfc__/.test(inlineCode2))
5189
- inlineCode2 += '\nexport default __ns_sfc__';
5190
- res.statusCode = 200;
5191
- res.end(inlineCode2);
5192
- return;
5193
- }
5194
- }
5195
- }
5196
- catch { }
5197
- // Do not use compiled ?vue or variant fallbacks; assembler must succeed or emit an error
5198
- // Prefer compiling template from source via compiler-sfc; fallback to variant extraction
5199
- let inlineOk = false;
5200
- let helperBindings = '';
5201
- let renderDecl = '';
5202
- let inlineBlock = undefined;
5203
- try {
5204
- const root = server.config?.root || process.cwd();
5205
- const abs = path.join(root, base.replace(/^\//, ''));
5206
- let sfcSrc = '';
5207
- try {
5208
- sfcSrc = readFileSync(abs, 'utf-8');
5209
- }
5210
- catch { }
5211
- if (sfcSrc) {
5212
- const { descriptor } = parse(sfcSrc, { filename: abs });
5213
- const tpl = descriptor.template?.content || '';
5214
- if (tpl) {
5215
- const id = createHash('md5').update(abs).digest('hex').slice(0, 8);
5216
- const ct = compileTemplate({
5217
- source: tpl,
5218
- id,
5219
- filename: abs,
5220
- isProd: false,
5221
- ssr: false,
5222
- compilerOptions: {
5223
- isCustomElement: (tag) => NS_NATIVE_TAGS.has(tag),
5224
- },
5225
- });
5226
- let compiled = (ct && (ct.code || '')) || '';
5227
- if (compiled) {
5228
- // Prefer a full inline template block preserving hoists
5229
- inlineBlock = buildInlineTemplateBlock(compiled) || undefined;
5230
- if (inlineBlock) {
5231
- inlineOk = true;
5232
- }
5233
- else {
5234
- const extracted = extractTemplateRender(compiled);
5235
- inlineOk = extracted.ok;
5236
- helperBindings = extracted.helperBindings;
5237
- renderDecl = extracted.renderDecl;
5238
- inlineBlock = extracted.inlineBlock;
5239
- }
5240
- }
5241
- }
5242
- }
5243
- }
5244
- catch { }
5245
- // If compiler-sfc path didn't succeed, attempt variant extraction once
5246
- if (!inlineOk) {
5247
- const extracted = extractTemplateRender(templateCode);
5248
- inlineOk = extracted.ok;
5249
- helperBindings = extracted.helperBindings;
5250
- renderDecl = extracted.renderDecl;
5251
- inlineBlock = extracted.inlineBlock;
5252
- }
5253
- let asm;
5254
- if (inlineOk) {
5255
- if (inlineBlock && inlineBlock.trim()) {
5256
- asm = [`// [sfc-asm] ${base} (inlined template body)`, `export * from ${JSON.stringify(scriptUrl)};`, `import * as __script from ${JSON.stringify(scriptUrl)};`, inlineBlock, `const __ns_sfc__ = (__script && __script.default) ? __script.default : {};`, `try { if (typeof __ns_render === 'function' && !__ns_sfc__.render) __ns_sfc__.render = __ns_render; } catch {}`, `export default __ns_sfc__;`].join('\n');
5257
- }
5258
- else {
5259
- asm = [`// [sfc-asm] ${base} (inlined template)`, `export * from ${JSON.stringify(scriptUrl)};`, `import * as __script from ${JSON.stringify(scriptUrl)};`, helperBindings, renderDecl, `const __ns_sfc__ = (__script && __script.default) ? __script.default : {};`, `try { if (typeof __ns_render === 'function' && !__ns_sfc__.render) __ns_sfc__.render = __ns_render; } catch {}`, `export default __ns_sfc__;`].filter(Boolean).join('\n');
5260
- }
5261
- }
5262
- else {
5263
- // Deterministic error path when template extraction failed
5264
- res.statusCode = 500;
5265
- res.end(`throw new Error('[sfc-asm] ${base}: template extraction failed');\nexport default {};`);
5266
- return;
5267
- }
5268
- // Run full device processing so helper aliasing and globals are consistent in this path too
5269
- let code = REQUIRE_GUARD_SNIPPET + asm;
5270
- code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(base), base);
5271
- try {
5272
- code = ensureVersionedCoreImports(code, getServerOrigin(server), Number(ver));
5273
- }
5274
- catch { }
5275
- code = rewriteImports(code, base, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, getServerOrigin(server));
5276
- try {
5277
- code = ensureDestructureCoreImports(code);
5278
- }
5279
- catch { }
5280
- code = ensureVariableDynamicImportHelper(code);
5281
- code = ensureGuardPlainDynamicImports(code, origin);
5282
- try {
5283
- const origin = getServerOrigin(server);
5284
- code = ensureVersionedRtImports(code, origin, Number(ver));
5285
- code = ACTIVE_STRATEGY.ensureVersionedImports(code, origin, Number(ver));
5286
- code = ensureVersionedCoreImports(code, origin, Number(ver));
5287
- }
5288
- catch { }
5289
- // Inline-template body path already runs processCodeForDevice (AST + sanitizers); no additional _defineComponent fix needed
5290
- res.statusCode = 200;
5291
- res.end(code);
5292
- }
5293
- catch (e) {
5294
- res.statusCode = 500;
5295
- res.end('export {}\n');
5296
- }
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,
5297
3367
  });
5298
3368
  wss.on('connection', async (ws) => {
5299
3369
  if (verbose)