@nativescript/vite 8.0.0-alpha.14 → 8.0.0-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/configuration/base.js +20 -1
  2. package/configuration/base.js.map +1 -1
  3. package/configuration/solid.js +25 -19
  4. package/configuration/solid.js.map +1 -1
  5. package/helpers/main-entry.js +43 -8
  6. package/helpers/main-entry.js.map +1 -1
  7. package/helpers/ns-core-url.d.ts +8 -3
  8. package/helpers/ns-core-url.js +32 -8
  9. package/helpers/ns-core-url.js.map +1 -1
  10. package/helpers/solid-jsx-deps.d.ts +15 -0
  11. package/helpers/solid-jsx-deps.js +178 -0
  12. package/helpers/solid-jsx-deps.js.map +1 -0
  13. package/hmr/client/index.js +36 -5
  14. package/hmr/client/index.js.map +1 -1
  15. package/hmr/client/vue-sfc-update-overlay.d.ts +82 -0
  16. package/hmr/client/vue-sfc-update-overlay.js +133 -0
  17. package/hmr/client/vue-sfc-update-overlay.js.map +1 -0
  18. package/hmr/entry-runtime.d.ts +1 -1
  19. package/hmr/entry-runtime.js +59 -14
  20. package/hmr/entry-runtime.js.map +1 -1
  21. package/hmr/helpers/ast-normalizer.js +45 -5
  22. package/hmr/helpers/ast-normalizer.js.map +1 -1
  23. package/hmr/server/core-sanitize.d.ts +25 -6
  24. package/hmr/server/core-sanitize.js +69 -28
  25. package/hmr/server/core-sanitize.js.map +1 -1
  26. package/hmr/server/import-map.js +22 -5
  27. package/hmr/server/import-map.js.map +1 -1
  28. package/hmr/server/websocket-core-bridge.d.ts +41 -4
  29. package/hmr/server/websocket-core-bridge.js +42 -42
  30. package/hmr/server/websocket-core-bridge.js.map +1 -1
  31. package/hmr/server/websocket-ns-m-finalize.js +6 -6
  32. package/hmr/server/websocket-ns-m-finalize.js.map +1 -1
  33. package/hmr/server/websocket-runtime-compat.js +6 -1
  34. package/hmr/server/websocket-runtime-compat.js.map +1 -1
  35. package/hmr/server/websocket-served-module-helpers.js +2 -2
  36. package/hmr/server/websocket-served-module-helpers.js.map +1 -1
  37. package/hmr/server/websocket-vue-sfc.d.ts +0 -1
  38. package/hmr/server/websocket-vue-sfc.js +0 -16
  39. package/hmr/server/websocket-vue-sfc.js.map +1 -1
  40. package/hmr/server/websocket.d.ts +1 -1
  41. package/hmr/server/websocket.js +153 -78
  42. package/hmr/server/websocket.js.map +1 -1
  43. package/hmr/shared/runtime/boot-placeholder-ui.d.ts +1 -1
  44. package/hmr/shared/runtime/boot-placeholder-ui.js +1 -1
  45. package/hmr/shared/runtime/boot-progress.d.ts +1 -1
  46. package/hmr/shared/runtime/boot-progress.js +2 -2
  47. package/hmr/shared/runtime/boot-progress.js.map +1 -1
  48. package/hmr/shared/vendor/manifest.js +73 -2
  49. package/hmr/shared/vendor/manifest.js.map +1 -1
  50. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { createRequire } from 'node:module';
2
- import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCoreSpecToBridge, isDeepCoreSubpath, rewriteSpecifiersForDevice } from './core-sanitize.js';
2
+ import { sanitizeStrayCoreReferences, isDeepCoreSubpath, rewriteSpecifiersForDevice } from './core-sanitize.js';
3
3
  import { buildDefaultExportFooter, buildShapeInstallHeader, hasNamespaceReExport, rewriteNamespaceReExportsForShape } from './ns-core-cjs-shape.js';
4
4
  // AST tooling for robust transformations
5
5
  import { parse as babelParse } from '@babel/parser';
@@ -33,7 +33,7 @@ import { astExtractImportsAndStripTypes } from '../helpers/ast-extract.js';
33
33
  import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
34
34
  import { buildRuntimeConfig, generateImportMap } from './import-map.js';
35
35
  import { getCliFlags } from '../../helpers/cli-flags.js';
36
- import { normalizeCoreSub as normalizeCoreSubCanonical } from '../../helpers/ns-core-url.js';
36
+ import { buildCoreUrl, buildCoreUrlPath, normalizeCoreSub as normalizeCoreSubCanonical } from '../../helpers/ns-core-url.js';
37
37
  import { isRuntimeGraphExcludedPath, matchesRuntimeGraphModuleId, normalizeRuntimeGraphPath, shouldIncludeRuntimeGraphFile, shouldSkipRuntimeGraphDirectoryName } from './runtime-graph-filter.js';
38
38
  import { resolveAngularCoreHmrImportSource, rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
39
39
  import { angularSourceHasSemanticDecorator, canonicalizeTransformRequestCacheKey, collectAngularEvictionUrls, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload } from './websocket-angular-hot-update.js';
@@ -43,13 +43,13 @@ import { classifyBootRoute, classifyHmrUpdateKind, createColdBootRequestCounter,
43
43
  import { createHmrPendingMessage } from './websocket-hmr-pending.js';
44
44
  import { extractVitePrebundleId, filterExistingNodeModulesTransformCandidates, getBlockedDeviceNodeModulesReason, getFlattenedManifestMap, isCoreGlobalsReference, isEsmFrameworkPackageSpecifier, isLikelyNativeScriptPluginSpecifier, isLikelyNativeScriptRuntimePluginSpecifier, isNativeScriptCoreModule, isNativeScriptPluginModule, normalizeNativeScriptCoreSpecifier, normalizeNodeModulesSpecifier, resolveCandidateFilePath, resolveInternalRuntimePluginBareSpecifier, resolveNodeModulesPackageBoundary, resolveVendorFromCandidate, resolveVendorRouting, rewriteFsAbsoluteToNsM, shouldPreserveBareRuntimePluginSubpathImport, stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule, viteDepsPathToBareSpecifier, } from './websocket-module-specifiers.js';
45
45
  import { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
46
- import { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, extractDirectExportedNames, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest, resolveRuntimeCoreModulePath } from './websocket-core-bridge.js';
46
+ import { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, extractDirectExportedNames, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest, resolveRuntimeCoreModulePath } from './websocket-core-bridge.js';
47
47
  import { createSharedTransformRequestRunner } from './shared-transform-request.js';
48
48
  import { formatNsMHmrServeTag, getNumericServeVersionTag, rewriteNsMImportPathForHmr } from './websocket-ns-m-paths.js';
49
49
  import { ensureDynamicHmrImportHelper } from './websocket-served-module-helpers.js';
50
50
  export { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
51
51
  export { stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
52
- export { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest } from './websocket-core-bridge.js';
52
+ export { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest } from './websocket-core-bridge.js';
53
53
  export { rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
54
54
  // Re-export the canonical URL rewriter from `websocket-ns-m-paths.js` so the
55
55
  // existing test suites (which import from `./websocket.js`) keep working
@@ -458,8 +458,7 @@ function stripViteDynamicImportVirtual(code) {
458
458
  // is install-once-per-isolate, dedupes per-URL (so semver's ~30 deep
459
459
  // imports produce ~30 lines on the first boot and 0 on subsequent
460
460
  // boots within the same isolate), and uses `console.warn` so the
461
- // message reads as advisory (the round-3 finding in
462
- // LATEST-05-07-2026-HMR_ANGULAR_DEBUG_SESSION.md). Forensic detail
461
+ // message reads as advisory. Forensic detail
463
462
  // (stack) lives on `globalThis.__NS_REQUIRE_GUARD_LAST__` for
464
463
  // post-mortem inspection. Set `globalThis.__NS_REQUIRE_GUARD_VERBOSE__`
465
464
  // to `true` before any module loads to restore per-call logging.
@@ -1209,6 +1208,22 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
1209
1208
  // Running the normalizer on libraries like tslib injects harmful destructures
1210
1209
  // (e.g., `const { SuppressedError } = __ns_rt_ns_1`) that shadow globals.
1211
1210
  if (!isNodeModule) {
1211
+ // CRITICAL ORDERING: canonicalise any bare `@nativescript/core[/sub]`
1212
+ // specifiers to `/ns/core[/sub]` BEFORE the AST normaliser sees them.
1213
+ // `astNormalizeModuleImportsAndHelpers` defensively rewrites bare
1214
+ // `@nativescript/core` imports and emits a one-shot
1215
+ // `[ast-normalizer] unexpected @nativescript/core spec` warning —
1216
+ // the warning means the upstream rewriter regressed. For Vue SFC
1217
+ // `<script>` blocks the bare specifier flows through Vite's
1218
+ // transform pipeline without a per-statement source-string rewrite
1219
+ // (Vite's resolver only edits the module graph, not the emitted
1220
+ // code), so the only upstream rewriter that can canonicalise these
1221
+ // in dev mode is this regex sweep. Running it here keeps the AST
1222
+ // normaliser purely a tripwire instead of an active rewriter.
1223
+ try {
1224
+ result = sanitizeStrayCoreReferences(result);
1225
+ }
1226
+ catch { }
1212
1227
  try {
1213
1228
  result = astNormalizeModuleImportsAndHelpers(result);
1214
1229
  }
@@ -1413,8 +1428,7 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
1413
1428
  const vendorCoreRE1 = /globalThis\.__nsVendor\s*\(\s*["']@nativescript\/core["']\s*\)/g;
1414
1429
  const vendorCoreRE2 = /__nsVendor\s*\(\s*["']@nativescript\/core["']\s*\)/g;
1415
1430
  if (vendorCoreRE1.test(result) || vendorCoreRE2.test(result)) {
1416
- // Ensure an import for the core bridge exists
1417
- const hasImport = /import\s+__ns_core_bridge\s+from\s+["'][^"']*\/(?:\@ns|ns)\/core(?:\/[\d]+)?(?:\?p=[^"']+)?["']\s*;?/.test(result) || /(^|\n)\s*import\s+__ns_core_bridge\s+from\s+["']\/(?:\@ns|ns)\/core["']\s*;?/m.test(result);
1431
+ const hasImport = /import\s+__ns_core_bridge\s+from\s+["'][^"']*\/(?:\@ns|ns)\/core(?:\/[^"']+)?["']\s*;?/.test(result) || /(^|\n)\s*import\s+__ns_core_bridge\s+from\s+["']\/(?:\@ns|ns)\/core["']\s*;?/m.test(result);
1418
1432
  if (!hasImport) {
1419
1433
  result = `import __ns_core_bridge from "/ns/core";\n` + result;
1420
1434
  }
@@ -1428,7 +1442,7 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
1428
1442
  const reqCoreRE1 = /(^|[^.\w$])require\(\s*["']@nativescript\/core([^"'\n]*)["']\s*\)/g;
1429
1443
  const reqCoreRE2 = /(?:globalThis|window|self)\.require\(\s*["']@nativescript\/core([^"'\n]*)["']\s*\)/g;
1430
1444
  if (reqCoreRE1.test(result) || reqCoreRE2.test(result)) {
1431
- const hasImport = /import\s+__ns_core_bridge\s+from\s+["'][^"']*\/(?:\@ns|ns)\/core(?:\/[\d]+)?(?:\?p=[^"']+)?["']\s*;?/.test(result) || /(^|\n)\s*import\s+__ns_core_bridge\s+from\s+["']\/(?:\@ns|ns)\/core["']\s*;?/m.test(result);
1445
+ const hasImport = /import\s+__ns_core_bridge\s+from\s+["'][^"']*\/(?:\@ns|ns)\/core(?:\/[^"']+)?["']\s*;?/.test(result) || /(^|\n)\s*import\s+__ns_core_bridge\s+from\s+["']\/(?:\@ns|ns)\/core["']\s*;?/m.test(result);
1432
1446
  if (!hasImport) {
1433
1447
  result = `import __ns_core_bridge from "/ns/core";\n` + result;
1434
1448
  }
@@ -1437,21 +1451,11 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
1437
1451
  }
1438
1452
  }
1439
1453
  catch { }
1440
- // Normalize stray string-literal side-effect lines that still reference @nativescript/core
1441
- // into proper imports of the unified core bridge. This prevents the local-core-path
1442
- // fast-fail from triggering due to upstream transforms that emitted naked literals.
1443
- try {
1444
- result = normalizeStrayCoreStringLiterals(result);
1445
- }
1446
- catch { }
1447
- try {
1448
- result = fixDanglingCoreFrom(result);
1449
- }
1450
- catch { }
1451
- try {
1452
- result = normalizeAnyCoreSpecToBridge(result);
1453
- }
1454
- catch { }
1454
+ // Apply the three-pass safety net for stray @nativescript/core references
1455
+ // (naked string literals, dangling `from` merges, lingering resolved-path
1456
+ // references). Centralised in core-sanitize.sanitizeStrayCoreReferences so
1457
+ // every NS-M emitter applies the same passes in the same order.
1458
+ result = sanitizeStrayCoreReferences(result);
1455
1459
  result = ensureVariableDynamicImportHelper(result);
1456
1460
  // Normalize any lingering @nativescript/core imports to the /ns/core bridge (non-destructive best-effort)
1457
1461
  try {
@@ -1468,7 +1472,7 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
1468
1472
  return m.length === 2 ? `${m[0].trim()}: ${m[1].trim()}` : seg;
1469
1473
  })
1470
1474
  .join(', ');
1471
- const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1475
+ const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[^"']+)?)['"];?\s*/gm;
1472
1476
  result = result.replace(reNamed, (_full, pfx, specList, src) => {
1473
1477
  // Deep subpath URLs serve actual ESM with real named exports — skip.
1474
1478
  if (isDeepCoreSubpath(src))
@@ -1478,7 +1482,7 @@ function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = fa
1478
1482
  const decl = `const { ${toDestructureCore(specList)} } = ${tmp};`;
1479
1483
  return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
1480
1484
  });
1481
- const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1485
+ const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[^"']+)?)['"];?\s*/gm;
1482
1486
  result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
1483
1487
  if (isDeepCoreSubpath(src))
1484
1488
  return _full;
@@ -1723,7 +1727,10 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
1723
1727
  }
1724
1728
  }
1725
1729
  // Ensure there are no lingering named imports from the unified core bridge.
1726
- // Converts named imports from /ns/core[/ver][?p=...] into default import + destructuring.
1730
+ // Converts named imports from `/ns/core[/<sub>]` into default import +
1731
+ // destructuring. The package-main bridge serves a shape-shifted module whose
1732
+ // named exports come from `__ns_core_bridge.default` rather than ESM named
1733
+ // exports, so a named-import binding would be `undefined` at evaluation.
1727
1734
  function ensureDestructureCoreImports(code) {
1728
1735
  try {
1729
1736
  let result = code;
@@ -1737,8 +1744,8 @@ function ensureDestructureCoreImports(code) {
1737
1744
  return m.length === 2 ? `${m[0].trim()}: ${m[1].trim()}` : seg;
1738
1745
  })
1739
1746
  .join(', ');
1740
- // import { A, B } from '/ns/core[/ver][?p=...]'
1741
- const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1747
+ // import { A, B } from '/ns/core[/<sub>]'
1748
+ const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[^"']+)?)['"];?\s*/gm;
1742
1749
  result = result.replace(reNamed, (_full, pfx, specList, src) => {
1743
1750
  // Deep subpath URLs serve actual ESM with real named exports — skip.
1744
1751
  if (isDeepCoreSubpath(src))
@@ -1748,8 +1755,8 @@ function ensureDestructureCoreImports(code) {
1748
1755
  const decl = `const { ${toDestructure(specList)} } = ${tmp};`;
1749
1756
  return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
1750
1757
  });
1751
- // import Default, { A, B } from '/ns/core[...]'
1752
- const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
1758
+ // import Default, { A, B } from '/ns/core[/<sub>]'
1759
+ const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[^"']+)?)['"];?\s*/gm;
1753
1760
  result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
1754
1761
  if (isDeepCoreSubpath(src))
1755
1762
  return _full;
@@ -1866,6 +1873,27 @@ function dedupeRtNamedImportsAgainstDestructures(code) {
1866
1873
  */
1867
1874
  export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
1868
1875
  let result = code;
1876
+ // Pre-normalize concatenated imports onto their own lines.
1877
+ //
1878
+ // Babel's `genCode(ast, { retainLines: true })` in
1879
+ // `astNormalizeModuleImportsAndHelpers` (which runs before us in the SFC
1880
+ // asm pipeline, and in several other hot paths) can emit multiple
1881
+ // statements on a single line, e.g.:
1882
+ // `} = __ns_rt_ns_1;import { $goTo } from "../utils";import PageWrapper from ...;`
1883
+ //
1884
+ // IMPORT_PATTERN_1/_2/_3/SIDE_EFFECT all anchor on `(?:^|\n)\s*import`,
1885
+ // so any import past the first one on a single line is silently dropped
1886
+ // by the rewriter. Downstream that leaves bare relative specifiers like
1887
+ // `../utils` to be resolved by the iOS HTTP ESM loader, which interprets
1888
+ // them relative to the `/ns/asm/0?path=...` URL and 404s on the
1889
+ // resulting `/ns/utils`. Splitting `;import` onto its own line makes the
1890
+ // existing patterns match every import, restoring the rewrite contract.
1891
+ //
1892
+ // Mirrors the same normalization already performed in
1893
+ // `core-sanitize.ts::normalizeStrayCoreStringLiterals` (`;\s*import` →
1894
+ // `;\nimport`) but applied universally at the rewriter entry point so
1895
+ // every caller benefits without having to opt in.
1896
+ result = result.replace(/;\s*import\s+/g, ';\nimport ');
1869
1897
  const httpOriginSafe = httpOrigin;
1870
1898
  const mixedRuntimePluginHttpRootPackages = collectMixedRuntimePluginHttpRootPackages(result, projectRoot);
1871
1899
  const isDynamicImportPrefix = (prefix) => /import\(\s*["']?$/.test(prefix.trimStart());
@@ -1883,10 +1911,31 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
1883
1911
  try {
1884
1912
  let coreAliasIdx = 0;
1885
1913
  const mkAlias = () => `__NSC${coreAliasIdx++}`;
1886
- const coreUrl = (sub) => {
1887
- const p = (sub || '').replace(/^\//, '');
1888
- return `${httpOriginSafe || ''}/ns/core` + (p ? `?p=${p}` : '');
1889
- };
1914
+ // Use the canonical PATH form `/ns/core/<sub>`. The iOS HTTP ESM
1915
+ // loader caches module records by URL string — every emitter
1916
+ // (`buildCoreUrl()` / `buildCoreUrlPath()`, the runtime import map,
1917
+ // vendor `require()` shims, app-side rewrites, the cold-boot
1918
+ // preload in `entry-runtime.ts`) MUST produce the same byte
1919
+ // string for the same logical core subpath. A divergence creates
1920
+ // two distinct V8 module records for the same source. Each gets
1921
+ // its own class
1922
+ // identities (TextBase, View, etc.), and side-effect patches
1923
+ // applied to one (e.g. @nativescript-community/text's
1924
+ // `TextBase.prototype.setTextDecorationAndTransform` override
1925
+ // installed when vendor.mjs evaluates `overrideSpanAndFormattedString()`
1926
+ // from `@nativescript-community/ui-label/index-common.js`) are
1927
+ // invisible to the other — manifesting as inconsistent line-height /
1928
+ // letter-spacing rendering between HMR and no-HMR.
1929
+ //
1930
+ // Mirrors `normalizeCoreSub()` (helpers/ns-core-url.ts) so the URL
1931
+ // produced here is byte-identical to what `buildCoreUrl()` produces
1932
+ // for the bundle entry, import map, and external-urls plugin.
1933
+ // Delegate to the ONE canonical URL builder so every emitter (this
1934
+ // rewriter, core-sanitize, the runtime import map, the ns-core-external-urls
1935
+ // build plugin, and main-entry) produces byte-identical URLs for
1936
+ // the same logical core module. Any drift here would re-introduce
1937
+ // the realm-split bug.
1938
+ const coreUrl = (sub) => (httpOriginSafe ? buildCoreUrl(httpOriginSafe, sub) : buildCoreUrlPath(sub));
1890
1939
  // Case 1: import { A, B } from '@nativescript/core[/sub]'
1891
1940
  result = result.replace(/(^|\n)\s*import\s*\{\s*([^}]+?)\s*\}\s*from\s+["']@nativescript\/core([^"'\n]*)["'];?/g, (_m, pfx, names, sub) => {
1892
1941
  const alias = mkAlias();
@@ -2238,6 +2287,40 @@ export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, proje
2238
2287
  return `${prefix}./${depFile}${suffix}`;
2239
2288
  }
2240
2289
  }
2290
+ // ── Bare specifier vendor routing ────────────────────────────
2291
+ // Bare specifiers like `pinia`, `dayjs`, `lodash` never reach
2292
+ // the `nodeModulesSpecifier` branch above because
2293
+ // `normalizeNodeModulesSpecifier` keys on a literal
2294
+ // `/node_modules/` segment in the path. Without this check
2295
+ // they'd fall straight into the HTTP fallback below and get
2296
+ // rewritten to `/ns/m/node_modules/<spec>`, which serves the
2297
+ // package source over HTTP and bypasses the device-side import
2298
+ // map's `<pkg>` → `ns-vendor://<pkg>` entry. For CJS/UMD
2299
+ // packages (e.g. Pinia) the bare HTTP path doesn't expose the
2300
+ // full named-exports surface (only the default export round-
2301
+ // trips), so consumers like
2302
+ // `import { defineStore } from "pinia"` blow up at instantiate
2303
+ // time with `SyntaxError: ... does not provide an export named
2304
+ // 'defineStore'`. Preserving the bare spec lets the vendor
2305
+ // bridge serve it from the prebuilt `bundle.mjs`, which already
2306
+ // re-exports the full CJS surface. Subpath imports
2307
+ // (`pinia/plugins/foo`) intentionally fall through to the
2308
+ // HTTP fallback — `resolveVendorRouting` returns
2309
+ // `{ route: 'http' }` for non-main-entry subpaths even when the
2310
+ // root package is in the manifest, mirroring the
2311
+ // `nodeModulesSpecifier` branch.
2312
+ if (spec && !spec.startsWith('/') && !spec.startsWith('./') && !spec.startsWith('../') && !/^https?:\/\//i.test(spec) && !spec.startsWith('ns-vendor:') && !spec.startsWith('@nativescript/core')) {
2313
+ const bareNpmRe = /^(?:@[A-Za-z0-9][\w.-]*\/)?[A-Za-z0-9][\w.-]*(?:\/[\w.\-/]+)?$/;
2314
+ if (bareNpmRe.test(spec)) {
2315
+ const bareVendorRouting = resolveVendorRouting(spec, projectRoot);
2316
+ if (bareVendorRouting?.route === 'vendor') {
2317
+ if (verbose) {
2318
+ console.log(`[rewrite] bare vendor import: ${spec} → ${bareVendorRouting.bareSpec}`);
2319
+ }
2320
+ return `${prefix}${bareVendorRouting.bareSpec}${suffix}`;
2321
+ }
2322
+ }
2323
+ }
2241
2324
  // Bare npm package specifier fallback — route to /ns/m/node_modules/.
2242
2325
  // This catches specifiers like `source-map-js/lib/source-map-generator.js`
2243
2326
  // emitted by helpers such as the CommonJS compat transform, which Vite
@@ -3133,7 +3216,6 @@ function createHmrWebSocketPlugin(opts) {
3133
3216
  const origin = getServerOrigin(server);
3134
3217
  code = ensureVersionedRtImports(code, origin, graphVersion);
3135
3218
  code = ACTIVE_STRATEGY.ensureVersionedImports(code, origin, graphVersion);
3136
- code = ensureVersionedCoreImports(code, origin, graphVersion);
3137
3219
  }
3138
3220
  catch { }
3139
3221
  // Compute rel .mjs output path
@@ -3190,7 +3272,6 @@ function createHmrWebSocketPlugin(opts) {
3190
3272
  try {
3191
3273
  depCode = ensureVersionedRtImports(depCode, getServerOrigin(server), graphVersion);
3192
3274
  depCode = ACTIVE_STRATEGY.ensureVersionedImports(depCode, getServerOrigin(server), graphVersion);
3193
- depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
3194
3275
  }
3195
3276
  catch { }
3196
3277
  let depRel = depResolved.replace(/^\//, '').replace(/\.(tsx?|jsx?)$/i, '.mjs');
@@ -3838,16 +3919,14 @@ export const piniaSymbol = p.piniaSymbol;
3838
3919
  code = ensureGuardPlainDynamicImports(code, getServerOrigin(server));
3839
3920
  }
3840
3921
  catch { }
3841
- // Extra hardening: normalize any remaining core references to the unified bridge
3842
- // - Stray string-literals
3843
- // - Dangling `from` merges
3844
- // - Any spec (including /node_modules resolves) that still references '@nativescript/core'
3845
- // Do this right before the final fast-fail assertion. If a rewrite occurred, add a small marker for diagnostics.
3922
+ // Extra hardening before the fast-fail assertion: run the
3923
+ // consolidated stray-core-reference safety net. If any
3924
+ // rewrite occurred, leave a diagnostic marker so the
3925
+ // pipeline review log explains why the served code carries
3926
+ // it.
3846
3927
  try {
3847
3928
  const __before = code;
3848
- code = normalizeStrayCoreStringLiterals(code);
3849
- code = fixDanglingCoreFrom(code);
3850
- code = normalizeAnyCoreSpecToBridge(code);
3929
+ code = sanitizeStrayCoreReferences(code);
3851
3930
  if (code !== __before) {
3852
3931
  code = `// [hmr-sanitize] core-literal->bridge\n` + code;
3853
3932
  }
@@ -3920,7 +3999,6 @@ export const piniaSymbol = p.piniaSymbol;
3920
3999
  const verNum = 0;
3921
4000
  code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
3922
4001
  code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
3923
- code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
3924
4002
  }
3925
4003
  catch { }
3926
4004
  // `/ns/m` URL finalize step.
@@ -4135,7 +4213,12 @@ export const piniaSymbol = p.piniaSymbol;
4135
4213
  const origin = getServerOrigin(server);
4136
4214
  let code = `// [ns-rt][v2.3] NativeScript-Vue runtime bridge (module-scoped cache, no globals)\n` +
4137
4215
  `const __origin = ((typeof globalThis !== 'undefined' && globalThis && globalThis.__NS_HTTP_ORIGIN__) || (new URL(import.meta.url)).origin);\n` +
4138
- `let __ns_core_bridge = null; try { import(__origin + "/ns/core/${rtVer}").then(m => { __ns_core_bridge = m; }).catch(() => {}); } catch {}\n` +
4216
+ // Always target the canonical, unversioned `/ns/core` URL the
4217
+ // runtime import map maps bare `@nativescript/core` to the same
4218
+ // URL, so vendor `require('@nativescript/core')` and this dynamic
4219
+ // import end up at one iOS HTTP-ESM module record (and one class
4220
+ // identity realm).
4221
+ `let __ns_core_bridge = null; try { import(__origin + "/ns/core").then(m => { __ns_core_bridge = m; }).catch(() => {}); } catch {}\n` +
4139
4222
  `const g = globalThis;\n` +
4140
4223
  `const reg = (g.__nsVendorRegistry ||= new Map());\n` +
4141
4224
  `const req = reg && reg.get ? (g.__nsVendorRequire || g.__nsRequire || g.require) : (g.__nsRequire || g.require);\n` +
@@ -4358,7 +4441,7 @@ export const piniaSymbol = p.piniaSymbol;
4358
4441
  return next();
4359
4442
  }
4360
4443
  });
4361
- // 2.6) ESM bridge for @nativescript/core: GET /ns/core[/<ver>][?p=sub/path]
4444
+ // 2.6) ESM bridge for @nativescript/core: GET /ns/core[/<sub>]
4362
4445
  //
4363
4446
  // Since bundle.mjs no longer bundles @nativescript/core (it is
4364
4447
  // declared external in the rolldown config under HMR), this
@@ -4382,12 +4465,29 @@ export const piniaSymbol = p.piniaSymbol;
4382
4465
  const coreRequest = parseCoreBridgeRequest(urlObj.pathname, urlObj.searchParams, Number(graphVersion || 0));
4383
4466
  if (!coreRequest)
4384
4467
  return next();
4468
+ // Non-canonical incoming URL — every emitter is supposed
4469
+ // to canonicalize before hitting the device. Promote the
4470
+ // drift to a 301 redirect so iOS still gets the file at
4471
+ // the canonical URL (no realm split) but the offending
4472
+ // caller is forced to update. We log the offending raw
4473
+ // pathname so the regression source is easy to find.
4474
+ if (coreRequest.canonicalPath) {
4475
+ try {
4476
+ console.warn(`[ns-core-bridge] 301 ${urlObj.pathname}${urlObj.search} → ${coreRequest.canonicalPath} (non-canonical core URL — please update emitter)`);
4477
+ }
4478
+ catch { }
4479
+ res.setHeader('Access-Control-Allow-Origin', '*');
4480
+ res.setHeader('Location', coreRequest.canonicalPath);
4481
+ res.statusCode = 301;
4482
+ res.end();
4483
+ return;
4484
+ }
4385
4485
  res.setHeader('Access-Control-Allow-Origin', '*');
4386
4486
  res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
4387
4487
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
4388
4488
  res.setHeader('Pragma', 'no-cache');
4389
4489
  res.setHeader('Expires', '0');
4390
- const { normalizedSub, sub, ver } = coreRequest;
4490
+ const { normalizedSub, sub } = coreRequest;
4391
4491
  const resolveModuleId = async (moduleId) => {
4392
4492
  const resolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
4393
4493
  return typeof resolved === 'string' ? resolved : resolved?.id || null;
@@ -4413,7 +4513,7 @@ export const piniaSymbol = p.piniaSymbol;
4413
4513
  // Vite's transform output references module IDs with /@fs,
4414
4514
  // relative specifiers, or absolute project paths. Rewrite
4415
4515
  // those to URLs iOS can fetch over HTTP.
4416
- let rewritten = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
4516
+ let rewritten = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(graphVersion || 0));
4417
4517
  // Invariant D (CJS/ESM interop shape) — EXPORT-SIDE fix.
4418
4518
  //
4419
4519
  // `@nativescript/core/index.js` declares namespace
@@ -5144,17 +5244,16 @@ export const piniaSymbol = p.piniaSymbol;
5144
5244
  code = ensureVariableDynamicImportHelper(code);
5145
5245
  try {
5146
5246
  // For variant requests under /ns/sfc, prefer the version from the path segment when present
5147
- // so that any internal '/ns/rt', '/ns/core', or '/ns/sfc' imports are aligned with the same version.
5247
+ // so that any internal '/ns/rt' or '/ns/sfc' imports are aligned with the same version.
5248
+ // `/ns/core` URLs are intentionally unversioned (realm-split history).
5148
5249
  const verNum = Number(verFromPath || '0');
5149
5250
  if (Number.isFinite(verNum) && verNum > 0) {
5150
5251
  code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
5151
5252
  code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
5152
- code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
5153
5253
  }
5154
5254
  else {
5155
5255
  code = ensureVersionedRtImports(code, getServerOrigin(server), graphVersion);
5156
5256
  code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), graphVersion);
5157
- code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
5158
5257
  }
5159
5258
  }
5160
5259
  catch { }
@@ -5164,7 +5263,7 @@ export const piniaSymbol = p.piniaSymbol;
5164
5263
  }
5165
5264
  catch { }
5166
5265
  // CRITICAL: As a last step for script/template variants, re-run AST normalization and strip
5167
- // any sentinel destructures that could cause duplicate locals, then re-apply core versioning.
5266
+ // any sentinel destructures that could cause duplicate locals, then re-apply rt versioning.
5168
5267
  try {
5169
5268
  code = astNormalizeModuleImportsAndHelpers(code);
5170
5269
  }
@@ -5178,11 +5277,9 @@ export const piniaSymbol = p.piniaSymbol;
5178
5277
  const verNum = Number(verFromPath || '0');
5179
5278
  if (Number.isFinite(verNum) && verNum > 0) {
5180
5279
  code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
5181
- code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
5182
5280
  }
5183
5281
  else {
5184
5282
  code = ensureVersionedRtImports(code, getServerOrigin(server), graphVersion);
5185
- code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
5186
5283
  }
5187
5284
  }
5188
5285
  catch { }
@@ -5595,10 +5692,6 @@ export const piniaSymbol = p.piniaSymbol;
5595
5692
  parts.push(`export default __ns_sfc__`);
5596
5693
  let inlineCode = parts.filter(Boolean).join('\n');
5597
5694
  inlineCode = processCodeForDevice(inlineCode, false, true);
5598
- try {
5599
- inlineCode = ensureVersionedCoreImports(inlineCode, getServerOrigin(server), Number(ver));
5600
- }
5601
- catch { }
5602
5695
  try {
5603
5696
  inlineCode = ensureDestructureCoreImports(inlineCode);
5604
5697
  }
@@ -5665,10 +5758,6 @@ export const piniaSymbol = p.piniaSymbol;
5665
5758
  outParts.push('export default __ns_sfc__');
5666
5759
  let inlineCode2 = outParts.filter(Boolean).join('\n');
5667
5760
  inlineCode2 = processCodeForDevice(inlineCode2, false, true);
5668
- try {
5669
- inlineCode2 = ensureVersionedCoreImports(inlineCode2, getServerOrigin(server), Number(ver));
5670
- }
5671
- catch { }
5672
5761
  try {
5673
5762
  inlineCode2 = ensureDestructureCoreImports(inlineCode2);
5674
5763
  }
@@ -5872,7 +5961,6 @@ export const piniaSymbol = p.piniaSymbol;
5872
5961
  const origin = getServerOrigin(server);
5873
5962
  inlineCode2 = ensureVersionedRtImports(inlineCode2, origin, Number(ver));
5874
5963
  inlineCode2 = ACTIVE_STRATEGY.ensureVersionedImports(inlineCode2, origin, Number(ver));
5875
- inlineCode2 = ensureVersionedCoreImports(inlineCode2, origin, Number(ver));
5876
5964
  }
5877
5965
  catch { }
5878
5966
  // Normalize imports/helpers via AST to ensure _defineComponent and other helpers are bound once
@@ -5976,10 +6064,6 @@ export const piniaSymbol = p.piniaSymbol;
5976
6064
  // Run full device processing so helper aliasing and globals are consistent in this path too
5977
6065
  let code = REQUIRE_GUARD_SNIPPET + asm;
5978
6066
  code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(base), base);
5979
- try {
5980
- code = ensureVersionedCoreImports(code, getServerOrigin(server), Number(ver));
5981
- }
5982
- catch { }
5983
6067
  code = rewriteImports(code, base, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, getServerOrigin(server));
5984
6068
  try {
5985
6069
  code = ensureDestructureCoreImports(code);
@@ -5991,7 +6075,6 @@ export const piniaSymbol = p.piniaSymbol;
5991
6075
  const origin = getServerOrigin(server);
5992
6076
  code = ensureVersionedRtImports(code, origin, Number(ver));
5993
6077
  code = ACTIVE_STRATEGY.ensureVersionedImports(code, origin, Number(ver));
5994
- code = ensureVersionedCoreImports(code, origin, Number(ver));
5995
6078
  }
5996
6079
  catch { }
5997
6080
  // Inline-template body path already runs processCodeForDevice (AST + sanitizers); no additional _defineComponent fix needed
@@ -6149,10 +6232,6 @@ export const piniaSymbol = p.piniaSymbol;
6149
6232
  // Reuse existing sanitation chain (lightweight)
6150
6233
  code = cleanCode(code);
6151
6234
  code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
6152
- try {
6153
- code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
6154
- }
6155
- catch { }
6156
6235
  code = rewriteImports(code, spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
6157
6236
  code = ensureVariableDynamicImportHelper(code);
6158
6237
  code = ensureGuardPlainDynamicImports(code, origin);
@@ -6204,10 +6283,6 @@ export const piniaSymbol = p.piniaSymbol;
6204
6283
  let depCode = depTrans.code;
6205
6284
  depCode = cleanCode(depCode);
6206
6285
  depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
6207
- try {
6208
- depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
6209
- }
6210
- catch { }
6211
6286
  depCode = rewriteImports(depCode, depResolved, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
6212
6287
  depCode = ensureVariableDynamicImportHelper(depCode);
6213
6288
  depCode = ensureGuardPlainDynamicImports(depCode, origin);