@nativescript/vite 8.0.0-alpha.1 → 8.0.0-alpha.10
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.
- package/configuration/angular.d.ts +1 -1
- package/configuration/angular.js +486 -140
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +159 -29
- package/configuration/base.js.map +1 -1
- package/configuration/javascript.js +3 -3
- package/configuration/javascript.js.map +1 -1
- package/configuration/solid.js +7 -0
- package/configuration/solid.js.map +1 -1
- package/configuration/typescript.js +4 -4
- package/configuration/typescript.js.map +1 -1
- package/helpers/angular/angular-linker.js +38 -42
- package/helpers/angular/angular-linker.js.map +1 -1
- package/helpers/angular/inject-component-hmr-registration.d.ts +112 -0
- package/helpers/angular/inject-component-hmr-registration.js +359 -0
- package/helpers/angular/inject-component-hmr-registration.js.map +1 -0
- package/helpers/angular/inline-decorator-component-templates.d.ts +3 -0
- package/helpers/angular/inline-decorator-component-templates.js +400 -0
- package/helpers/angular/inline-decorator-component-templates.js.map +1 -0
- package/helpers/angular/shared-linker.d.ts +7 -0
- package/helpers/angular/shared-linker.js +37 -1
- package/helpers/angular/shared-linker.js.map +1 -1
- package/helpers/angular/synthesize-decorator-ctor-parameters.d.ts +1 -0
- package/helpers/angular/synthesize-decorator-ctor-parameters.js +256 -0
- package/helpers/angular/synthesize-decorator-ctor-parameters.js.map +1 -0
- package/helpers/angular/synthesize-injectable-factories.d.ts +3 -0
- package/helpers/angular/synthesize-injectable-factories.js +414 -0
- package/helpers/angular/synthesize-injectable-factories.js.map +1 -0
- package/helpers/angular/util.d.ts +1 -0
- package/helpers/angular/util.js +88 -0
- package/helpers/angular/util.js.map +1 -1
- package/helpers/commonjs-plugins.d.ts +5 -2
- package/helpers/commonjs-plugins.js +126 -0
- package/helpers/commonjs-plugins.js.map +1 -1
- package/helpers/config-as-json.js +10 -0
- package/helpers/config-as-json.js.map +1 -1
- package/helpers/esbuild-platform-resolver.js +5 -5
- package/helpers/esbuild-platform-resolver.js.map +1 -1
- package/helpers/external-configs.d.ts +9 -1
- package/helpers/external-configs.js +31 -6
- package/helpers/external-configs.js.map +1 -1
- package/helpers/global-defines.d.ts +51 -0
- package/helpers/global-defines.js +77 -0
- package/helpers/global-defines.js.map +1 -1
- package/helpers/import-meta-path.d.ts +4 -0
- package/helpers/import-meta-path.js +5 -0
- package/helpers/import-meta-path.js.map +1 -0
- package/helpers/import-specifier.d.ts +1 -0
- package/helpers/import-specifier.js +18 -0
- package/helpers/import-specifier.js.map +1 -0
- package/helpers/logging.d.ts +1 -0
- package/helpers/logging.js +63 -3
- package/helpers/logging.js.map +1 -1
- package/helpers/main-entry.d.ts +5 -2
- package/helpers/main-entry.js +365 -116
- package/helpers/main-entry.js.map +1 -1
- package/helpers/nativeclass-transform.js +8 -127
- package/helpers/nativeclass-transform.js.map +1 -1
- package/helpers/nativeclass-transformer-plugin.d.ts +19 -1
- package/helpers/nativeclass-transformer-plugin.js +318 -36
- package/helpers/nativeclass-transformer-plugin.js.map +1 -1
- package/helpers/ns-core-url.d.ts +83 -0
- package/helpers/ns-core-url.js +167 -0
- package/helpers/ns-core-url.js.map +1 -0
- package/helpers/prelink-angular.js +1 -4
- package/helpers/prelink-angular.js.map +1 -1
- package/helpers/project.d.ts +35 -0
- package/helpers/project.js +120 -2
- package/helpers/project.js.map +1 -1
- package/helpers/ts-config-paths.js +50 -2
- package/helpers/ts-config-paths.js.map +1 -1
- package/helpers/workers.d.ts +20 -19
- package/helpers/workers.js +620 -3
- package/helpers/workers.js.map +1 -1
- package/hmr/client/css-handler.js +60 -19
- package/hmr/client/css-handler.js.map +1 -1
- package/hmr/client/hmr-pending-overlay.d.ts +27 -0
- package/hmr/client/hmr-pending-overlay.js +50 -0
- package/hmr/client/hmr-pending-overlay.js.map +1 -0
- package/hmr/client/index.js +597 -24
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +5 -0
- package/hmr/client/utils.js +212 -21
- package/hmr/client/utils.js.map +1 -1
- package/hmr/entry-runtime.d.ts +10 -0
- package/hmr/entry-runtime.js +330 -42
- package/hmr/entry-runtime.js.map +1 -1
- package/hmr/frameworks/angular/client/index.d.ts +3 -1
- package/hmr/frameworks/angular/client/index.js +821 -25
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/server/linker.js +37 -6
- package/hmr/frameworks/angular/server/linker.js.map +1 -1
- package/hmr/frameworks/angular/server/strategy.js +30 -6
- package/hmr/frameworks/angular/server/strategy.js.map +1 -1
- package/hmr/frameworks/typescript/server/strategy.js +8 -2
- package/hmr/frameworks/typescript/server/strategy.js.map +1 -1
- package/hmr/frameworks/vue/client/index.js +18 -42
- package/hmr/frameworks/vue/client/index.js.map +1 -1
- package/hmr/helpers/ast-normalizer.js +22 -10
- package/hmr/helpers/ast-normalizer.js.map +1 -1
- package/hmr/helpers/cjs-named-exports.d.ts +23 -0
- package/hmr/helpers/cjs-named-exports.js +152 -0
- package/hmr/helpers/cjs-named-exports.js.map +1 -0
- package/hmr/server/constants.d.ts +1 -0
- package/hmr/server/constants.js +14 -3
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +49 -2
- package/hmr/server/core-sanitize.js +267 -24
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/import-map.d.ts +65 -0
- package/hmr/server/import-map.js +222 -0
- package/hmr/server/import-map.js.map +1 -0
- package/hmr/server/index.d.ts +2 -1
- package/hmr/server/index.js.map +1 -1
- package/hmr/server/ns-core-cjs-shape.d.ts +204 -0
- package/hmr/server/ns-core-cjs-shape.js +271 -0
- package/hmr/server/ns-core-cjs-shape.js.map +1 -0
- package/hmr/server/perf-instrumentation.d.ts +114 -0
- package/hmr/server/perf-instrumentation.js +195 -0
- package/hmr/server/perf-instrumentation.js.map +1 -0
- package/hmr/server/runtime-graph-filter.d.ts +5 -0
- package/hmr/server/runtime-graph-filter.js +21 -0
- package/hmr/server/runtime-graph-filter.js.map +1 -0
- package/hmr/server/shared-transform-request.d.ts +12 -0
- package/hmr/server/shared-transform-request.js +144 -0
- package/hmr/server/shared-transform-request.js.map +1 -0
- package/hmr/server/vite-plugin.d.ts +21 -1
- package/hmr/server/vite-plugin.js +461 -22
- package/hmr/server/vite-plugin.js.map +1 -1
- package/hmr/server/websocket-angular-entry.d.ts +2 -0
- package/hmr/server/websocket-angular-entry.js +68 -0
- package/hmr/server/websocket-angular-entry.js.map +1 -0
- package/hmr/server/websocket-angular-hot-update.d.ts +78 -0
- package/hmr/server/websocket-angular-hot-update.js +413 -0
- package/hmr/server/websocket-angular-hot-update.js.map +1 -0
- package/hmr/server/websocket-core-bridge.d.ts +21 -0
- package/hmr/server/websocket-core-bridge.js +357 -0
- package/hmr/server/websocket-core-bridge.js.map +1 -0
- package/hmr/server/websocket-graph-upsert.d.ts +21 -0
- package/hmr/server/websocket-graph-upsert.js +33 -0
- package/hmr/server/websocket-graph-upsert.js.map +1 -0
- package/hmr/server/websocket-hmr-pending.d.ts +43 -0
- package/hmr/server/websocket-hmr-pending.js +55 -0
- package/hmr/server/websocket-hmr-pending.js.map +1 -0
- package/hmr/server/websocket-module-bindings.d.ts +6 -0
- package/hmr/server/websocket-module-bindings.js +471 -0
- package/hmr/server/websocket-module-bindings.js.map +1 -0
- package/hmr/server/websocket-module-specifiers.d.ts +101 -0
- package/hmr/server/websocket-module-specifiers.js +820 -0
- package/hmr/server/websocket-module-specifiers.js.map +1 -0
- package/hmr/server/websocket-ns-m-finalize.d.ts +22 -0
- package/hmr/server/websocket-ns-m-finalize.js +88 -0
- package/hmr/server/websocket-ns-m-finalize.js.map +1 -0
- package/hmr/server/websocket-ns-m-paths.d.ts +3 -0
- package/hmr/server/websocket-ns-m-paths.js +92 -0
- package/hmr/server/websocket-ns-m-paths.js.map +1 -0
- package/hmr/server/websocket-ns-m-request.d.ts +45 -0
- package/hmr/server/websocket-ns-m-request.js +196 -0
- package/hmr/server/websocket-ns-m-request.js.map +1 -0
- package/hmr/server/websocket-runtime-compat.d.ts +19 -0
- package/hmr/server/websocket-runtime-compat.js +287 -0
- package/hmr/server/websocket-runtime-compat.js.map +1 -0
- package/hmr/server/websocket-served-module-helpers.d.ts +36 -0
- package/hmr/server/websocket-served-module-helpers.js +631 -0
- package/hmr/server/websocket-served-module-helpers.js.map +1 -0
- package/hmr/server/websocket-txn.d.ts +6 -0
- package/hmr/server/websocket-txn.js +45 -0
- package/hmr/server/websocket-txn.js.map +1 -0
- package/hmr/server/websocket-vendor-unifier.d.ts +10 -0
- package/hmr/server/websocket-vendor-unifier.js +51 -0
- package/hmr/server/websocket-vendor-unifier.js.map +1 -0
- package/hmr/server/websocket-vue-sfc.d.ts +27 -0
- package/hmr/server/websocket-vue-sfc.js +1069 -0
- package/hmr/server/websocket-vue-sfc.js.map +1 -0
- package/hmr/server/websocket.d.ts +26 -3
- package/hmr/server/websocket.js +2233 -796
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/package-classifier.d.ts +9 -0
- package/hmr/shared/package-classifier.js +58 -0
- package/hmr/shared/package-classifier.js.map +1 -0
- package/hmr/shared/runtime/boot-timeline.d.ts +17 -0
- package/hmr/shared/runtime/boot-timeline.js +51 -0
- package/hmr/shared/runtime/boot-timeline.js.map +1 -0
- package/hmr/shared/runtime/browser-runtime-contract.d.ts +64 -0
- package/hmr/shared/runtime/browser-runtime-contract.js +54 -0
- package/hmr/shared/runtime/browser-runtime-contract.js.map +1 -0
- package/hmr/shared/runtime/dev-overlay.d.ts +85 -0
- package/hmr/shared/runtime/dev-overlay.js +1236 -0
- package/hmr/shared/runtime/dev-overlay.js.map +1 -0
- package/hmr/shared/runtime/http-only-boot.d.ts +1 -0
- package/hmr/shared/runtime/http-only-boot.js +53 -6
- package/hmr/shared/runtime/http-only-boot.js.map +1 -1
- package/hmr/shared/runtime/module-provenance.d.ts +1 -0
- package/hmr/shared/runtime/module-provenance.js +63 -0
- package/hmr/shared/runtime/module-provenance.js.map +1 -0
- package/hmr/shared/runtime/platform-polyfills.d.ts +26 -0
- package/hmr/shared/runtime/platform-polyfills.js +122 -0
- package/hmr/shared/runtime/platform-polyfills.js.map +1 -0
- package/hmr/shared/runtime/root-placeholder.d.ts +1 -0
- package/hmr/shared/runtime/root-placeholder.js +552 -82
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/session-bootstrap.d.ts +1 -0
- package/hmr/shared/runtime/session-bootstrap.js +195 -0
- package/hmr/shared/runtime/session-bootstrap.js.map +1 -0
- package/hmr/shared/runtime/vendor-bootstrap.js +52 -15
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest.d.ts +37 -0
- package/hmr/shared/vendor/manifest.js +677 -57
- package/hmr/shared/vendor/manifest.js.map +1 -1
- package/hmr/shared/vendor/registry.js +104 -7
- package/hmr/shared/vendor/registry.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.js +5 -0
- package/index.js.map +1 -1
- package/package.json +14 -2
- package/runtime/core-aliases-early.js +94 -67
- package/runtime/core-aliases-early.js.map +1 -1
- package/shims/solid-jsx-runtime.d.ts +7 -0
- package/shims/solid-jsx-runtime.js +17 -0
- package/shims/solid-jsx-runtime.js.map +1 -0
package/hmr/server/websocket.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCoreSpecToBridge } from './core-sanitize.js';
|
|
2
|
+
import { normalizeStrayCoreStringLiterals, fixDanglingCoreFrom, normalizeAnyCoreSpecToBridge, isDeepCoreSubpath, rewriteSpecifiersForDevice } from './core-sanitize.js';
|
|
3
|
+
import { buildDefaultExportFooter, buildShapeInstallHeader, hasNamespaceReExport, rewriteNamespaceReExportsForShape } from './ns-core-cjs-shape.js';
|
|
3
4
|
// AST tooling for robust transformations
|
|
4
5
|
import { parse as babelParse } from '@babel/parser';
|
|
5
6
|
import { genCode } from '../helpers/babel.js';
|
|
6
7
|
import babelCore from '@babel/core';
|
|
7
|
-
import pluginTransformTypescript from '@babel/plugin-transform-typescript';
|
|
8
8
|
import traverse from '@babel/traverse';
|
|
9
9
|
// Ensure traverse callable across CJS/ESM builds
|
|
10
10
|
const babelTraverse = traverse?.default || traverse;
|
|
11
11
|
import * as t from '@babel/types';
|
|
12
|
-
import { existsSync, readFileSync } from 'fs';
|
|
12
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
13
13
|
import { astNormalizeModuleImportsAndHelpers, astVerifyAndAnnotateDuplicates } from '../helpers/ast-normalizer.js';
|
|
14
|
+
import { getCjsNamedExports } from '../helpers/cjs-named-exports.js';
|
|
14
15
|
import { stripRtCoreSentinel, stripDanglingViteCjsImports } from '../helpers/sanitize.js';
|
|
15
16
|
import { WebSocketServer } from 'ws';
|
|
16
17
|
import * as path from 'path';
|
|
17
18
|
import { createHash } from 'crypto';
|
|
18
19
|
import * as PAT from './constants.js';
|
|
19
20
|
import { getVendorManifest, resolveVendorSpecifier } from '../shared/vendor/registry.js';
|
|
20
|
-
import { getPackageJson, getProjectFilePath } from '../../helpers/project.js';
|
|
21
|
+
import { getMonorepoWorkspaceRoot, getPackageJson, getProjectFilePath, getProjectRootPath } from '../../helpers/project.js';
|
|
21
22
|
import { loadPrebuiltVendorManifest } from '../shared/vendor/manifest-loader.js';
|
|
22
23
|
import '../vendor-bootstrap.js';
|
|
23
24
|
import { NS_NATIVE_TAGS } from './compiler.js';
|
|
@@ -30,12 +31,83 @@ import { typescriptServerStrategy } from '../frameworks/typescript/server/strate
|
|
|
30
31
|
import { buildInlineTemplateBlock, createProcessSfcCode, extractTemplateRender, processTemplateVariantMinimal } from '../frameworks/vue/server/sfc-transforms.js';
|
|
31
32
|
import { astExtractImportsAndStripTypes } from '../helpers/ast-extract.js';
|
|
32
33
|
import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
|
|
34
|
+
import { buildRuntimeConfig, generateImportMap } from './import-map.js';
|
|
35
|
+
import { getCliFlags } from '../../helpers/cli-flags.js';
|
|
36
|
+
import { normalizeCoreSub as normalizeCoreSubCanonical } from '../../helpers/ns-core-url.js';
|
|
37
|
+
import { isRuntimeGraphExcludedPath, matchesRuntimeGraphModuleId, normalizeRuntimeGraphPath, shouldIncludeRuntimeGraphFile, shouldSkipRuntimeGraphDirectoryName } from './runtime-graph-filter.js';
|
|
38
|
+
import { resolveAngularCoreHmrImportSource, rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
|
|
39
|
+
import { angularSourceHasSemanticDecorator, canonicalizeTransformRequestCacheKey, collectAngularEvictionUrls, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload } from './websocket-angular-hot-update.js';
|
|
40
|
+
import { classifyGraphUpsert, shouldBroadcastGraphUpsertDelta, shouldBumpGraphVersion } from './websocket-graph-upsert.js';
|
|
41
|
+
import { classifyBootRoute, classifyHmrUpdateKind, createColdBootRequestCounter, formatHmrUpdateSummary, formatPopulateInitialGraphSummary, formatServerStartupBanner } from './perf-instrumentation.js';
|
|
42
|
+
import { createHmrPendingMessage } from './websocket-hmr-pending.js';
|
|
43
|
+
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';
|
|
44
|
+
import { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
|
|
45
|
+
import { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, extractDirectExportedNames, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest, resolveRuntimeCoreModulePath } from './websocket-core-bridge.js';
|
|
46
|
+
import { createSharedTransformRequestRunner } from './shared-transform-request.js';
|
|
47
|
+
import { formatNsMHmrServeTag, getNumericServeVersionTag, rewriteNsMImportPathForHmr } from './websocket-ns-m-paths.js';
|
|
48
|
+
import { ensureDynamicHmrImportHelper } from './websocket-served-module-helpers.js';
|
|
49
|
+
export { ensureNativeScriptModuleBindings, getProcessCodeResolvedSpecifierOverrides } from './websocket-module-bindings.js';
|
|
50
|
+
export { stripDecoratedServePrefixes, tryReadRawExplicitJavaScriptModule } from './websocket-module-specifiers.js';
|
|
51
|
+
export { collectStaticExportNamesFromFile, collectStaticExportOriginsFromFile, ensureVersionedCoreImports, normalizeCoreExportOriginsForRuntime, parseCoreBridgeRequest } from './websocket-core-bridge.js';
|
|
52
|
+
export { rewriteAngularEntryRegisterOnly } from './websocket-angular-entry.js';
|
|
53
|
+
// Re-export the canonical URL rewriter from `websocket-ns-m-paths.js` so the
|
|
54
|
+
// existing test suites (which import from `./websocket.js`) keep working
|
|
55
|
+
// without churn while the implementation lives in a focused module.
|
|
56
|
+
export { formatNsMHmrServeTag, rewriteNsMImportPathForHmr } from './websocket-ns-m-paths.js';
|
|
57
|
+
export { angularSourceHasSemanticDecorator, canonicalizeTransformRequestCacheKey, collectAngularEvictionUrls, collectAngularHotUpdateRoots, collectAngularTransformCacheInvalidationUrls, collectAngularTransitiveImportersForInvalidation, collectGraphUpdateModulesForHotUpdate, createSharedTransformRequestRunner, normalizeHotReloadMatchPath, shouldInvalidateAngularTransitiveImporters, shouldSuppressDefaultViteHotUpdate, shouldSuppressViteFullReloadPayload, classifyGraphUpsert, shouldBroadcastGraphUpsertDelta, shouldBumpGraphVersion };
|
|
58
|
+
const pluginTransformTypescript = (() => {
|
|
59
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
60
|
+
const loaded = requireFromHere('@babel/plugin-transform-typescript');
|
|
61
|
+
return loaded?.default || loaded;
|
|
62
|
+
})();
|
|
63
|
+
// Build a serialized process.env object from CLI --env.* flags.
|
|
64
|
+
// This is injected into every HTTP-served module so app code referencing
|
|
65
|
+
// process.env.TEST_ENV (etc.) works on device in HMR dev mode.
|
|
66
|
+
const __processEnvEntries = { NODE_ENV: 'development' };
|
|
67
|
+
try {
|
|
68
|
+
const flags = getCliFlags();
|
|
69
|
+
for (const [k, v] of Object.entries(flags || {})) {
|
|
70
|
+
// Skip internal NativeScript build flags
|
|
71
|
+
if (['ios', 'android', 'visionos', 'platform', 'hmr', 'verbose'].includes(k))
|
|
72
|
+
continue;
|
|
73
|
+
__processEnvEntries[k] = String(v);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { }
|
|
77
|
+
const __processEnvJson = JSON.stringify(__processEnvEntries);
|
|
33
78
|
const { parse, compileTemplate, compileScript } = vueSfcCompiler;
|
|
34
79
|
const APP_ROOT_DIR = getProjectAppPath();
|
|
35
80
|
const APP_VIRTUAL_PREFIX = getProjectAppVirtualPath();
|
|
36
81
|
const APP_VIRTUAL_WITH_SLASH = `${APP_VIRTUAL_PREFIX}/`;
|
|
37
82
|
const DEFAULT_MAIN_ENTRY = getProjectAppRelativePath('app.ts');
|
|
38
83
|
const DEFAULT_MAIN_ENTRY_VIRTUAL = getProjectAppVirtualPath('app.ts');
|
|
84
|
+
// Memoized resolver for the project bootstrap entry as a posix
|
|
85
|
+
// project-relative path (e.g. `/src/main.ts`). This mirrors the
|
|
86
|
+
// resolution the cold-boot wrapper performs (`getPackageJson().main` →
|
|
87
|
+
// project-relative under `/<APP_ROOT_DIR>/`) so the eviction set for
|
|
88
|
+
// HMR always lines up with the URL the runtime actually re-imports.
|
|
89
|
+
// Resolved at first call and cached: `package.json` is read at startup
|
|
90
|
+
// and never changes during a dev session, so it's safe to memoize.
|
|
91
|
+
let __ns_bootstrap_entry_rel_cached = null;
|
|
92
|
+
function getBootstrapEntryRelPath() {
|
|
93
|
+
if (__ns_bootstrap_entry_rel_cached)
|
|
94
|
+
return __ns_bootstrap_entry_rel_cached;
|
|
95
|
+
let entry = DEFAULT_MAIN_ENTRY_VIRTUAL;
|
|
96
|
+
try {
|
|
97
|
+
const pkg = getPackageJson();
|
|
98
|
+
const main = (pkg && pkg.main) || DEFAULT_MAIN_ENTRY;
|
|
99
|
+
const abs = getProjectFilePath(main).replace(/\\/g, '/');
|
|
100
|
+
const marker = `/${APP_ROOT_DIR}/`;
|
|
101
|
+
const idx = abs.indexOf(marker);
|
|
102
|
+
entry = idx >= 0 ? abs.substring(idx) : DEFAULT_MAIN_ENTRY_VIRTUAL;
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
if (!entry.startsWith('/')) {
|
|
106
|
+
entry = '/' + entry;
|
|
107
|
+
}
|
|
108
|
+
__ns_bootstrap_entry_rel_cached = entry;
|
|
109
|
+
return entry;
|
|
110
|
+
}
|
|
39
111
|
const STRATEGY_REGISTRY = new Map([
|
|
40
112
|
['vue', vueServerStrategy],
|
|
41
113
|
['angular', angularServerStrategy],
|
|
@@ -43,255 +115,177 @@ const STRATEGY_REGISTRY = new Map([
|
|
|
43
115
|
['typescript', typescriptServerStrategy],
|
|
44
116
|
]);
|
|
45
117
|
function resolveFrameworkStrategy(flavor) {
|
|
46
|
-
|
|
118
|
+
const strategy = STRATEGY_REGISTRY.get(flavor);
|
|
119
|
+
if (!strategy) {
|
|
120
|
+
throw new Error(`[ns-hmr] Unsupported framework strategy: ${flavor}`);
|
|
121
|
+
}
|
|
122
|
+
return strategy;
|
|
47
123
|
}
|
|
48
124
|
let ACTIVE_STRATEGY;
|
|
125
|
+
function isSocketClientOpen(client) {
|
|
126
|
+
if (!client) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const openState = typeof client.OPEN === 'number' ? client.OPEN : 1;
|
|
130
|
+
return client.readyState === openState;
|
|
131
|
+
}
|
|
132
|
+
function getHmrSocketRoleFromRequestUrl(requestUrl) {
|
|
133
|
+
try {
|
|
134
|
+
const url = new URL(requestUrl || '/ns-hmr', 'http://localhost');
|
|
135
|
+
return url.searchParams.get('ns_hmr_role') || 'unknown';
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return 'unknown';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function getHmrSocketRole(client) {
|
|
142
|
+
if (!client) {
|
|
143
|
+
return 'unknown';
|
|
144
|
+
}
|
|
145
|
+
return typeof client.__nsHmrClientRole === 'string' && client.__nsHmrClientRole ? client.__nsHmrClientRole : 'unknown';
|
|
146
|
+
}
|
|
147
|
+
function shouldAllowLocalCoreSanitizerPaths(contextLabel) {
|
|
148
|
+
return /\bnode_modules\/@nativescript\/vite\/hmr\/(?:client|frameworks)\//.test(contextLabel);
|
|
149
|
+
}
|
|
150
|
+
export function prepareAngularEntryForDevice(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
|
|
151
|
+
const rewrittenCode = rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp);
|
|
152
|
+
return rewriteAngularEntryRegisterOnly(rewrittenCode, resolveAngularCoreHmrImportSource(rewrittenCode, httpOrigin));
|
|
153
|
+
}
|
|
49
154
|
const processSfcCode = createProcessSfcCode(processCodeForDevice);
|
|
50
155
|
// Bare specifiers and special skip patterns (virtual, data:, etc.)
|
|
51
156
|
const VENDOR_PACKAGES = /^[A-Za-z@][^:\/\s]*$/;
|
|
52
157
|
const SKIP_PATTERNS = /^(?:data:|blob:|node:|virtual:|vite:|\0|\/@@?id|\/__vite|__vite|__x00__)/;
|
|
53
|
-
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for (const canonical of mods) {
|
|
67
|
-
const flat = canonical.replace(/\./g, '__').replace(/\//g, '_');
|
|
68
|
-
map.set(flat, canonical);
|
|
69
|
-
const alias = manifest.aliases?.[canonical];
|
|
70
|
-
if (alias) {
|
|
71
|
-
const aliasFlat = String(alias).replace(/\./g, '__').replace(/\//g, '_');
|
|
72
|
-
map.set(aliasFlat, canonical);
|
|
158
|
+
const MODULE_IMPORT_ANALYSIS_PLUGINS = ['typescript', 'jsx', 'importMeta', 'topLevelAwait', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'decorators-legacy'];
|
|
159
|
+
function collectTopLevelImportRecords(code) {
|
|
160
|
+
if (!code || typeof code !== 'string' || !/\bimport\b/.test(code)) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const ast = babelParse(code, {
|
|
165
|
+
sourceType: 'module',
|
|
166
|
+
plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
|
|
167
|
+
});
|
|
168
|
+
const body = ast?.program?.body;
|
|
169
|
+
if (!Array.isArray(body)) {
|
|
170
|
+
return [];
|
|
73
171
|
}
|
|
172
|
+
return body
|
|
173
|
+
.filter((node) => t.isImportDeclaration(node) && typeof node.start === 'number' && typeof node.end === 'number' && typeof node.source?.value === 'string')
|
|
174
|
+
.map((node) => ({
|
|
175
|
+
start: node.start,
|
|
176
|
+
end: node.end,
|
|
177
|
+
text: code.slice(node.start, node.end),
|
|
178
|
+
source: node.source.value,
|
|
179
|
+
hasOnlyNamedSpecifiers: Array.isArray(node.specifiers) && node.specifiers.length > 0 && node.specifiers.every((spec) => t.isImportSpecifier(spec)),
|
|
180
|
+
namedBindings: Array.isArray(node.specifiers)
|
|
181
|
+
? node.specifiers
|
|
182
|
+
.filter((spec) => t.isImportSpecifier(spec) && typeof spec.start === 'number' && typeof spec.end === 'number')
|
|
183
|
+
.map((spec) => ({
|
|
184
|
+
importedName: t.isIdentifier(spec.imported) ? spec.imported.name : String(spec.imported?.value || ''),
|
|
185
|
+
text: code.slice(spec.start, spec.end),
|
|
186
|
+
}))
|
|
187
|
+
: [],
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
74
192
|
}
|
|
75
|
-
return map;
|
|
76
|
-
}
|
|
77
|
-
// NativeScript module detectors
|
|
78
|
-
function isCoreGlobalsReference(spec) {
|
|
79
|
-
return /@nativescript(?:[\/_-])core(?:[\/_-])globals/.test(spec || '');
|
|
80
|
-
}
|
|
81
|
-
function isNativeScriptCoreModule(spec) {
|
|
82
|
-
return /^(?:@nativescript[\/_-]core|@nativescript\/core)(?:\b|\/)/i.test(spec || '');
|
|
83
193
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
194
|
+
function hoistTopLevelStaticImports(code) {
|
|
195
|
+
const imports = collectTopLevelImportRecords(code);
|
|
196
|
+
if (!imports.length) {
|
|
197
|
+
return code;
|
|
198
|
+
}
|
|
199
|
+
let stripped = code;
|
|
200
|
+
for (const imp of [...imports].sort((left, right) => right.start - left.start)) {
|
|
201
|
+
stripped = stripped.slice(0, imp.start) + stripped.slice(imp.end);
|
|
202
|
+
}
|
|
203
|
+
const hoisted = [];
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
for (const imp of imports) {
|
|
206
|
+
const text = imp.text.trim();
|
|
207
|
+
if (!text || seen.has(text)) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
seen.add(text);
|
|
211
|
+
hoisted.push(text);
|
|
212
|
+
}
|
|
213
|
+
if (!hoisted.length) {
|
|
214
|
+
return stripped;
|
|
215
|
+
}
|
|
216
|
+
return `${hoisted.join('\n')}\n${stripped.replace(/^\s*\n+/, '')}`;
|
|
86
217
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (/^(?:\.|\/|https?:\/\/)/i.test(s))
|
|
96
|
-
return false;
|
|
97
|
-
// App alias paths like '@/...' are not vendor packages
|
|
98
|
-
if (s.startsWith('@@/'))
|
|
99
|
-
return false; // extremely rare double '@' alias
|
|
100
|
-
if (s.startsWith('~/'))
|
|
101
|
-
return false; // NativeScript tilde alias (app root)
|
|
102
|
-
if (s.startsWith('@/'))
|
|
103
|
-
return false; // Common Vite alias for src
|
|
104
|
-
// .vue SFCs are not vendor packages
|
|
105
|
-
if (/\.vue(?:\?|$)/i.test(s))
|
|
106
|
-
return false;
|
|
107
|
-
// Exclude core and vue runtime which are handled by dedicated bridges
|
|
108
|
-
if (/^@nativescript\/core(\b|\/)/i.test(s))
|
|
109
|
-
return false;
|
|
110
|
-
if (/^(?:vue|nativescript-vue)(?:\b|\/)/i.test(s))
|
|
111
|
-
return false;
|
|
112
|
-
// Treat any other bare package id as device-resolved (require) during HMR
|
|
113
|
-
return true;
|
|
218
|
+
export function buildBootProgressSnippet(bootModuleLabel) {
|
|
219
|
+
const normalizedLabel = JSON.stringify(String(bootModuleLabel || '').replace(/\\/g, '/'));
|
|
220
|
+
return [
|
|
221
|
+
`const __nsBootGlobal=globalThis;`,
|
|
222
|
+
`try{if(!__nsBootGlobal.__NS_HMR_BOOT_COMPLETE__){const __nsBootApi=__nsBootGlobal.__NS_HMR_DEV_OVERLAY__;if(__nsBootApi&&typeof __nsBootApi.setBootStage==='function'){const __nsBootCount=(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__=Number(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__||0)+1);__nsBootGlobal.__NS_HMR_BOOT_LAST_MODULE__=${normalizedLabel};const __nsBootNow=Date.now();const __nsBootLast=Number(__nsBootGlobal.__NS_HMR_BOOT_LAST_PROGRESS_AT__||0);if(__nsBootCount<=8||__nsBootCount%6===0||__nsBootNow-__nsBootLast>90){__nsBootGlobal.__NS_HMR_BOOT_LAST_PROGRESS_AT__=__nsBootNow;const __nsBootProgress=Math.min(94,82+Math.min(10,Math.round((Math.log(__nsBootCount+1)/Math.LN2)*2)));__nsBootApi.setBootStage('importing-main',{detail:'Evaluated '+__nsBootCount+' modules\\n'+__nsBootGlobal.__NS_HMR_BOOT_LAST_MODULE__,attempt:Number(__nsBootGlobal.__NS_HMR_BOOT_MAIN_ATTEMPT__||1),attempts:Number(__nsBootGlobal.__NS_HMR_BOOT_MAIN_ATTEMPTS__||6),progress:__nsBootProgress});}}}}catch(__nsBootErr){}`,
|
|
223
|
+
`if(!__nsBootGlobal.__NS_HMR_BOOT_COMPLETE__){const __nsBootCount=Number(__nsBootGlobal.__NS_HMR_BOOT_MODULE_COUNT__||0);if(__nsBootCount<=24||__nsBootCount%8===0){await new Promise((resolve)=>setTimeout(resolve,0));}}`,
|
|
224
|
+
'',
|
|
225
|
+
].join('\n');
|
|
114
226
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
let entry = modules.get(canonical);
|
|
128
|
-
if (!entry) {
|
|
129
|
-
entry = {
|
|
130
|
-
default: new Set(),
|
|
131
|
-
namespace: new Set(),
|
|
132
|
-
named: [],
|
|
133
|
-
sideEffectOnly: false,
|
|
134
|
-
};
|
|
135
|
-
modules.set(canonical, entry);
|
|
227
|
+
function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
|
|
228
|
+
const imports = collectTopLevelImportRecords(code);
|
|
229
|
+
if (!imports.length) {
|
|
230
|
+
return code;
|
|
231
|
+
}
|
|
232
|
+
const edits = [];
|
|
233
|
+
for (const imp of imports) {
|
|
234
|
+
const source = imp.source;
|
|
235
|
+
const depMatch = source.match(/(?:^|\/)node_modules\/\.vite\/deps\/(.+)$/);
|
|
236
|
+
const depPath = depMatch?.[1] || (source.startsWith('.vite/deps/') ? source.slice('.vite/deps/'.length) : null);
|
|
237
|
+
if (!depPath) {
|
|
238
|
+
continue;
|
|
136
239
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.map((segment) => segment.trim())
|
|
144
|
-
.filter(Boolean)
|
|
145
|
-
.forEach((segment) => {
|
|
146
|
-
const [imported, local] = segment.split(/\s+as\s+/i).map((s) => s.trim());
|
|
147
|
-
const resolvedImported = imported;
|
|
148
|
-
const resolvedLocal = local || imported;
|
|
149
|
-
if (resolvedImported) {
|
|
150
|
-
binding.named.push({
|
|
151
|
-
imported: resolvedImported,
|
|
152
|
-
local: resolvedLocal,
|
|
153
|
-
});
|
|
240
|
+
let replacement = '';
|
|
241
|
+
if (preserveVendorImports) {
|
|
242
|
+
const canonical = resolveVendorFromCandidate(`.vite/deps/${depPath}`);
|
|
243
|
+
const bareSpecifier = canonical || viteDepsPathToBareSpecifier(depPath);
|
|
244
|
+
if (bareSpecifier) {
|
|
245
|
+
replacement = imp.text.replace(source, bareSpecifier);
|
|
154
246
|
}
|
|
155
|
-
});
|
|
156
|
-
};
|
|
157
|
-
// Handle "import ... from 'x'" forms
|
|
158
|
-
code = code.replace(importRegex, (full, pfx, clause, rawSpec) => {
|
|
159
|
-
// Capture original for potential preservation (strip the leading newline to avoid double spacing when hoisted)
|
|
160
|
-
const original = full.replace(/^\n/, '');
|
|
161
|
-
// Do not touch type-only imports other than hoisting
|
|
162
|
-
if (full.trimStart().startsWith('import type')) {
|
|
163
|
-
preservedImports.push(original);
|
|
164
|
-
return pfx || '';
|
|
165
|
-
}
|
|
166
|
-
const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
|
|
167
|
-
let canonical = resolveVendorFromCandidate(specifier);
|
|
168
|
-
// If not found in vendor manifest, treat well-known NativeScript plugin-style packages
|
|
169
|
-
// as require() based modules so the device can resolve them from the app bundle or vendor.
|
|
170
|
-
if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
|
|
171
|
-
canonical = specifier;
|
|
172
|
-
}
|
|
173
|
-
// CRITICAL: never vendor-inject @nativescript/core here — preserve for the later core-bridge pass.
|
|
174
|
-
if (canonical && /^@nativescript\/core(\b|\/)/i.test(canonical)) {
|
|
175
|
-
preservedImports.push(original);
|
|
176
|
-
return pfx || '';
|
|
177
|
-
}
|
|
178
|
-
if (!canonical) {
|
|
179
|
-
preservedImports.push(original);
|
|
180
|
-
return pfx || '';
|
|
181
|
-
}
|
|
182
|
-
const binding = getModuleBinding(canonical);
|
|
183
|
-
const trimmed = String(clause).trim();
|
|
184
|
-
if (!trimmed) {
|
|
185
|
-
binding.sideEffectOnly = true;
|
|
186
|
-
return pfx || ''; // erase the import line
|
|
187
|
-
}
|
|
188
|
-
// namespace: import * as ns from 'x'
|
|
189
|
-
if (trimmed.startsWith('*')) {
|
|
190
|
-
const m = trimmed.match(/\*\s+as\s+(\w+)/i);
|
|
191
|
-
if (m?.[1])
|
|
192
|
-
binding.namespace.add(m[1]);
|
|
193
|
-
return pfx || '';
|
|
194
|
-
}
|
|
195
|
-
// named: import { a, b as c } from 'x'
|
|
196
|
-
if (trimmed.startsWith('{')) {
|
|
197
|
-
parseNamedImports(trimmed, binding);
|
|
198
|
-
return pfx || '';
|
|
199
|
-
}
|
|
200
|
-
// default + named: import Default, { a as A } from 'x'
|
|
201
|
-
if (trimmed.includes(',') && trimmed.includes('{')) {
|
|
202
|
-
const [defaultPart, namedPart] = trimmed.split(/,(.+)/, 2);
|
|
203
|
-
const def = defaultPart.trim();
|
|
204
|
-
if (def)
|
|
205
|
-
binding.default.add(def);
|
|
206
|
-
if (namedPart)
|
|
207
|
-
parseNamedImports(namedPart.trim(), binding);
|
|
208
|
-
return pfx || '';
|
|
209
|
-
}
|
|
210
|
-
// default only
|
|
211
|
-
binding.default.add(trimmed);
|
|
212
|
-
return pfx || '';
|
|
213
|
-
});
|
|
214
|
-
// Handle side-effect only imports: import 'x'
|
|
215
|
-
code = code.replace(sideEffectRegex, (full, _pfx, rawSpec) => {
|
|
216
|
-
const original = full.replace(/^\n/, '');
|
|
217
|
-
const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
|
|
218
|
-
let canonical = resolveVendorFromCandidate(specifier);
|
|
219
|
-
if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
|
|
220
|
-
canonical = specifier;
|
|
221
|
-
}
|
|
222
|
-
if (canonical && /^@nativescript\/core(\b|\/)/i.test(canonical)) {
|
|
223
|
-
preservedImports.push(original);
|
|
224
|
-
return _pfx || '';
|
|
225
|
-
}
|
|
226
|
-
if (!canonical) {
|
|
227
|
-
preservedImports.push(original);
|
|
228
|
-
return _pfx || '';
|
|
229
|
-
}
|
|
230
|
-
const binding = getModuleBinding(canonical);
|
|
231
|
-
binding.sideEffectOnly = true;
|
|
232
|
-
return _pfx || '';
|
|
233
|
-
});
|
|
234
|
-
// If there are no vendor modules to bind, still hoist preserved imports if any were collected.
|
|
235
|
-
if (!modules.size) {
|
|
236
|
-
if (preservedImports.length) {
|
|
237
|
-
const preserved = preservedImports.join('') + '\n';
|
|
238
|
-
return preserved + code;
|
|
239
247
|
}
|
|
248
|
+
edits.push({
|
|
249
|
+
start: imp.start,
|
|
250
|
+
end: imp.end,
|
|
251
|
+
text: replacement,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (!edits.length) {
|
|
240
255
|
return code;
|
|
241
256
|
}
|
|
242
|
-
let
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
for (const [canonical, binding] of modules) {
|
|
259
|
-
const cacheKey = JSON.stringify(canonical);
|
|
260
|
-
const moduleVar = `__nsVendorModule_${index++}`;
|
|
261
|
-
injection += `const ${moduleVar} = __nsVendorRegistry.has(${cacheKey}) ? __nsVendorRegistry.get(${cacheKey}) : (() => { try { const mod = __nsVendorRequire(${cacheKey}); __nsVendorRegistry.set(${cacheKey}, mod); return mod; } catch (e) { try { console.error('[ns-hmr][vendor][require-failed]', ${cacheKey}, (e && (e.message||e)) ); } catch {} try { if (__NS_VENDOR_SOFT__) { const stub = __nsMissing(${cacheKey}); __nsVendorRegistry.set(${cacheKey}, stub); return stub; } } catch {} throw e; } })();\n`;
|
|
262
|
-
binding.namespace.forEach((alias) => {
|
|
263
|
-
// For namespace imports, expose both the raw module and a default fallback for interop consumers
|
|
264
|
-
injection += `const ${alias} = ${moduleVar};\n`;
|
|
265
|
-
injection += `(${alias} && typeof ${alias} === 'object' && !('default' in ${alias})) && (${alias}.default = ${alias});\n`;
|
|
266
|
-
});
|
|
267
|
-
if (binding.named.length) {
|
|
268
|
-
// Bind each named import robustly from either default or namespace using helper.
|
|
269
|
-
for (const { imported, local } of binding.named) {
|
|
270
|
-
const localName = local;
|
|
271
|
-
const importedName = imported;
|
|
272
|
-
injection += `const ${localName} = __nsPick(${moduleVar}, ${JSON.stringify(importedName)});\n`;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (binding.default.size) {
|
|
276
|
-
// Create one stable default candidate per module and reuse for all default locals
|
|
277
|
-
const defVar = `${moduleVar}__def`;
|
|
278
|
-
injection += `const ${defVar} = __nsDefault(${moduleVar});\n`;
|
|
279
|
-
binding.default.forEach((localName) => {
|
|
280
|
-
injection += `const ${localName} = (__nsHasInstall(${defVar})
|
|
281
|
-
? ${defVar}
|
|
282
|
-
: (__nsHasInstall(${moduleVar})
|
|
283
|
-
? ${moduleVar}
|
|
284
|
-
: (function(){ const _n = __nsNestedDefault(${moduleVar}); return _n !== undefined ? _n : ${defVar}; })()));\n`;
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
if (binding.sideEffectOnly && !binding.namespace.size && !binding.named.length && !binding.default.size) {
|
|
288
|
-
injection += `void ${moduleVar};\n`;
|
|
257
|
+
let next = code;
|
|
258
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
259
|
+
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
260
|
+
}
|
|
261
|
+
return next;
|
|
262
|
+
}
|
|
263
|
+
function buildNodeModuleProvenancePrelude(sourceId) {
|
|
264
|
+
if (!sourceId) {
|
|
265
|
+
return '';
|
|
266
|
+
}
|
|
267
|
+
const cleaned = sourceId.replace(PAT.QUERY_PATTERN, '');
|
|
268
|
+
let normalized = normalizeNodeModulesSpecifier(cleaned);
|
|
269
|
+
if (!normalized) {
|
|
270
|
+
const viteDepsMatch = cleaned.match(/(?:^|\/)node_modules\/\.vite\/deps\/([^?#]+)/);
|
|
271
|
+
if (viteDepsMatch?.[1]) {
|
|
272
|
+
normalized = `.vite/deps/${viteDepsMatch[1]}`;
|
|
289
273
|
}
|
|
290
274
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
275
|
+
if (!normalized) {
|
|
276
|
+
return '';
|
|
277
|
+
}
|
|
278
|
+
let packageSpecifier = normalized;
|
|
279
|
+
let via = 'node_modules';
|
|
280
|
+
if (normalized.startsWith('.vite/deps/')) {
|
|
281
|
+
via = 'vite-deps';
|
|
282
|
+
packageSpecifier = viteDepsPathToBareSpecifier(normalized.slice('.vite/deps/'.length)) || normalized;
|
|
283
|
+
}
|
|
284
|
+
const rootPackage = resolveNodeModulesPackageBoundary(packageSpecifier, getProjectRootPath()).packageName;
|
|
285
|
+
if (!rootPackage) {
|
|
286
|
+
return '';
|
|
287
|
+
}
|
|
288
|
+
return `try { const __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(${JSON.stringify(rootPackage)}, ${JSON.stringify({ kind: 'http-esm', specifier: packageSpecifier, url: sourceId, via })}); } } catch {}\n`;
|
|
295
289
|
}
|
|
296
290
|
// Guard any bare dynamic import(spec) occurring in assembled module code.
|
|
297
291
|
// We cannot override native dynamic import globally; for SFC assembler outputs we inline
|
|
@@ -318,113 +312,6 @@ function guardBareDynamicImports(code) {
|
|
|
318
312
|
return code;
|
|
319
313
|
}
|
|
320
314
|
}
|
|
321
|
-
function normalizeNativeScriptCoreSpecifier(spec) {
|
|
322
|
-
let normalized = spec.replace(/@nativescript[_-]core/gi, '@nativescript/core').replace(/@nativescript\/core\/index\.js$/i, '@nativescript/core/index.js');
|
|
323
|
-
if (normalized.startsWith('/node_modules/')) {
|
|
324
|
-
const idx = normalized.toLowerCase().indexOf('@nativescript/core');
|
|
325
|
-
if (idx !== -1) {
|
|
326
|
-
normalized = normalized.slice(idx);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
if (normalized.toLowerCase().startsWith('@nativescript/core')) {
|
|
330
|
-
normalized = normalized.replace(/\?[^"'`]*$/, '');
|
|
331
|
-
}
|
|
332
|
-
return normalized;
|
|
333
|
-
}
|
|
334
|
-
function normalizeNodeModulesSpecifier(spec) {
|
|
335
|
-
if (!spec) {
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
let normalized = spec.replace(/\\/g, '/');
|
|
339
|
-
const idx = normalized.lastIndexOf('/node_modules/');
|
|
340
|
-
if (idx === -1) {
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
let subPath = normalized.slice(idx + '/node_modules/'.length);
|
|
344
|
-
if (!subPath) {
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
347
|
-
subPath = subPath.replace(PAT.QUERY_PATTERN, '');
|
|
348
|
-
if (!subPath) {
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
// Skip Vite pre-bundled deps that we already map to vendor
|
|
352
|
-
if (subPath.startsWith('.vite/')) {
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
return subPath.startsWith('/') ? subPath.slice(1) : subPath;
|
|
356
|
-
}
|
|
357
|
-
function resolveVendorFromCandidate(specifier) {
|
|
358
|
-
if (!specifier) {
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
const manifest = getVendorManifest();
|
|
362
|
-
if (!manifest) {
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
const cleaned = specifier.replace(PAT.QUERY_PATTERN, '');
|
|
366
|
-
const direct = resolveVendorSpecifier(cleaned);
|
|
367
|
-
if (direct) {
|
|
368
|
-
return direct;
|
|
369
|
-
}
|
|
370
|
-
const flattenedId = extractVitePrebundleId(cleaned);
|
|
371
|
-
if (flattenedId) {
|
|
372
|
-
const flattenedMap = getFlattenedManifestMap(manifest);
|
|
373
|
-
const flatMatch = flattenedMap.get(flattenedId);
|
|
374
|
-
if (flatMatch) {
|
|
375
|
-
return flatMatch;
|
|
376
|
-
}
|
|
377
|
-
for (const [flatKey, canonical] of flattenedMap.entries()) {
|
|
378
|
-
if (flattenedId === flatKey || flattenedId.startsWith(`${flatKey}_`)) {
|
|
379
|
-
return canonical;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const guessedId = flattenedId.replace(/__/g, '.').replace(/_/g, '/');
|
|
383
|
-
if (guessedId && guessedId !== flattenedId) {
|
|
384
|
-
const guessedCanonical = resolveVendorSpecifier(guessedId);
|
|
385
|
-
if (guessedCanonical) {
|
|
386
|
-
return guessedCanonical;
|
|
387
|
-
}
|
|
388
|
-
const prefix = findVendorPrefix(guessedId, manifest);
|
|
389
|
-
if (prefix) {
|
|
390
|
-
return prefix;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
const normalizedCore = normalizeNativeScriptCoreSpecifier(cleaned);
|
|
395
|
-
if (normalizedCore !== cleaned) {
|
|
396
|
-
const nsCanonical = resolveVendorSpecifier(normalizedCore);
|
|
397
|
-
if (nsCanonical) {
|
|
398
|
-
return nsCanonical;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
const nodeModulesSpecifier = normalizeNodeModulesSpecifier(cleaned);
|
|
402
|
-
if (nodeModulesSpecifier) {
|
|
403
|
-
const canonical = resolveVendorSpecifier(nodeModulesSpecifier);
|
|
404
|
-
if (canonical) {
|
|
405
|
-
return canonical;
|
|
406
|
-
}
|
|
407
|
-
const prefix = findVendorPrefix(nodeModulesSpecifier, manifest);
|
|
408
|
-
if (prefix) {
|
|
409
|
-
return prefix;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
const prefix = findVendorPrefix(cleaned, manifest);
|
|
413
|
-
if (prefix) {
|
|
414
|
-
return prefix;
|
|
415
|
-
}
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
418
|
-
function findVendorPrefix(specifier, manifest) {
|
|
419
|
-
const { modules } = manifest;
|
|
420
|
-
const keys = Object.keys(modules || {});
|
|
421
|
-
for (const key of keys) {
|
|
422
|
-
if (specifier === key || specifier.startsWith(`${key}/`)) {
|
|
423
|
-
return key;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
428
315
|
function stripCoreGlobalsImports(code) {
|
|
429
316
|
const pattern = /^\s*(?:import\s+(?:[^'"\n]*from\s+)?|export\s+\*\s+from\s+)["'][^"']*(?:@nativescript(?:[/_-])core(?:[\/_-])globals|@nativescript_core_globals)[^"']*["'];?\s*$/gm;
|
|
430
317
|
return code.replace(pattern, '');
|
|
@@ -452,18 +339,14 @@ function ensureVariableDynamicImportHelper(code) {
|
|
|
452
339
|
`};\n`;
|
|
453
340
|
return `${helper}${code}`;
|
|
454
341
|
}
|
|
455
|
-
// Final safety net for plain dynamic import(expressions) that might slip through
|
|
456
|
-
// Vite's helper transformation. We rewrite occurrences of `import(` to `__ns_import(`
|
|
457
|
-
// and inject a small wrapper that maps the anomalous request '@' to a harmless stub.
|
|
458
342
|
function ensureGuardPlainDynamicImports(code, origin) {
|
|
459
343
|
try {
|
|
460
344
|
if (!code || !/\bimport\s*\(/.test(code))
|
|
461
345
|
return code;
|
|
462
|
-
const
|
|
463
|
-
// Replace only when `import(` is not part of an identifier or property (no preceding "." or word char)
|
|
346
|
+
const wrapper = `const __ns_import = (s) => { try { if (s === '@') { return import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href); } } catch {} return import(s); }\n`;
|
|
464
347
|
const replaced = code.replace(/(^|[^\.\w$])import\s*\(/g, (_m, p1) => `${p1}__ns_import(`);
|
|
465
348
|
if (replaced !== code) {
|
|
466
|
-
return
|
|
349
|
+
return wrapper + replaced;
|
|
467
350
|
}
|
|
468
351
|
return code;
|
|
469
352
|
}
|
|
@@ -471,13 +354,63 @@ function ensureGuardPlainDynamicImports(code, origin) {
|
|
|
471
354
|
return code;
|
|
472
355
|
}
|
|
473
356
|
}
|
|
474
|
-
//
|
|
475
|
-
//
|
|
357
|
+
// `ensureDynamicHmrImportHelper` lives in
|
|
358
|
+
// `./websocket-served-module-helpers.js`. See that file for the
|
|
359
|
+
// architectural rationale and the current helper implementation.
|
|
360
|
+
async function expandStarExports(code, server, projectRoot, verbose, sharedTransformer) {
|
|
361
|
+
const STAR_RE = /^[ \t]*(export\s+\*\s+from\s+["'])([^"']+)(["'];?)[ \t]*$/gm;
|
|
362
|
+
let match;
|
|
363
|
+
const replacements = [];
|
|
364
|
+
while ((match = STAR_RE.exec(code)) !== null) {
|
|
365
|
+
const url = match[2];
|
|
366
|
+
if (!url.includes('/node_modules/'))
|
|
367
|
+
continue;
|
|
368
|
+
replacements.push({ full: match[0], url, prefix: match[1], suffix: match[3] });
|
|
369
|
+
}
|
|
370
|
+
if (!replacements.length)
|
|
371
|
+
return code;
|
|
372
|
+
// Pull target URLs through the shared runner when it's available so each
|
|
373
|
+
// node_modules path shares the 60s TTL cache with the main /ns/m pipeline
|
|
374
|
+
// and respects the global concurrency gate. Fan them out in parallel —
|
|
375
|
+
// this block used to be a serial `for await` loop, which dominated cold
|
|
376
|
+
// boot on apps with dozens of star-re-exports.
|
|
377
|
+
const transformer = sharedTransformer ?? ((url) => server.transformRequest(url));
|
|
378
|
+
const resolved = await Promise.all(replacements.map(async (rep) => {
|
|
379
|
+
try {
|
|
380
|
+
let vitePath = rep.url.replace(/^https?:\/\/[^/]+/, '');
|
|
381
|
+
vitePath = vitePath.replace(/^\/ns\/m\//, '/');
|
|
382
|
+
vitePath = vitePath.replace(/^\/__ns_boot__\/[^/]+/, '');
|
|
383
|
+
vitePath = vitePath.replace(/\/__ns_hmr__\/[^/]+/, '');
|
|
384
|
+
const result = await transformer(vitePath);
|
|
385
|
+
if (!result?.code)
|
|
386
|
+
return null;
|
|
387
|
+
const names = extractExportedNames(result.code);
|
|
388
|
+
if (!names.length)
|
|
389
|
+
return null;
|
|
390
|
+
if (verbose) {
|
|
391
|
+
console.log(`[ns/m] expanded export* -> ${names.length} names from ${vitePath}`);
|
|
392
|
+
}
|
|
393
|
+
return { rep, names };
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}));
|
|
399
|
+
for (const entry of resolved) {
|
|
400
|
+
if (!entry)
|
|
401
|
+
continue;
|
|
402
|
+
const explicit = `export { ${entry.names.join(', ')} } from ${JSON.stringify(entry.rep.url)};`;
|
|
403
|
+
code = code.replace(entry.rep.full, explicit);
|
|
404
|
+
}
|
|
405
|
+
return code;
|
|
406
|
+
}
|
|
407
|
+
function extractExportedNames(code) {
|
|
408
|
+
return extractDirectExportedNames(code);
|
|
409
|
+
}
|
|
476
410
|
function repairImportEqualsAssignments(code) {
|
|
477
411
|
try {
|
|
478
412
|
if (!code || typeof code !== 'string')
|
|
479
413
|
return code;
|
|
480
|
-
// import { a, b as c } = expr; -> const { a, b: c } = expr;
|
|
481
414
|
code = code.replace(/(^|\n)\s*import\s*\{([^}]+)\}\s*=\s*([^;]+);?/g, (_m, p1, specList, rhs) => {
|
|
482
415
|
const cleaned = String(specList)
|
|
483
416
|
.split(',')
|
|
@@ -487,56 +420,28 @@ function repairImportEqualsAssignments(code) {
|
|
|
487
420
|
.join(', ');
|
|
488
421
|
return `${p1}const { ${cleaned} } = ${rhs};`;
|
|
489
422
|
});
|
|
490
|
-
|
|
491
|
-
code = code.replace(/(^|\n)\s*import\s
|
|
492
|
-
return `${p1}const ${ns} = (${rhs});`;
|
|
493
|
-
});
|
|
494
|
-
// import name = expr; -> const name = expr;
|
|
495
|
-
code = code.replace(/(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, id, rhs) => {
|
|
496
|
-
return `${p1}const ${id} = ${rhs};`;
|
|
497
|
-
});
|
|
423
|
+
code = code.replace(/(^|\n)\s*import\s*\*\s*as\s*([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, ns, rhs) => `${p1}const ${ns} = (${rhs});`);
|
|
424
|
+
code = code.replace(/(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*=\s*([^;]+);?/g, (_m, p1, id, rhs) => `${p1}const ${id} = ${rhs};`);
|
|
498
425
|
}
|
|
499
426
|
catch { }
|
|
500
427
|
return code;
|
|
501
428
|
}
|
|
502
|
-
// Ensure imports of the NativeScript-Vue runtime bridge '/ns/rt' are versioned to
|
|
503
|
-
// bust the device HTTP loader cache whenever the HMR graph version increments.
|
|
504
429
|
function ensureVersionedRtImports(code, origin, ver) {
|
|
505
430
|
if (!code || !origin || !Number.isFinite(ver))
|
|
506
431
|
return code;
|
|
507
|
-
// Static imports: import { ... } from ".../ns/rt" (plus optional version)
|
|
508
432
|
code = code.replace(/(from\s+["'])(?:https?:\/\/[^"']+)?\/(?:\ns|ns)\/rt(?:\/[\d]+)?(["'])/g, (_m, p1, p3) => `${p1}/ns/rt/${ver}${p3}`);
|
|
509
|
-
// Dynamic imports: import(".../ns/rt") (plus optional version)
|
|
510
433
|
code = code.replace(/(import\(\s*["'])(?:https?:\/\/[^"']+)?\/(?:\@ns|ns)\/rt(?:\/[\d]+)?(["']\s*\))/g, (_m, p1, p3) => `${p1}/ns/rt/${ver}${p3}`);
|
|
511
434
|
return code;
|
|
512
435
|
}
|
|
513
|
-
// Ensure imports of @nativescript/core resolve via the unified /ns/core bridge to keep a single realm
|
|
514
|
-
function ensureVersionedCoreImports(code, origin, ver) {
|
|
515
|
-
try {
|
|
516
|
-
// Static imports already handled in rewriteImports; just ensure absolute origin prefix and version
|
|
517
|
-
code = code.replace(/(["'])\/ns\/core(\?p=[^"']+)?\1/g, (_m, q, qp) => `${q}/ns/core/${ver}${qp || ''}${q}`);
|
|
518
|
-
// Dynamic imports already handled in rewriteImports; ensure origin and version
|
|
519
|
-
code = code.replace(/import\(\s*(["'])\/ns\/core(\?p=[^"']+)?\1\s*\)/g, (_m, q, qp) => `import(${q}/ns/core/${ver}${qp || ''}${q})`);
|
|
520
|
-
}
|
|
521
|
-
catch { }
|
|
522
|
-
return code;
|
|
523
|
-
}
|
|
524
|
-
// Hardened removal of Vite's virtual dynamic-import-helper. Some variants (side-effect only
|
|
525
|
-
// or minified forms) slipped past earlier regexes causing runtime attempts to resolve
|
|
526
|
-
// /@id/__x00__vite/dynamic-import-helper.js which does not exist in the device mirror.
|
|
527
|
-
// We aggressively strip any reference and inline a helper if necessary.
|
|
528
436
|
function stripViteDynamicImportVirtual(code) {
|
|
529
437
|
if (!/\/@id\/__x00__vite\/dynamic-import-helper/.test(code)) {
|
|
530
438
|
return code;
|
|
531
439
|
}
|
|
532
440
|
const original = code;
|
|
533
|
-
// Remove any import lines referencing the virtual helper (with or without bindings)
|
|
534
441
|
code = code.replace(/^[\t ]*import[^\n]*\/@id\/__x00__vite\/dynamic-import-helper[^\n]*$/gm, '');
|
|
535
|
-
// If any raw spec strings remain (e.g. concatenated), neutralize them
|
|
536
442
|
if (/\/@id\/__x00__vite\/dynamic-import-helper/.test(code)) {
|
|
537
443
|
code = code.replace(/\/@id\/__x00__vite\/dynamic-import-helper[^"'`)]*/g, '/__NS_UNUSED_DYNAMIC_IMPORT_HELPER__');
|
|
538
444
|
}
|
|
539
|
-
// Ensure helper present
|
|
540
445
|
if (!/__variableDynamicImportRuntimeHelper/.test(code)) {
|
|
541
446
|
const inline = `const __variableDynamicImportRuntimeHelper = (map, request, importMode) => {\n try { if (request === '@') { return import('/ns/m/__invalid_at__.mjs'); } } catch {}\n const loader = map && (map[request] || map[request?.replace(/\\\\/g, '/')]);\n if (!loader) { const e = new Error('Cannot dynamically import: ' + request); /*@ts-ignore*/ e.code = 'ERR_MODULE_NOT_FOUND'; return Promise.reject(e); }\n try { return loader(importMode); } catch (e) { return Promise.reject(e); }\n};\n`;
|
|
542
447
|
code = inline + code;
|
|
@@ -546,17 +451,7 @@ function stripViteDynamicImportVirtual(code) {
|
|
|
546
451
|
}
|
|
547
452
|
return code;
|
|
548
453
|
}
|
|
549
|
-
|
|
550
|
-
const REQUIRE_GUARD_SNIPPET = `// [guard] install require('http(s)://') detector\n(()=>{try{var g=globalThis;if(g.__NS_REQUIRE_GUARD_INSTALLED__){}else{var mk=function(o,l){return function(){try{var s=arguments[0];if(typeof s==='string'&&/^(?:https?:)\\/\\//.test(s)){var e=new Error('[ns-hmr][require-guard] require of URL: '+s+' via '+l);try{console.error(e.message+'\\n'+(e.stack||''));}catch(e2){}try{g.__NS_REQUIRE_GUARD_LAST__={spec:s,stack:e.stack,label:l,ts:Date.now()};}catch(e3){}}}catch(e1){}return o.apply(this, arguments);};};if(typeof g.require==='function'&&!g.require.__NS_REQ_GUARDED__){var o1=g.require;g.require=mk(o1,'require');g.require.__NS_REQ_GUARDED__=true;}if(typeof g.__nsRequire==='function'&&!g.__nsRequire.__NS_REQ_GUARDED__){var o2=g.__nsRequire;g.__nsRequire=mk(o2,'__nsRequire');g.__nsRequire.__NS_REQ_GUARDED__=true;}g.__NS_REQUIRE_GUARD_INSTALLED__=true;}}catch(e){}})();\n`;
|
|
551
|
-
// ============================================================================
|
|
552
|
-
// HELPER FUNCTIONS
|
|
553
|
-
// ============================================================================
|
|
554
|
-
// Origin invariant: we own both client and server. All URLs must use an explicit
|
|
555
|
-
// http(s)://host:port origin with no trailing slash. Build it deterministically
|
|
556
|
-
// where needed; do not post-sanitize.
|
|
557
|
-
/**
|
|
558
|
-
* Check if an import spec should be remapped to dep-*.mjs
|
|
559
|
-
*/
|
|
454
|
+
const REQUIRE_GUARD_SNIPPET = `// [guard] install require('http(s)://') detector\n(()=>{try{var g=globalThis;if(g.__NS_REQUIRE_GUARD_INSTALLED__){}else{var mk=function(o,l){return function(){try{var s=arguments[0];if(typeof s==='string'&&/^(?:https?:)\\/\\//.test(s)){var e=new Error('[ns-hmr][require-guard] require of URL: '+s+' via '+l);console.error(e.message+'\\n'+(e.stack||''));try{g.__NS_REQUIRE_GUARD_LAST__={spec:s,stack:e.stack,label:l,ts:Date.now()};}catch(e3){}}}catch(e1){}return o.apply(this, arguments);};};if(typeof g.require==='function'&&!g.require.__NS_REQ_GUARDED__){var o1=g.require;g.require=mk(o1,'require');g.require.__NS_REQ_GUARDED__=true;}if(typeof g.__nsRequire==='function'&&!g.__nsRequire.__NS_REQ_GUARDED__){var o2=g.__nsRequire;g.__nsRequire=mk(o2,'__nsRequire');g.__nsRequire.__NS_REQ_GUARDED__=true;}g.__NS_REQUIRE_GUARD_INSTALLED__=true;}}catch(e){}})();\n`;
|
|
560
455
|
function shouldRemapImport(spec) {
|
|
561
456
|
if (!spec || typeof spec !== 'string')
|
|
562
457
|
return false;
|
|
@@ -577,12 +472,9 @@ function shouldRemapImport(spec) {
|
|
|
577
472
|
}
|
|
578
473
|
return true;
|
|
579
474
|
}
|
|
580
|
-
// (legacy wrapSfcWithStableDefault removed; full SFCs now delegate to /ns/asm)
|
|
581
475
|
function removeNamedImports(code, names) {
|
|
582
476
|
const regex = /^(\s*import\s*\{)([^}]*)(\}\s*from\s*['"][^'"]+['"];?)/gm;
|
|
583
477
|
return code.replace(regex, (_m, p1, specList, p3) => {
|
|
584
|
-
// Only strip for known globalized framework sources (Vue/Nativescript-Vue).
|
|
585
|
-
// Keep imports from all other packages (Pinia, third-party libs, app modules) intact.
|
|
586
478
|
const srcMatch = /from\s*['"]\s*([^'"\s]+)\s*['"]/i.exec(_m);
|
|
587
479
|
const src = (srcMatch?.[1] || '').toLowerCase();
|
|
588
480
|
const isVueSource = /^(?:vue|nativescript-vue)(?:\b|\/)/i.test(src);
|
|
@@ -681,7 +573,7 @@ function normalizeImportPath(spec, importerDir) {
|
|
|
681
573
|
else if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
682
574
|
key = path.posix.normalize(path.posix.join(importerDir, spec));
|
|
683
575
|
if (!key.startsWith('/')) {
|
|
684
|
-
key =
|
|
576
|
+
key = `/${key}`;
|
|
685
577
|
}
|
|
686
578
|
}
|
|
687
579
|
else {
|
|
@@ -748,6 +640,70 @@ function findDependencyFileName(depFileMap, key) {
|
|
|
748
640
|
}
|
|
749
641
|
return undefined;
|
|
750
642
|
}
|
|
643
|
+
function isRuntimePluginRootEntrySpecifier(specifier, projectRoot) {
|
|
644
|
+
if (!specifier) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
const cleaned = specifier.replace(PAT.QUERY_PATTERN, '');
|
|
648
|
+
const normalized = normalizeNodeModulesSpecifier(cleaned) || cleaned.replace(/^\/+/, '');
|
|
649
|
+
if (!normalized) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
const { packageName, subpath } = resolveNodeModulesPackageBoundary(normalized, projectRoot);
|
|
653
|
+
if (!packageName || !isLikelyNativeScriptRuntimePluginSpecifier(packageName, projectRoot)) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
if (!subpath) {
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
if (subpath.includes('/')) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const pkgBaseName = packageName.split('/').pop() || '';
|
|
663
|
+
const withoutExt = /(?:\.(?:ios|android|visionos))?\.(?:ts|tsx|js|jsx|mjs|mts|cts)$/i.test(subpath) ? subpath.replace(/\.[^.]+$/, '') : subpath;
|
|
664
|
+
const withoutPlatform = withoutExt.replace(/\.(ios|android|visionos)$/i, '');
|
|
665
|
+
return withoutPlatform === 'index' || withoutPlatform === pkgBaseName;
|
|
666
|
+
}
|
|
667
|
+
function collectMixedRuntimePluginHttpRootPackages(code, projectRoot) {
|
|
668
|
+
const nonRootSubpathPackages = new Set();
|
|
669
|
+
const rootEntryPackages = new Set();
|
|
670
|
+
const visitSpecifier = (rawSpecifier) => {
|
|
671
|
+
if (!rawSpecifier) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const specifier = normalizeNativeScriptCoreSpecifier(rawSpecifier).replace(PAT.QUERY_PATTERN, '');
|
|
675
|
+
if (!specifier) {
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (/^https?:\/\//.test(specifier) || specifier.startsWith('/ns/')) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
if (/^(?:\.|\/)/.test(specifier) && !specifier.includes('/node_modules/')) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const normalized = normalizeNodeModulesSpecifier(specifier) || specifier.replace(/^\/+/, '');
|
|
685
|
+
if (!normalized) {
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const { packageName } = resolveNodeModulesPackageBoundary(normalized, projectRoot);
|
|
689
|
+
if (!packageName || !isLikelyNativeScriptRuntimePluginSpecifier(packageName, projectRoot)) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (isRuntimePluginRootEntrySpecifier(normalized, projectRoot)) {
|
|
693
|
+
rootEntryPackages.add(packageName);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
nonRootSubpathPackages.add(packageName);
|
|
697
|
+
};
|
|
698
|
+
for (const pattern of [PAT.IMPORT_PATTERN_1, PAT.IMPORT_PATTERN_2, PAT.IMPORT_PATTERN_3, PAT.IMPORT_PATTERN_SIDE_EFFECT]) {
|
|
699
|
+
pattern.lastIndex = 0;
|
|
700
|
+
let match;
|
|
701
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
702
|
+
visitSpecifier(match[2]);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return new Set(Array.from(nonRootSubpathPackages).filter((packageName) => rootEntryPackages.has(packageName)));
|
|
706
|
+
}
|
|
751
707
|
function collectImportDependencies(code, importerPath) {
|
|
752
708
|
const importerDir = path.posix.dirname(importerPath);
|
|
753
709
|
const deps = new Set();
|
|
@@ -809,68 +765,15 @@ function cleanCode(code) {
|
|
|
809
765
|
result = ACTIVE_STRATEGY.preClean(result);
|
|
810
766
|
result = ACTIVE_STRATEGY.rewriteFrameworkImports(result);
|
|
811
767
|
// Vendor manifest-driven import rewrites
|
|
768
|
+
// NOTE: Static and side-effect vendor imports are intentionally NOT rewritten here.
|
|
769
|
+
// They are left as import statements so that ensureNativeScriptModuleBindings()
|
|
770
|
+
// (called later in processCodeForDevice) can transform them using the robust
|
|
771
|
+
// __nsVendorRequire + __nsPick pattern that works on device.
|
|
772
|
+
// Only dynamic imports are handled here since ensureNativeScriptModuleBindings
|
|
773
|
+
// does not process dynamic import() calls.
|
|
812
774
|
try {
|
|
813
775
|
const manifest = getVendorManifest();
|
|
814
776
|
if (manifest) {
|
|
815
|
-
// Pattern: capture full import statement (static) with optional bindings
|
|
816
|
-
// import X from 'pkg'; | import {a,b as c} from "pkg"; | import * as ns from 'pkg';
|
|
817
|
-
const staticImportRE = /(import\s+([^;]*?)\s+from\s*["'])([^"']+)(["'];?)/g;
|
|
818
|
-
result = result.replace(staticImportRE, (full, pre, bindings, spec, post) => {
|
|
819
|
-
// Do not vendor-rewrite @nativescript/core — handled by the unified HTTP bridge later
|
|
820
|
-
if (isNativeScriptCoreModule(spec))
|
|
821
|
-
return full;
|
|
822
|
-
const resolved = resolveVendorSpecifier(spec);
|
|
823
|
-
if (!resolved || /^@nativescript\/core(\b|\/)/i.test(resolved))
|
|
824
|
-
return full; // not vendor or is core
|
|
825
|
-
// Determine binding style
|
|
826
|
-
const trimmed = (bindings || '').trim();
|
|
827
|
-
let injected = '';
|
|
828
|
-
if (!trimmed || trimmed === '') {
|
|
829
|
-
// Side-effect import: import 'pkg'; -> we drop it (vendor already evaluated)
|
|
830
|
-
return `/* vendor side-effect dropped: ${spec} */`;
|
|
831
|
-
}
|
|
832
|
-
// Default + named or default only
|
|
833
|
-
// Examples of trimmed:
|
|
834
|
-
// defaultExport
|
|
835
|
-
// { a, b as c }
|
|
836
|
-
// * as ns
|
|
837
|
-
// defaultExport, { a, b }
|
|
838
|
-
const globalAccessor = `globalThis.__nsVendor && globalThis.__nsVendor(${JSON.stringify(resolved)})`;
|
|
839
|
-
const ensureHelper = `globalThis.__nsVendor=require? (globalThis.__nsVendor|| (globalThis.__nsVendor=(id)=>{const m=(globalThis.__NS_VENDOR_MANIFEST__?globalThis.__NS_VENDOR_MANIFEST__.modules[id]:null);return (globalThis.__nsModules && globalThis.__nsModules.get? (globalThis.__nsModules.get(id)||globalThis.__nsModules.get(m?.id||id)):undefined);})):globalThis.__nsVendor`;
|
|
840
|
-
if (trimmed.startsWith('{')) {
|
|
841
|
-
// Named only
|
|
842
|
-
injected = `${ensureHelper}; const ${trimmed} = ${globalAccessor} || {};`;
|
|
843
|
-
}
|
|
844
|
-
else if (trimmed.startsWith('*')) {
|
|
845
|
-
// Namespace import: * as ns
|
|
846
|
-
const m = /\*\s+as\s+(\w+)/.exec(trimmed);
|
|
847
|
-
if (m) {
|
|
848
|
-
injected = `${ensureHelper}; const ${m[1]} = ${globalAccessor} || {};`;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
else if (trimmed.includes(',')) {
|
|
852
|
-
// default plus named
|
|
853
|
-
const parts = trimmed.split(',');
|
|
854
|
-
const def = parts[0].trim();
|
|
855
|
-
const named = parts.slice(1).join(',').trim();
|
|
856
|
-
injected = `${ensureHelper}; const __vmod = ${globalAccessor} || {}; const ${def} = __vmod.default || __vmod; const ${named} = __vmod;`;
|
|
857
|
-
}
|
|
858
|
-
else {
|
|
859
|
-
// default only
|
|
860
|
-
injected = `${ensureHelper}; const ${trimmed} = (${globalAccessor}||{}).default || ${globalAccessor};`;
|
|
861
|
-
}
|
|
862
|
-
return injected;
|
|
863
|
-
});
|
|
864
|
-
// Bare side-effect imports: import 'pkg';
|
|
865
|
-
const sideEffectRE = /(import\s*["'])([^"']+)(["'];?)/g;
|
|
866
|
-
result = result.replace(sideEffectRE, (full, pre, spec, post) => {
|
|
867
|
-
if (isNativeScriptCoreModule(spec))
|
|
868
|
-
return full;
|
|
869
|
-
const resolved = resolveVendorSpecifier(spec);
|
|
870
|
-
if (!resolved || /^@nativescript\/core(\b|\/)/i.test(resolved))
|
|
871
|
-
return full;
|
|
872
|
-
return `/* vendor side-effect skipped: ${spec} */`;
|
|
873
|
-
});
|
|
874
777
|
// Dynamic import rewrites: import('pkg') -> Promise.resolve(__nsVendor('id'))
|
|
875
778
|
const dynImportRE = /(import\(\s*["'])([^"']+)(["']\s*\))/g;
|
|
876
779
|
result = result.replace(dynImportRE, (full, pre, spec, post) => {
|
|
@@ -999,6 +902,20 @@ function toAppModuleBaseId(importPath, projectRoot) {
|
|
|
999
902
|
const base = projectRelative.replace(/\.mjs$/i, '');
|
|
1000
903
|
return `/${base}`;
|
|
1001
904
|
}
|
|
905
|
+
function toNodeModulesHttpModuleId(importPath) {
|
|
906
|
+
const nodeModulesSpecifier = normalizeNodeModulesSpecifier(importPath);
|
|
907
|
+
if (!nodeModulesSpecifier) {
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
return `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
911
|
+
}
|
|
912
|
+
// `rewriteNsMImportPathForHmr` and `getNumericServeVersionTag` live in
|
|
913
|
+
// `./websocket-ns-m-paths.js`. The path rewriter is part of the
|
|
914
|
+
// "Stable URL + Explicit Invalidation" architecture and must be a
|
|
915
|
+
// single source of truth so the canonicalization rules can't drift
|
|
916
|
+
// between modules. They are imported above and re-exported below for
|
|
917
|
+
// tests / external callers that historically reached them through this
|
|
918
|
+
// module.
|
|
1002
919
|
function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
|
|
1003
920
|
if (!spec || typeof spec !== 'string') {
|
|
1004
921
|
return null;
|
|
@@ -1025,13 +942,211 @@ function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
|
|
|
1025
942
|
}
|
|
1026
943
|
return absolute;
|
|
1027
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* After the Angular linker runs on code that Vite has already resolved (bare
|
|
947
|
+
* specifiers → full URLs), the linker injects NEW import statements with bare
|
|
948
|
+
* specifiers (e.g. `import {Component} from '@angular/core'`). These cause:
|
|
949
|
+
* 1. Duplicate-identifier SyntaxErrors (the name was already imported via URL)
|
|
950
|
+
* 2. Unresolvable bare specifiers at runtime on device
|
|
951
|
+
*
|
|
952
|
+
* This function:
|
|
953
|
+
* • builds a map packageName → resolvedURL from existing resolved imports
|
|
954
|
+
* • collects all binding names already imported per package
|
|
955
|
+
* • for each bare-specifier import, removes duplicate bindings
|
|
956
|
+
* • rewrites any genuinely-new bindings to use the resolved URL
|
|
957
|
+
*/
|
|
958
|
+
function deduplicateLinkerImports(code) {
|
|
959
|
+
if (!code)
|
|
960
|
+
return code;
|
|
961
|
+
try {
|
|
962
|
+
const imports = collectTopLevelImportRecords(code);
|
|
963
|
+
if (!imports.length) {
|
|
964
|
+
return code;
|
|
965
|
+
}
|
|
966
|
+
// ── Step 1: collect resolved imports already in the file ──────────
|
|
967
|
+
const pkgUrlMap = new Map();
|
|
968
|
+
const pkgBindings = new Map();
|
|
969
|
+
for (const imp of imports) {
|
|
970
|
+
const url = imp.source;
|
|
971
|
+
if (!/^https?:\/\//.test(url) && !url.startsWith('/')) {
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
const nmIdx = url.lastIndexOf('/node_modules/');
|
|
975
|
+
if (nmIdx === -1)
|
|
976
|
+
continue;
|
|
977
|
+
const afterNm = url.substring(nmIdx + '/node_modules/'.length);
|
|
978
|
+
const parts = afterNm.split('/');
|
|
979
|
+
const pkg = parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
980
|
+
if (!pkgUrlMap.has(pkg))
|
|
981
|
+
pkgUrlMap.set(pkg, url);
|
|
982
|
+
if (imp.namedBindings.length) {
|
|
983
|
+
if (!pkgBindings.has(pkg))
|
|
984
|
+
pkgBindings.set(pkg, new Set());
|
|
985
|
+
for (const binding of imp.namedBindings) {
|
|
986
|
+
if (binding.importedName)
|
|
987
|
+
pkgBindings.get(pkg).add(binding.importedName);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (pkgUrlMap.size === 0)
|
|
992
|
+
return code;
|
|
993
|
+
// ── Step 2: rewrite bare-specifier imports ───────────────────────
|
|
994
|
+
const edits = [];
|
|
995
|
+
for (const imp of imports) {
|
|
996
|
+
if (!imp.hasOnlyNamedSpecifiers) {
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
const specifier = imp.source;
|
|
1000
|
+
if (specifier.startsWith('/') || specifier.startsWith('.') || specifier.startsWith('http')) {
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
const parts = specifier.split('/');
|
|
1004
|
+
const pkg = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
1005
|
+
const url = pkgUrlMap.get(pkg);
|
|
1006
|
+
if (!url) {
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
const existing = pkgBindings.get(pkg) || new Set();
|
|
1010
|
+
const newBindings = imp.namedBindings.filter((binding) => !existing.has(binding.importedName));
|
|
1011
|
+
if (newBindings.length === 0) {
|
|
1012
|
+
edits.push({ start: imp.start, end: imp.end, text: '' });
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (newBindings.length === imp.namedBindings.length) {
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
for (const binding of newBindings) {
|
|
1019
|
+
existing.add(binding.importedName);
|
|
1020
|
+
}
|
|
1021
|
+
edits.push({
|
|
1022
|
+
start: imp.start,
|
|
1023
|
+
end: imp.end,
|
|
1024
|
+
text: `import { ${newBindings.map((binding) => binding.text).join(', ')} } from ${JSON.stringify(url)};`,
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
if (!edits.length) {
|
|
1028
|
+
return code;
|
|
1029
|
+
}
|
|
1030
|
+
let next = code;
|
|
1031
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
1032
|
+
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
1033
|
+
}
|
|
1034
|
+
return next;
|
|
1035
|
+
}
|
|
1036
|
+
catch {
|
|
1037
|
+
return code;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
export function wrapCommonJsModuleForDevice(code, absolutePath) {
|
|
1041
|
+
if (!code)
|
|
1042
|
+
return code;
|
|
1043
|
+
try {
|
|
1044
|
+
const hasExportDefault = /\bexport\s+default\b/.test(code) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(code);
|
|
1045
|
+
const hasNamedExports = /\bexport\s+(?:const|let|var|function|class|async)\b/.test(code) || /\bexport\s*\{/.test(code);
|
|
1046
|
+
const hasCjsExports = /\bmodule\s*\.\s*exports\b/.test(code) || /\bexports\s*\.\s*\w/.test(code);
|
|
1047
|
+
if (hasExportDefault || hasNamedExports || !hasCjsExports) {
|
|
1048
|
+
return code;
|
|
1049
|
+
}
|
|
1050
|
+
const namedExports = new Set();
|
|
1051
|
+
const exportsRe = /\bexports\s*\.\s*([A-Za-z_$][\w$]*)\s*=/g;
|
|
1052
|
+
let em;
|
|
1053
|
+
while ((em = exportsRe.exec(code)) !== null) {
|
|
1054
|
+
const name = em[1];
|
|
1055
|
+
if (name !== '__esModule' && name !== 'default') {
|
|
1056
|
+
namedExports.add(name);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const defPropRe = /Object\s*\.\s*defineProperty\s*\(\s*exports\s*,\s*['"]([^'"]+)['"]/g;
|
|
1060
|
+
while ((em = defPropRe.exec(code)) !== null) {
|
|
1061
|
+
const name = em[1];
|
|
1062
|
+
if (name !== '__esModule' && name !== 'default') {
|
|
1063
|
+
namedExports.add(name);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
// Static enumeration only sees `exports.foo = ...` and `Object.defineProperty(exports, 'foo', ...)`.
|
|
1067
|
+
// Real-world packages like lodash attach their entire surface to a function inside an IIFE and
|
|
1068
|
+
// then `module.exports = thatFunction`. Static analysis returns zero in that case. To handle
|
|
1069
|
+
// these modules we ALSO load the package in the dev-server's Node context (only when we have a
|
|
1070
|
+
// node_modules path) and merge the runtime keys. See `helpers/cjs-named-exports.ts` for the
|
|
1071
|
+
// reasoning and safety boundaries.
|
|
1072
|
+
if (absolutePath) {
|
|
1073
|
+
try {
|
|
1074
|
+
for (const n of getCjsNamedExports(absolutePath)) {
|
|
1075
|
+
namedExports.add(n);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
catch {
|
|
1079
|
+
/* fall through to whatever we caught statically */
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
let suffix = `\nvar __cjs_mod = module.exports;\nexport default __cjs_mod;\n`;
|
|
1083
|
+
if (namedExports.size) {
|
|
1084
|
+
const entries = Array.from(namedExports);
|
|
1085
|
+
const temps = entries.map((name, i) => `var __cjs_e${i} = __cjs_mod[${JSON.stringify(name)}];`);
|
|
1086
|
+
const reExports = entries.map((name, i) => `__cjs_e${i} as ${name}`);
|
|
1087
|
+
suffix += `${temps.join(' ')}\nexport { ${reExports.join(', ')} };\n`;
|
|
1088
|
+
}
|
|
1089
|
+
const prelude = `var module = { exports: {} }; var exports = module.exports;\n` +
|
|
1090
|
+
`var __ns_cjs_require_base = (typeof globalThis.__nsBaseRequire === 'function' ? globalThis.__nsBaseRequire : (typeof globalThis.__nsRequire === 'function' ? globalThis.__nsRequire : (typeof globalThis.require === 'function' ? globalThis.require : undefined)));\n` +
|
|
1091
|
+
`var __ns_cjs_require_kind = (typeof globalThis.__nsBaseRequire === 'function' ? 'base-require' : (typeof globalThis.__nsRequire === 'function' ? 'vendor-require' : 'global-require'));\n` +
|
|
1092
|
+
`var require = function(spec) {\n` +
|
|
1093
|
+
` if (!__ns_cjs_require_base) { throw new Error('require is not defined'); }\n` +
|
|
1094
|
+
// Resolve relative specifiers against the HTTP-served module's URL
|
|
1095
|
+
// before delegating to NS's runtime require. Without this step,
|
|
1096
|
+
// \`require('./base64-vlq')\` inside a CJS module served from
|
|
1097
|
+
// \`http://.../ns/m/node_modules/source-map-js/lib/source-map-generator.js\`
|
|
1098
|
+
// would pass a literal '"./base64-vlq"' to the native require, which
|
|
1099
|
+
// has no notion of the current HTTP-module's location and either
|
|
1100
|
+
// throws "Module not found" or fetches an arbitrary filesystem path
|
|
1101
|
+
// that happens to parse as code (producing misleading syntax errors
|
|
1102
|
+
// like "missing ) after argument list" from unrelated modules).
|
|
1103
|
+
` var __nsResolvedSpec = spec;\n` +
|
|
1104
|
+
` try {\n` +
|
|
1105
|
+
` if (typeof spec === 'string' && (spec.indexOf('./') === 0 || spec.indexOf('../') === 0)) {\n` +
|
|
1106
|
+
` var __nsParentUrl = (typeof import.meta !== 'undefined' && import.meta && typeof import.meta.url === 'string') ? import.meta.url : null;\n` +
|
|
1107
|
+
` if (__nsParentUrl) {\n` +
|
|
1108
|
+
` var __nsResolvedUrl = new URL(spec, __nsParentUrl);\n` +
|
|
1109
|
+
` // Common Node-style bare extensions: prefer .js if the resolved URL lacks an extension in its last path segment.\n` +
|
|
1110
|
+
` if (!/\\.[A-Za-z0-9]+$/.test(__nsResolvedUrl.pathname.split('/').pop() || '')) {\n` +
|
|
1111
|
+
` __nsResolvedUrl.pathname = __nsResolvedUrl.pathname.replace(/\\/+$/, '') + '.js';\n` +
|
|
1112
|
+
` }\n` +
|
|
1113
|
+
` __nsResolvedSpec = __nsResolvedUrl.href;\n` +
|
|
1114
|
+
` }\n` +
|
|
1115
|
+
` }\n` +
|
|
1116
|
+
` } catch (e) {}\n` +
|
|
1117
|
+
` try { var __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(String(__nsResolvedSpec), { kind: __ns_cjs_require_kind, specifier: String(spec), url: __nsResolvedSpec !== spec ? __nsResolvedSpec : undefined, via: 'cjs-wrapper', parent: (typeof import.meta !== 'undefined' && import.meta && import.meta.url) ? import.meta.url : undefined }); } } catch (e) {}\n` +
|
|
1118
|
+
` var mod = __ns_cjs_require_base(__nsResolvedSpec);\n` +
|
|
1119
|
+
` try {\n` +
|
|
1120
|
+
` if (mod && (typeof mod === 'object' || typeof mod === 'function') && mod.default !== undefined) {\n` +
|
|
1121
|
+
` var keys = [];\n` +
|
|
1122
|
+
` try { keys = Object.keys(mod); } catch (e) {}\n` +
|
|
1123
|
+
` var defaultOnly = keys.length === 1 && keys[0] === 'default';\n` +
|
|
1124
|
+
` var esModuleOnly = keys.length === 2 && keys.indexOf('default') !== -1 && keys.indexOf('__esModule') !== -1;\n` +
|
|
1125
|
+
` if (mod.__esModule || defaultOnly || esModuleOnly) { return mod.default; }\n` +
|
|
1126
|
+
` }\n` +
|
|
1127
|
+
` } catch (e) {}\n` +
|
|
1128
|
+
` return mod;\n` +
|
|
1129
|
+
`};\n`;
|
|
1130
|
+
return `${prelude}${code}${suffix}`;
|
|
1131
|
+
}
|
|
1132
|
+
catch {
|
|
1133
|
+
return code;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1028
1136
|
/**
|
|
1029
1137
|
* Process code for device: inject globals, remove framework imports
|
|
1030
1138
|
*/
|
|
1031
|
-
function processCodeForDevice(code, isVitePreBundled) {
|
|
1139
|
+
function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = false, isNodeModule = false, sourceId, options) {
|
|
1032
1140
|
let result = code;
|
|
1141
|
+
const resolvedSpecifierOverrides = options?.resolvedSpecifierOverrides || getProcessCodeResolvedSpecifierOverrides(sourceId, getProjectRootPath());
|
|
1142
|
+
const bindingOptions = {
|
|
1143
|
+
preserveNonPluginVendorImports: preserveVendorImports,
|
|
1144
|
+
resolvedSpecifierOverrides,
|
|
1145
|
+
};
|
|
1033
1146
|
// Ensure Angular partial declarations are linked before any sanitizers run so runtime never hits the JIT path.
|
|
1034
1147
|
result = linkAngularPartialsIfNeeded(result);
|
|
1148
|
+
// Post-linker: deduplicate/resolve imports the Angular linker injected with bare specifiers
|
|
1149
|
+
result = deduplicateLinkerImports(result);
|
|
1035
1150
|
// First: aggressively strip any lingering virtual dynamic-import-helper before anything else.
|
|
1036
1151
|
// Doing this up-front prevents downstream dependency collection from seeing the virtual id.
|
|
1037
1152
|
result = stripViteDynamicImportVirtual(result);
|
|
@@ -1042,13 +1157,17 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1042
1157
|
// Inject ALL NativeScript/build globals at the top (matching global-defines.ts)
|
|
1043
1158
|
// This ensures any code using __DEV__, __ANDROID__, __IOS__, etc. works correctly
|
|
1044
1159
|
const allGlobals = [
|
|
1160
|
+
// Minimal process shim — populated with CLI --env.* flags at module load time.
|
|
1161
|
+
// In production builds, Vite/Rollup replaces process.env.* statically.
|
|
1162
|
+
// In HMR dev mode the code runs as-is on device, so we need the shim.
|
|
1163
|
+
`if (typeof process === "undefined") { globalThis.process = { env: ${__processEnvJson} }; } else if (!process.env) { process.env = ${__processEnvJson}; }`,
|
|
1045
1164
|
'const __ANDROID__ = globalThis.__ANDROID__ !== undefined ? globalThis.__ANDROID__ : false;',
|
|
1046
1165
|
'const __IOS__ = globalThis.__IOS__ !== undefined ? globalThis.__IOS__ : false;',
|
|
1047
1166
|
'const __VISIONOS__ = globalThis.__VISIONOS__ !== undefined ? globalThis.__VISIONOS__ : false;',
|
|
1048
1167
|
'const __APPLE__ = globalThis.__APPLE__ !== undefined ? globalThis.__APPLE__ : (__IOS__ || __VISIONOS__);',
|
|
1049
1168
|
'const __DEV__ = globalThis.__DEV__ !== undefined ? globalThis.__DEV__ : false;',
|
|
1050
1169
|
'const __COMMONJS__ = globalThis.__COMMONJS__ !== undefined ? globalThis.__COMMONJS__ : false;',
|
|
1051
|
-
'const __NS_WEBPACK__ = globalThis.__NS_WEBPACK__ !== undefined ? globalThis.__NS_WEBPACK__ :
|
|
1170
|
+
'const __NS_WEBPACK__ = globalThis.__NS_WEBPACK__ !== undefined ? globalThis.__NS_WEBPACK__ : false;',
|
|
1052
1171
|
'const __NS_ENV_VERBOSE__ = globalThis.__NS_ENV_VERBOSE__ !== undefined ? !!globalThis.__NS_ENV_VERBOSE__ : false;',
|
|
1053
1172
|
"const __CSS_PARSER__ = globalThis.__CSS_PARSER__ !== undefined ? globalThis.__CSS_PARSER__ : 'css-tree';",
|
|
1054
1173
|
'const __UI_USE_XML_PARSER__ = globalThis.__UI_USE_XML_PARSER__ !== undefined ? globalThis.__UI_USE_XML_PARSER__ : true;',
|
|
@@ -1056,19 +1175,29 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1056
1175
|
'const __TEST__ = globalThis.__TEST__ !== undefined ? globalThis.__TEST__ : false;',
|
|
1057
1176
|
];
|
|
1058
1177
|
result = allGlobals.join('\n') + '\n' + result;
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
result =
|
|
1178
|
+
const nodeModuleProvenancePrelude = buildNodeModuleProvenancePrelude(sourceId);
|
|
1179
|
+
if (nodeModuleProvenancePrelude) {
|
|
1180
|
+
result = nodeModuleProvenancePrelude + result;
|
|
1062
1181
|
}
|
|
1063
|
-
|
|
1064
|
-
//
|
|
1065
|
-
|
|
1066
|
-
|
|
1182
|
+
// AST normalization: inject /ns/rt helper aliases for underscore-prefixed identifiers.
|
|
1183
|
+
// ONLY for app source files — library code in node_modules should be served as-is.
|
|
1184
|
+
// Running the normalizer on libraries like tslib injects harmful destructures
|
|
1185
|
+
// (e.g., `const { SuppressedError } = __ns_rt_ns_1`) that shadow globals.
|
|
1186
|
+
if (!isNodeModule) {
|
|
1187
|
+
try {
|
|
1188
|
+
result = astNormalizeModuleImportsAndHelpers(result);
|
|
1189
|
+
}
|
|
1190
|
+
catch { }
|
|
1191
|
+
// Verify there are no duplicate top-level const/let bindings after AST normalization
|
|
1192
|
+
try {
|
|
1193
|
+
result = astVerifyAndAnnotateDuplicates(result);
|
|
1194
|
+
}
|
|
1195
|
+
catch { }
|
|
1067
1196
|
}
|
|
1068
|
-
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
if (!/^\s*(?:\/\/|\/\*) \[ast-normalized\]/m.test(result)) {
|
|
1197
|
+
// If AST marker present OR this is a node_modules file, skip regex-based helper
|
|
1198
|
+
// alias injection. Library code should NOT get /ns/rt destructures injected —
|
|
1199
|
+
// underscore-prefixed identifiers in libraries are internal variables, not NS helpers.
|
|
1200
|
+
if (!isNodeModule && !/^\s*(?:\/\/|\/\*) \[ast-normalized\]/m.test(result)) {
|
|
1072
1201
|
try {
|
|
1073
1202
|
const underscored = new Set();
|
|
1074
1203
|
const re = /(^|[^.\w$])_([A-Za-z]\w*)\b/g;
|
|
@@ -1149,7 +1278,11 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1149
1278
|
result = result.replace(/(^|\n)([\t ]*import\s+[^;]*?\s+from)\s*\n\s*("\/?node_modules\/\.vite\/deps\/[^"\n]+"\s*;?\s*)/gm, (_m, p1, p2, p3) => `${p1}${p2} ${p3}`);
|
|
1150
1279
|
}
|
|
1151
1280
|
catch { }
|
|
1152
|
-
|
|
1281
|
+
// When preserveVendorImports is true (HMR /ns/m/ endpoint), skip the
|
|
1282
|
+
// __nsVendorRequire + __nsPick rewrite. Vendor imports stay as bare
|
|
1283
|
+
// specifiers so the device-side import map resolves them via V8's native
|
|
1284
|
+
// module system, which correctly handles export * re-exports.
|
|
1285
|
+
result = ensureNativeScriptModuleBindings(result, bindingOptions);
|
|
1153
1286
|
// Repair any accidental "import ... = expr" assignments that may have slipped in.
|
|
1154
1287
|
try {
|
|
1155
1288
|
result = repairImportEqualsAssignments(result);
|
|
@@ -1158,10 +1291,7 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1158
1291
|
// Strip Vite prebundle deps imports (both named and side-effect) and any malformed const string artifacts
|
|
1159
1292
|
// Example problematic line observed: const "/node_modules/.vite/deps/@nativescript_firebase-messaging.js?v=...";
|
|
1160
1293
|
if (/node_modules\/\.vite\/deps\//.test(result)) {
|
|
1161
|
-
|
|
1162
|
-
result = result.replace(/^[\t ]*import\s+[^;]*from\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '');
|
|
1163
|
-
// Side-effect only imports from prebundle deps
|
|
1164
|
-
result = result.replace(/^[\t ]*import\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '');
|
|
1294
|
+
result = rewriteVitePrebundleImportsForDevice(result, preserveVendorImports);
|
|
1165
1295
|
// Malformed const string lines accidentally produced by upstream transforms
|
|
1166
1296
|
result = result.replace(/^[\t ]*const\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '// [hmr-sanitize] stripped malformed const prebundle ref\n');
|
|
1167
1297
|
// Naked string-only lines pointing at prebundle deps
|
|
@@ -1248,7 +1378,7 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1248
1378
|
}
|
|
1249
1379
|
// Ensure vendor bindings also apply after potential wrapper injections above
|
|
1250
1380
|
// (idempotent: second pass will be a no-op if imports already consumed).
|
|
1251
|
-
result = ensureNativeScriptModuleBindings(result);
|
|
1381
|
+
result = ensureNativeScriptModuleBindings(result, bindingOptions);
|
|
1252
1382
|
try {
|
|
1253
1383
|
result = repairImportEqualsAssignments(result);
|
|
1254
1384
|
}
|
|
@@ -1289,17 +1419,17 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1289
1419
|
result = normalizeStrayCoreStringLiterals(result);
|
|
1290
1420
|
}
|
|
1291
1421
|
catch { }
|
|
1422
|
+
try {
|
|
1423
|
+
result = fixDanglingCoreFrom(result);
|
|
1424
|
+
}
|
|
1425
|
+
catch { }
|
|
1426
|
+
try {
|
|
1427
|
+
result = normalizeAnyCoreSpecToBridge(result);
|
|
1428
|
+
}
|
|
1429
|
+
catch { }
|
|
1292
1430
|
result = ensureVariableDynamicImportHelper(result);
|
|
1293
1431
|
// Normalize any lingering @nativescript/core imports to the /ns/core bridge (non-destructive best-effort)
|
|
1294
1432
|
try {
|
|
1295
|
-
result = result.replace(/from\s+["']@nativescript\/core([^"'\n]*)["']/g, (_m, sub) => {
|
|
1296
|
-
const qp = (sub || '').trim().replace(/^\//, '');
|
|
1297
|
-
return `from "/ns/core${qp ? `?p=${qp}` : ''}"`;
|
|
1298
|
-
});
|
|
1299
|
-
result = result.replace(/import\(\s*["']@nativescript\/core([^"'\n]*)["']\s*\)/g, (_m, sub) => {
|
|
1300
|
-
const qp = (sub || '').trim().replace(/^\//, '');
|
|
1301
|
-
return `import("/ns/core${qp ? `?p=${qp}` : ''}")`;
|
|
1302
|
-
});
|
|
1303
1433
|
// Rewrite named imports from the /ns/core bridge into default import + destructuring.
|
|
1304
1434
|
// This makes `import { Frame } from '@nativescript/core'` work even if the bridge provides only a default export.
|
|
1305
1435
|
{
|
|
@@ -1315,6 +1445,9 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1315
1445
|
.join(', ');
|
|
1316
1446
|
const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1317
1447
|
result = result.replace(reNamed, (_full, pfx, specList, src) => {
|
|
1448
|
+
// Deep subpath URLs serve actual ESM with real named exports — skip.
|
|
1449
|
+
if (isDeepCoreSubpath(src))
|
|
1450
|
+
return _full;
|
|
1318
1451
|
__core_ns_seq++;
|
|
1319
1452
|
const tmp = `__ns_core_ns${__core_ns_seq}`;
|
|
1320
1453
|
const decl = `const { ${toDestructureCore(specList)} } = ${tmp};`;
|
|
@@ -1322,6 +1455,8 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1322
1455
|
});
|
|
1323
1456
|
const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1324
1457
|
result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
|
|
1458
|
+
if (isDeepCoreSubpath(src))
|
|
1459
|
+
return _full;
|
|
1325
1460
|
const decl = `const { ${toDestructureCore(specList)} } = ${defName};`;
|
|
1326
1461
|
return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1327
1462
|
});
|
|
@@ -1335,8 +1470,19 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1335
1470
|
// Keep a single semicolon before the import to avoid generating ';;'
|
|
1336
1471
|
result = result.replace(/;\s*import\s+/g, ';\nimport ');
|
|
1337
1472
|
result = result.replace(/}\s*import\s+/g, '}\nimport ');
|
|
1338
|
-
// Fallback: ensure any static import that isn't at start of line gets a newline before it
|
|
1339
|
-
|
|
1473
|
+
// Fallback: ensure any static import that isn't at start of line gets a newline before it.
|
|
1474
|
+
//
|
|
1475
|
+
// Only match after **structural** statement-ending characters: `;`, `}`, `)`, `]`. We
|
|
1476
|
+
// deliberately do NOT include `'`, `"`, or `` ` `` here — those are string-literal
|
|
1477
|
+
// terminators (and openers!), and including them caused the regex to fire inside
|
|
1478
|
+
// example code embedded in error strings. Concrete failure observed:
|
|
1479
|
+
// `@supabase/realtime-js` throws an Error whose message contains the literal
|
|
1480
|
+
// `' import ws from "ws"\n' +`. With `'` in the delimiter class, the engine matched
|
|
1481
|
+
// the opening `'` of that string literal as a "statement terminator" and rewrote the
|
|
1482
|
+
// example to `'\nimport ws from "..."` — splitting the string across two lines and
|
|
1483
|
+
// producing a SyntaxError on device. The structural delimiters below do not appear
|
|
1484
|
+
// inside string-literal openers, so the rewrite is safe.
|
|
1485
|
+
result = result.replace(/([;}\)\]])\s*(import\s+[^;\n]*\s+from\s*["'][^"']+["'])/g, '$1\n$2');
|
|
1340
1486
|
}
|
|
1341
1487
|
catch { }
|
|
1342
1488
|
// Collapse duplicate destructuring from the same temp namespace var (e.g., multiple const { x } = __ns_rt_ns1)
|
|
@@ -1370,22 +1516,13 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1370
1516
|
// always come before any statements that might reference their bindings. This ordering avoids
|
|
1371
1517
|
// device runtimes that are stricter about imports-first semantics during module instantiation.
|
|
1372
1518
|
try {
|
|
1373
|
-
|
|
1374
|
-
const lines = [];
|
|
1375
|
-
result = result.replace(importLineRe, (imp) => {
|
|
1376
|
-
lines.push(imp.trim());
|
|
1377
|
-
return '';
|
|
1378
|
-
});
|
|
1379
|
-
if (lines.length) {
|
|
1380
|
-
const hoisted = Array.from(new Set(lines)).join('\n') + '\n';
|
|
1381
|
-
result = hoisted + result;
|
|
1382
|
-
}
|
|
1519
|
+
result = hoistTopLevelStaticImports(result);
|
|
1383
1520
|
}
|
|
1384
1521
|
catch { }
|
|
1385
1522
|
// Final safety: normalize any lingering named imports from /ns/rt into default+destructure
|
|
1386
|
-
// Skip
|
|
1523
|
+
// Skip for node_modules (no /ns/rt helpers needed) and when AST marker present
|
|
1387
1524
|
try {
|
|
1388
|
-
if (!/^\s*\/\* \[ast-normalized\] \*\//m.test(result)) {
|
|
1525
|
+
if (!isNodeModule && !/^\s*\/\* \[ast-normalized\] \*\//m.test(result)) {
|
|
1389
1526
|
result = ensureDestructureRtImports(result);
|
|
1390
1527
|
}
|
|
1391
1528
|
}
|
|
@@ -1531,6 +1668,16 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
|
|
|
1531
1668
|
}
|
|
1532
1669
|
}
|
|
1533
1670
|
if (localCore.test(ln)) {
|
|
1671
|
+
// Comments can never cause split-realm risk at runtime — skip them.
|
|
1672
|
+
// Library authors commonly reference @nativescript/core in comments
|
|
1673
|
+
// (e.g. TSDoc /// <reference> directives, module resolution notes).
|
|
1674
|
+
const trimmed = ln.trimStart();
|
|
1675
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
if (shouldAllowLocalCoreSanitizerPaths(contextLabel)) {
|
|
1679
|
+
continue;
|
|
1680
|
+
}
|
|
1534
1681
|
offenders.push(`${i + 1}: ${ln.substring(0, 200)} [local-core-path]`);
|
|
1535
1682
|
}
|
|
1536
1683
|
if (offenders.length >= 10)
|
|
@@ -1555,6 +1702,7 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
|
|
|
1555
1702
|
function ensureDestructureCoreImports(code) {
|
|
1556
1703
|
try {
|
|
1557
1704
|
let result = code;
|
|
1705
|
+
let coreImportCounter = 0;
|
|
1558
1706
|
const toDestructure = (specList) => specList
|
|
1559
1707
|
.split(',')
|
|
1560
1708
|
.map((s) => s.trim())
|
|
@@ -1567,13 +1715,19 @@ function ensureDestructureCoreImports(code) {
|
|
|
1567
1715
|
// import { A, B } from '/ns/core[/ver][?p=...]'
|
|
1568
1716
|
const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1569
1717
|
result = result.replace(reNamed, (_full, pfx, specList, src) => {
|
|
1570
|
-
|
|
1718
|
+
// Deep subpath URLs serve actual ESM with real named exports — skip.
|
|
1719
|
+
if (isDeepCoreSubpath(src))
|
|
1720
|
+
return _full;
|
|
1721
|
+
const tmp = `__ns_core_ns_re${coreImportCounter > 0 ? `_${coreImportCounter}` : ''}`;
|
|
1722
|
+
coreImportCounter++;
|
|
1571
1723
|
const decl = `const { ${toDestructure(specList)} } = ${tmp};`;
|
|
1572
1724
|
return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1573
1725
|
});
|
|
1574
1726
|
// import Default, { A, B } from '/ns/core[...]'
|
|
1575
1727
|
const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1576
1728
|
result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
|
|
1729
|
+
if (isDeepCoreSubpath(src))
|
|
1730
|
+
return _full;
|
|
1577
1731
|
const decl = `const { ${toDestructure(specList)} } = ${defName};`;
|
|
1578
1732
|
return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1579
1733
|
});
|
|
@@ -1685,14 +1839,21 @@ function dedupeRtNamedImportsAgainstDestructures(code) {
|
|
|
1685
1839
|
/**
|
|
1686
1840
|
* THE SINGLE REWRITE FUNCTION - used everywhere for consistency
|
|
1687
1841
|
*/
|
|
1688
|
-
function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin) {
|
|
1842
|
+
export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
|
|
1689
1843
|
let result = code;
|
|
1690
1844
|
const httpOriginSafe = httpOrigin;
|
|
1845
|
+
const mixedRuntimePluginHttpRootPackages = collectMixedRuntimePluginHttpRootPackages(result, projectRoot);
|
|
1846
|
+
const isDynamicImportPrefix = (prefix) => /import\(\s*["']?$/.test(prefix.trimStart());
|
|
1691
1847
|
const importerDir = path.posix.dirname(importerPath);
|
|
1848
|
+
// Resolved once per `rewriteImports` call so the per-import `/@fs/` rewriter
|
|
1849
|
+
// can convert workspace-lib paths back into our `/ns/m/` pipeline. Memoized
|
|
1850
|
+
// upstream — calling here is cheap and we reuse the value below.
|
|
1851
|
+
const monorepoWorkspaceRootForRewrite = getMonorepoWorkspaceRoot(projectRoot);
|
|
1692
1852
|
// Determine importer output relative path (project-relative .mjs) to compute relative imports consistently
|
|
1693
1853
|
const importerOutRel = outputDirOverrideRel || getProjectRelativeImportPath(importerPath, projectRoot) || stripToProjectRelative(importerPath, projectRoot).replace(/\.(ts|js|tsx|jsx|mjs|mts|cts)$/i, '.mjs');
|
|
1694
1854
|
const importerOutDir = importerOutRel ? path.posix.dirname(importerOutRel) : '';
|
|
1695
1855
|
const ensureRel = (p) => (p.startsWith('.') ? p : `./${p}`);
|
|
1856
|
+
const isNsSfcSpecifier = (spec) => /^(?:https?:\/\/[^/]+)?\/ns\/sfc(?:\/\d+)?(?:\/|$)/.test(spec.replace(PAT.QUERY_PATTERN, ''));
|
|
1696
1857
|
// Normalize all @nativescript/core imports to the unified HTTP ESM core bridge to guarantee a single realm on device
|
|
1697
1858
|
try {
|
|
1698
1859
|
let coreAliasIdx = 0;
|
|
@@ -1754,7 +1915,23 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1754
1915
|
const cleanPath = jsonPath.split('?')[0];
|
|
1755
1916
|
// Resolve the JSON file path relative to the importer
|
|
1756
1917
|
let fullPath;
|
|
1757
|
-
if (cleanPath.startsWith('/')) {
|
|
1918
|
+
if (cleanPath.startsWith('/@fs/')) {
|
|
1919
|
+
// Vite filesystem URL: `/@fs/<abs-path>`. Strip the `/@fs` prefix
|
|
1920
|
+
// (4 chars, leaving the leading `/`) to recover the absolute
|
|
1921
|
+
// path. This matches `rewriteFsAbsoluteToNsM`'s convention and
|
|
1922
|
+
// covers both bare specifiers Vite pre-resolved out of the
|
|
1923
|
+
// project root (e.g. `emojibase-data/en/compact.json` →
|
|
1924
|
+
// `/@fs/.../node_modules/.../compact.json`) and tsconfig
|
|
1925
|
+
// path-alias targets that resolve outside the project root
|
|
1926
|
+
// (e.g. `~shared/...metadata.json` → `/@fs/.../tools/...json`).
|
|
1927
|
+
// Without this branch the next `else if` would `path.join` the
|
|
1928
|
+
// `/@fs/...` URL onto `projectRoot`, collapsing the leading `/`
|
|
1929
|
+
// and producing a malformed nested path that always misses on
|
|
1930
|
+
// `existsSync` and triggers a `ReferenceError` at runtime when
|
|
1931
|
+
// the JSON-import-failed comment leaves the binding undefined.
|
|
1932
|
+
fullPath = cleanPath.slice('/@fs'.length);
|
|
1933
|
+
}
|
|
1934
|
+
else if (cleanPath.startsWith('/')) {
|
|
1758
1935
|
// Absolute from project root
|
|
1759
1936
|
fullPath = path.join(projectRoot, cleanPath);
|
|
1760
1937
|
}
|
|
@@ -1776,7 +1953,7 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1776
1953
|
return `const ${varName} = ${jsonContent};`;
|
|
1777
1954
|
}
|
|
1778
1955
|
else {
|
|
1779
|
-
console.warn(`[rewrite] JSON file not found: ${fullPath}`);
|
|
1956
|
+
console.warn(`[rewrite] JSON file not found: ${fullPath} (specifier=${jsonPath})`);
|
|
1780
1957
|
}
|
|
1781
1958
|
}
|
|
1782
1959
|
catch (error) {
|
|
@@ -1827,14 +2004,48 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1827
2004
|
if (spec === '@') {
|
|
1828
2005
|
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
1829
2006
|
if (verbose) {
|
|
1830
|
-
|
|
1831
|
-
console.warn(`[rewrite] mapped bare '@' spec to stub: ${stub}`);
|
|
1832
|
-
}
|
|
1833
|
-
catch { }
|
|
2007
|
+
console.warn(`[rewrite] mapped bare '@' spec to stub: ${stub}`);
|
|
1834
2008
|
}
|
|
1835
2009
|
return `${prefix}${stub}${suffix}`;
|
|
1836
2010
|
}
|
|
1837
2011
|
spec = normalizeNativeScriptCoreSpecifier(spec);
|
|
2012
|
+
// Pull `/@fs/<abs-path>` URLs back into the `/ns/m/` pipeline so they
|
|
2013
|
+
// hit our CJS/UMD-wrapping handler. Vite emits `/@fs/...` for any
|
|
2014
|
+
// resolved id outside the configured `root` — including hoisted
|
|
2015
|
+
// `node_modules/<pkg>` entries and workspace libs in monorepos. Left
|
|
2016
|
+
// untouched, the device fetches them through Vite's standard
|
|
2017
|
+
// middleware which never invokes `wrapCommonJsModuleForDevice`, so a
|
|
2018
|
+
// UMD module like papaparse crashes on `(this).Papa = factory()`
|
|
2019
|
+
// because top-level `this` is `undefined` in ESM context.
|
|
2020
|
+
if (spec.startsWith('/@fs/')) {
|
|
2021
|
+
const rewritten = rewriteFsAbsoluteToNsM(spec, projectRoot, monorepoWorkspaceRootForRewrite);
|
|
2022
|
+
if (rewritten) {
|
|
2023
|
+
if (httpOriginSafe) {
|
|
2024
|
+
return `${prefix}${httpOriginSafe}${rewritten}${suffix}`;
|
|
2025
|
+
}
|
|
2026
|
+
return `${prefix}${rewritten}${suffix}`;
|
|
2027
|
+
}
|
|
2028
|
+
// Path resolves outside both roots — leave Vite's URL alone as a
|
|
2029
|
+
// last resort. The original behaviour was to fall through here
|
|
2030
|
+
// and let downstream branches (e.g. `normalizeNodeModulesSpecifier`)
|
|
2031
|
+
// handle paths whose abs form happens to contain `/node_modules/`,
|
|
2032
|
+
// so preserve that for the unrewritable case below.
|
|
2033
|
+
}
|
|
2034
|
+
// Route Vite virtual modules (/@solid-refresh, etc.) through /ns/m/ so their
|
|
2035
|
+
// internal imports (e.g. solid-js) get vendor-rewritten by our pipeline.
|
|
2036
|
+
// Skip known Vite internals (/@vite/, /@id/) which are handled elsewhere.
|
|
2037
|
+
// `/@fs/` is intentionally excluded above; if we ever reach here with a
|
|
2038
|
+
// `/@fs/` spec it means the rewrite-to-`/ns/m/` pass couldn't anchor it
|
|
2039
|
+
// under projectRoot or workspaceRoot, so we fall through and rely on the
|
|
2040
|
+
// `normalizeNodeModulesSpecifier` branch below for paths that still
|
|
2041
|
+
// contain a `/node_modules/<pkg>/` segment.
|
|
2042
|
+
if (spec.startsWith('/@') && !/^\/@(?:vite|id|fs)\//.test(spec)) {
|
|
2043
|
+
const out = `/ns/m${spec}`;
|
|
2044
|
+
if (httpOriginSafe) {
|
|
2045
|
+
return `${prefix}${httpOriginSafe}${out}${suffix}`;
|
|
2046
|
+
}
|
|
2047
|
+
return `${prefix}${out}${suffix}`;
|
|
2048
|
+
}
|
|
1838
2049
|
// Route internal NS endpoints to absolute HTTP origin for device
|
|
1839
2050
|
if (spec.startsWith('/ns/')) {
|
|
1840
2051
|
if (httpOriginSafe) {
|
|
@@ -1846,20 +2057,50 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1846
2057
|
return `${prefix}${spec}${suffix}`;
|
|
1847
2058
|
}
|
|
1848
2059
|
const nodeModulesSpecifier = normalizeNodeModulesSpecifier(spec);
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
if (
|
|
1853
|
-
|
|
2060
|
+
const normalizedRuntimePluginSpec = nodeModulesSpecifier || spec.replace(PAT.QUERY_PATTERN, '').replace(/^\/+/, '');
|
|
2061
|
+
if (normalizedRuntimePluginSpec && mixedRuntimePluginHttpRootPackages.size > 0) {
|
|
2062
|
+
const { packageName } = resolveNodeModulesPackageBoundary(normalizedRuntimePluginSpec, projectRoot);
|
|
2063
|
+
if (packageName && mixedRuntimePluginHttpRootPackages.has(packageName)) {
|
|
2064
|
+
const httpNodeModulesSpecifier = nodeModulesSpecifier || normalizedRuntimePluginSpec;
|
|
2065
|
+
const httpSpec = `/ns/m/node_modules/${httpNodeModulesSpecifier}`;
|
|
2066
|
+
if (httpOriginSafe) {
|
|
2067
|
+
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
2068
|
+
}
|
|
2069
|
+
return `${prefix}${httpSpec}${suffix}`;
|
|
1854
2070
|
}
|
|
1855
|
-
return `${prefix}${spec.replace(PAT.QUERY_PATTERN, '')}${suffix}`;
|
|
1856
2071
|
}
|
|
1857
|
-
if (
|
|
1858
|
-
const
|
|
1859
|
-
|
|
2072
|
+
if (shouldPreserveBareRuntimePluginSubpathImport(spec, projectRoot)) {
|
|
2073
|
+
const httpSpec = `/ns/m/node_modules/${spec.replace(PAT.QUERY_PATTERN, '').replace(/^\/+/, '')}`;
|
|
2074
|
+
if (httpOriginSafe) {
|
|
2075
|
+
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
2076
|
+
}
|
|
2077
|
+
return `${prefix}${httpSpec}${suffix}`;
|
|
1860
2078
|
}
|
|
2079
|
+
const candidateNativeScriptSpec = nodeModulesSpecifier ?? spec;
|
|
2080
|
+
// ── Node modules routing ──────────────────────────────────────
|
|
2081
|
+
// Uses the package's own package.json exports field to determine
|
|
2082
|
+
// whether an import is the main entry (→ vendor bridge) or a
|
|
2083
|
+
// subpath entry (→ HTTP). This replaces the old heuristic-based
|
|
2084
|
+
// approach that tried to guess from file paths.
|
|
1861
2085
|
if (nodeModulesSpecifier) {
|
|
1862
|
-
|
|
2086
|
+
const vendorRouting = resolveVendorRouting(nodeModulesSpecifier, projectRoot);
|
|
2087
|
+
if (vendorRouting) {
|
|
2088
|
+
if (vendorRouting.route === 'vendor') {
|
|
2089
|
+
return `${prefix}${vendorRouting.bareSpec}${suffix}`;
|
|
2090
|
+
}
|
|
2091
|
+
// Vendor package but subpath/platform-specific → HTTP
|
|
2092
|
+
const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
2093
|
+
if (httpOriginSafe) {
|
|
2094
|
+
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
2095
|
+
}
|
|
2096
|
+
return `${prefix}${httpSpec}${suffix}`;
|
|
2097
|
+
}
|
|
2098
|
+
// Not a vendor package → serve via HTTP from Vite dev server
|
|
2099
|
+
const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
2100
|
+
if (httpOriginSafe) {
|
|
2101
|
+
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
2102
|
+
}
|
|
2103
|
+
return `${prefix}${httpSpec}${suffix}`;
|
|
1863
2104
|
}
|
|
1864
2105
|
// Handle .vue imports
|
|
1865
2106
|
if (PAT.VUE_FILE_PATTERN.test(spec)) {
|
|
@@ -1883,7 +2124,7 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1883
2124
|
return `${prefix}${out}${suffix}`;
|
|
1884
2125
|
}
|
|
1885
2126
|
// Case B: plain .vue module → rewrite to SFC endpoint or local artifact
|
|
1886
|
-
const vueKey = resolveVueKey(spec.replace(PAT.QUERY_PATTERN, ''));
|
|
2127
|
+
const vueKey = resolveVueKey(spec.replace(PAT.QUERY_PATTERN, '')) || '';
|
|
1887
2128
|
if (vueKey) {
|
|
1888
2129
|
if (true) {
|
|
1889
2130
|
const absVue = vueKey.startsWith('/') ? vueKey : '/' + vueKey;
|
|
@@ -1913,9 +2154,25 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1913
2154
|
// Rewrite relative application imports to HTTP for served modules
|
|
1914
2155
|
if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
1915
2156
|
const absMaybe = normalizeImportPath(spec, importerDir);
|
|
2157
|
+
const nodeModulesHttpSpec = absMaybe ? toNodeModulesHttpModuleId(absMaybe) : null;
|
|
2158
|
+
if (nodeModulesHttpSpec) {
|
|
2159
|
+
if (isDynamicImportPrefix(prefix)) {
|
|
2160
|
+
if (verbose)
|
|
2161
|
+
console.log(`[rewrite][http] dynamic relative node_modules import → ${nodeModulesHttpSpec} (from ${spec})`);
|
|
2162
|
+
return `__nsDynamicHmrImport(${JSON.stringify(nodeModulesHttpSpec)})`;
|
|
2163
|
+
}
|
|
2164
|
+
if (verbose)
|
|
2165
|
+
console.log(`[rewrite][http] relative node_modules import → ${nodeModulesHttpSpec} (from ${spec})`);
|
|
2166
|
+
return `${prefix}${nodeModulesHttpSpec}${suffix}`;
|
|
2167
|
+
}
|
|
1916
2168
|
const baseId = absMaybe ? toAppModuleBaseId(absMaybe, projectRoot) : null; // e.g. /src/foo.mjs
|
|
1917
2169
|
if (baseId) {
|
|
1918
2170
|
const httpSpec = `/ns/m${baseId}`;
|
|
2171
|
+
if (isDynamicImportPrefix(prefix)) {
|
|
2172
|
+
if (verbose)
|
|
2173
|
+
console.log(`[rewrite][http] dynamic relative app import → ${httpSpec} (from ${spec})`);
|
|
2174
|
+
return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
|
|
2175
|
+
}
|
|
1919
2176
|
if (verbose)
|
|
1920
2177
|
console.log(`[rewrite][http] relative app import → ${httpSpec} (from ${spec})`);
|
|
1921
2178
|
return `${prefix}${httpSpec}${suffix}`;
|
|
@@ -1928,6 +2185,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1928
2185
|
const baseId = toAppModuleBaseId(spec, projectRoot);
|
|
1929
2186
|
if (baseId) {
|
|
1930
2187
|
const httpSpec = `/ns/m${baseId}`;
|
|
2188
|
+
if (isDynamicImportPrefix(prefix)) {
|
|
2189
|
+
if (verbose)
|
|
2190
|
+
console.log(`[rewrite][http] dynamic app import → ${httpSpec} (from ${spec})`);
|
|
2191
|
+
return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
|
|
2192
|
+
}
|
|
1931
2193
|
if (verbose)
|
|
1932
2194
|
console.log(`[rewrite][http] absolute app import → ${httpSpec} (from ${spec})`);
|
|
1933
2195
|
return `${prefix}${httpSpec}${suffix}`;
|
|
@@ -1951,6 +2213,27 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1951
2213
|
return `${prefix}./${depFile}${suffix}`;
|
|
1952
2214
|
}
|
|
1953
2215
|
}
|
|
2216
|
+
// Bare npm package specifier fallback — route to /ns/m/node_modules/.
|
|
2217
|
+
// This catches specifiers like `source-map-js/lib/source-map-generator.js`
|
|
2218
|
+
// emitted by helpers such as the CommonJS compat transform, which Vite
|
|
2219
|
+
// would normally resolve to an absolute path but which pass through the
|
|
2220
|
+
// rewriter as bare strings here. Under HMR (core external) bundle.mjs
|
|
2221
|
+
// depends on these resolving over HTTP rather than via a filesystem
|
|
2222
|
+
// bare-specifier lookup, which iOS can't satisfy and which crashes with
|
|
2223
|
+
// "Module not found".
|
|
2224
|
+
if (spec && !spec.startsWith('/') && !spec.startsWith('./') && !spec.startsWith('../') && !/^https?:\/\//i.test(spec) && !spec.startsWith('ns-vendor:') && !spec.startsWith('@nativescript/core')) {
|
|
2225
|
+
// Only treat as a package spec if it looks like one — disallow
|
|
2226
|
+
// plain identifiers like `moment` unresolved (those are left alone
|
|
2227
|
+
// for existing vendor-routing paths to handle).
|
|
2228
|
+
const bareNpmRe = /^(?:@[A-Za-z0-9][\w.-]*\/)?[A-Za-z0-9][\w.-]*(?:\/[\w.\-/]+)?$/;
|
|
2229
|
+
if (bareNpmRe.test(spec)) {
|
|
2230
|
+
const httpSpec = `/ns/m/node_modules/${spec}`;
|
|
2231
|
+
if (httpOriginSafe) {
|
|
2232
|
+
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
2233
|
+
}
|
|
2234
|
+
return `${prefix}${httpSpec}${suffix}`;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
1954
2237
|
// Leave everything else unchanged (vendor imports, etc.)
|
|
1955
2238
|
return `${prefix}${spec}${suffix}`;
|
|
1956
2239
|
};
|
|
@@ -1959,16 +2242,17 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1959
2242
|
result = result.replace(PAT.IMPORT_PATTERN_2, replaceVueImport);
|
|
1960
2243
|
result = result.replace(PAT.EXPORT_PATTERN, replaceVueImport);
|
|
1961
2244
|
result = result.replace(PAT.IMPORT_PATTERN_3, replaceVueImport);
|
|
2245
|
+
// Side-effect imports (import "spec") — must run AFTER named-import patterns
|
|
2246
|
+
// since IMPORT_PATTERN_1 already handles `import ... from "spec"`.
|
|
2247
|
+
result = result.replace(PAT.IMPORT_PATTERN_SIDE_EFFECT, replaceVueImport);
|
|
2248
|
+
result = ensureDynamicHmrImportHelper(result);
|
|
1962
2249
|
// Extra guard: map any lingering dynamic import('@') to a safe stub module path
|
|
1963
2250
|
// to prevent device runtime normalization errors.
|
|
1964
2251
|
// Example matched: import('@') or import("@") with optional whitespace before closing paren
|
|
1965
2252
|
result = result.replace(/(import\(\s*['"])@(['"]\s*\))/g, (_m) => {
|
|
1966
2253
|
const stubExpr = `import(new URL('/ns/m/__invalid_at__.mjs', import.meta.url).href)`;
|
|
1967
2254
|
if (verbose) {
|
|
1968
|
-
|
|
1969
|
-
console.warn(`[rewrite] mapped dynamic import('@') to /ns/m/__invalid_at__.mjs via import.meta.url`);
|
|
1970
|
-
}
|
|
1971
|
-
catch { }
|
|
2255
|
+
console.warn(`[rewrite] mapped dynamic import('@') to /ns/m/__invalid_at__.mjs via import.meta.url`);
|
|
1972
2256
|
}
|
|
1973
2257
|
return stubExpr;
|
|
1974
2258
|
});
|
|
@@ -1977,10 +2261,7 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1977
2261
|
result = result.replace(/(from\s*['"])@(['"])/g, (_m, p1, p2) => {
|
|
1978
2262
|
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
1979
2263
|
if (verbose) {
|
|
1980
|
-
|
|
1981
|
-
console.warn(`[rewrite] mapped static from '@' to ${stub}`);
|
|
1982
|
-
}
|
|
1983
|
-
catch { }
|
|
2264
|
+
console.warn(`[rewrite] mapped static from '@' to ${stub}`);
|
|
1984
2265
|
}
|
|
1985
2266
|
return `${p1}${stub}${p2}`;
|
|
1986
2267
|
});
|
|
@@ -1988,10 +2269,7 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1988
2269
|
result = result.replace(/(import\s*(?!\()\s*['"])@(['"])/g, (_m, p1, p2) => {
|
|
1989
2270
|
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
1990
2271
|
if (verbose) {
|
|
1991
|
-
|
|
1992
|
-
console.warn(`[rewrite] mapped side-effect import '@' to ${stub}`);
|
|
1993
|
-
}
|
|
1994
|
-
catch { }
|
|
2272
|
+
console.warn(`[rewrite] mapped side-effect import '@' to ${stub}`);
|
|
1995
2273
|
}
|
|
1996
2274
|
return `${p1}${stub}${p2}`;
|
|
1997
2275
|
});
|
|
@@ -1999,6 +2277,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1999
2277
|
// In HTTP mode, skip legacy local-path rewrite to avoid mixing module origins
|
|
2000
2278
|
result = result.replace(PAT.VUE_FILE_IMPORT, (_m, p1, spec, p3) => {
|
|
2001
2279
|
if (httpOrigin) {
|
|
2280
|
+
if (isNsSfcSpecifier(spec)) {
|
|
2281
|
+
if (verbose)
|
|
2282
|
+
console.log(`[rewrite] .vue already routed (VUE_FILE_IMPORT http): ${spec}`);
|
|
2283
|
+
return `${p1}${spec}${p3}`;
|
|
2284
|
+
}
|
|
2002
2285
|
// Route via /ns/sfc with full query preserved
|
|
2003
2286
|
try {
|
|
2004
2287
|
let base = spec;
|
|
@@ -2048,6 +2331,13 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
2048
2331
|
console.log(`[rewrite][http] internal ns import (dynamic) → ${spec} via import.meta.url`);
|
|
2049
2332
|
return expr;
|
|
2050
2333
|
}
|
|
2334
|
+
const nodeModulesHttpSpec = toNodeModulesHttpModuleId(spec);
|
|
2335
|
+
if (nodeModulesHttpSpec) {
|
|
2336
|
+
const expr = `import(new URL('${nodeModulesHttpSpec}', import.meta.url).href)`;
|
|
2337
|
+
if (verbose)
|
|
2338
|
+
console.log(`[rewrite][http] absolute dynamic node_modules import → ${nodeModulesHttpSpec} via import.meta.url (from ${spec})`);
|
|
2339
|
+
return expr;
|
|
2340
|
+
}
|
|
2051
2341
|
const baseId = toAppModuleBaseId(spec, projectRoot);
|
|
2052
2342
|
if (!baseId)
|
|
2053
2343
|
return match;
|
|
@@ -2064,6 +2354,8 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
2064
2354
|
function createHmrWebSocketPlugin(opts) {
|
|
2065
2355
|
const verbose = !!opts.verbose;
|
|
2066
2356
|
let wss = null;
|
|
2357
|
+
let sharedTransformRequest;
|
|
2358
|
+
const pendingAngularReloadSuppressions = new Map();
|
|
2067
2359
|
const sfcFileMap = new Map();
|
|
2068
2360
|
const depFileMap = new Map();
|
|
2069
2361
|
// Generic module manifest (spec -> emitted relative .mjs path)
|
|
@@ -2074,14 +2366,42 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2074
2366
|
let registrySent = false;
|
|
2075
2367
|
let vendorBootstrapDone = false;
|
|
2076
2368
|
let pluginRoot;
|
|
2077
|
-
|
|
2369
|
+
// graphVersion starts at 1 so the very first /ns/m response uses a stable
|
|
2370
|
+
// `v1` URL tag (see dynamic-import helper at lines 398-432). Keeping it
|
|
2371
|
+
// stable during cold boot prevents double-loads when the graph fills up
|
|
2372
|
+
// lazily as modules are served.
|
|
2373
|
+
let graphVersion = 1;
|
|
2078
2374
|
// Transactional HMR batches: map graphVersion -> ordered list of changed ids for that version
|
|
2079
2375
|
const txnBatches = new Map();
|
|
2080
2376
|
const graph = new Map();
|
|
2377
|
+
// Tracks the background initial-graph population so handleHotUpdate can
|
|
2378
|
+
// await completion before computing delta roots for the first HMR event.
|
|
2379
|
+
let graphInitialPopulationPromise = null;
|
|
2380
|
+
// Cold-boot /ns/m request counter — populated the first time a /ns/m
|
|
2381
|
+
// request arrives, finalized when the request window goes idle.
|
|
2382
|
+
// See Shared across requests so a single counter spans the whole cold boot.
|
|
2383
|
+
let coldBootCounter = null;
|
|
2384
|
+
function rememberAngularReloadSuppression(root, file, ttlMs = 3000) {
|
|
2385
|
+
const absPath = normalizeHotReloadMatchPath(file);
|
|
2386
|
+
const relPath = normalizeHotReloadMatchPath(file, root);
|
|
2387
|
+
pendingAngularReloadSuppressions.set(absPath, {
|
|
2388
|
+
absPath,
|
|
2389
|
+
relPath,
|
|
2390
|
+
expiresAt: Date.now() + ttlMs,
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
function pruneAngularReloadSuppressions(now = Date.now()) {
|
|
2394
|
+
for (const [key, entry] of pendingAngularReloadSuppressions) {
|
|
2395
|
+
if (!entry || entry.expiresAt <= now) {
|
|
2396
|
+
pendingAngularReloadSuppressions.delete(key);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2081
2400
|
// Compute a dependency-closed, topologically sorted list of modules for a given set of changed ids.
|
|
2082
2401
|
// Only include application modules we can serve (e.g., under /src and known .vue/.ts/.js entries in the graph).
|
|
2083
2402
|
function computeTxnOrderForChanged(changedIds) {
|
|
2084
|
-
const
|
|
2403
|
+
const includeAppModule = (id) => matchesRuntimeGraphModuleId(id, APP_VIRTUAL_WITH_SLASH, /\.(ts|js|mjs|tsx|jsx)$/i);
|
|
2404
|
+
const includeExt = (id) => ACTIVE_STRATEGY.matchesFile(id) || includeAppModule(id);
|
|
2085
2405
|
const isApp = (id) => id.startsWith(APP_VIRTUAL_WITH_SLASH);
|
|
2086
2406
|
const roots = changedIds.map(normalizeGraphId).filter((id) => graph.has(id) && (isApp(id) || ACTIVE_STRATEGY.matchesFile(id)) && includeExt(id));
|
|
2087
2407
|
const toVisit = new Set();
|
|
@@ -2220,7 +2540,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2220
2540
|
catch { }
|
|
2221
2541
|
});
|
|
2222
2542
|
}
|
|
2223
|
-
function upsertGraphModule(rawId, code, deps) {
|
|
2543
|
+
function upsertGraphModule(rawId, code, deps, options) {
|
|
2224
2544
|
const id = normalizeGraphId(rawId);
|
|
2225
2545
|
const normDeps = deps
|
|
2226
2546
|
.map((d) => normalizeGraphId(d))
|
|
@@ -2229,19 +2549,25 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2229
2549
|
.sort();
|
|
2230
2550
|
const hash = computeHash(code);
|
|
2231
2551
|
const existing = graph.get(id);
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2552
|
+
const classification = classifyGraphUpsert(existing, hash, normDeps);
|
|
2553
|
+
if (classification === 'unchanged')
|
|
2554
|
+
return existing;
|
|
2555
|
+
// Version bumps are only meaningful for live edits — serve-time graph
|
|
2556
|
+
// warm-ups and the initial bulk walk should leave graphVersion stable.
|
|
2557
|
+
const bumpVersion = shouldBumpGraphVersion(classification, options?.bumpVersion !== false);
|
|
2558
|
+
if (bumpVersion) {
|
|
2559
|
+
graphVersion++;
|
|
2560
|
+
}
|
|
2235
2561
|
const gm = { id, deps: normDeps, hash };
|
|
2236
2562
|
graph.set(id, gm);
|
|
2237
2563
|
if (verbose) {
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2564
|
+
console.log('[hmr-ws][graph] upsert', { id, deps: normDeps, hash, graphVersion, classification, bumpVersion });
|
|
2565
|
+
console.log('[hmr-ws][graph] size', graph.size);
|
|
2566
|
+
}
|
|
2567
|
+
if (shouldBroadcastGraphUpsertDelta(classification, options?.emitDeltaOnInsert === true, options?.broadcastDelta !== false)) {
|
|
2568
|
+
emitDelta([gm], []);
|
|
2243
2569
|
}
|
|
2244
|
-
|
|
2570
|
+
return gm;
|
|
2245
2571
|
}
|
|
2246
2572
|
function isTypescriptFlavor() {
|
|
2247
2573
|
try {
|
|
@@ -2261,6 +2587,8 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2261
2587
|
async function populateInitialGraph(server) {
|
|
2262
2588
|
if (graph.size)
|
|
2263
2589
|
return; // already populated
|
|
2590
|
+
const tStart = Date.now();
|
|
2591
|
+
const versionAtStart = graphVersion;
|
|
2264
2592
|
const root = server.config.root || process.cwd();
|
|
2265
2593
|
// Avoid direct require in ESM build: lazily obtain fs & path via createRequire or dynamic import
|
|
2266
2594
|
let fs;
|
|
@@ -2276,21 +2604,33 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2276
2604
|
fs = await import('fs');
|
|
2277
2605
|
pathMod = await import('path');
|
|
2278
2606
|
}
|
|
2607
|
+
// Route every bulk transform through `sharedTransformRequest` when it's
|
|
2608
|
+
// already been wired up — this way the background walk shares the 60s
|
|
2609
|
+
// TTL cache with live /ns/m requests, so the device sees cached results
|
|
2610
|
+
// for any file the walker already visited. The fallback keeps the
|
|
2611
|
+
// walker working during server tests where the shared runner isn't
|
|
2612
|
+
// constructed yet.
|
|
2613
|
+
const bulkTransform = (rel) => {
|
|
2614
|
+
if (sharedTransformRequest) {
|
|
2615
|
+
return sharedTransformRequest(rel);
|
|
2616
|
+
}
|
|
2617
|
+
return server.transformRequest(rel);
|
|
2618
|
+
};
|
|
2279
2619
|
async function walk(dir) {
|
|
2280
2620
|
for (const name of fs.readdirSync(dir)) {
|
|
2281
|
-
|
|
2282
|
-
if (name === 'node_modules' || name.startsWith('.'))
|
|
2621
|
+
if (name === 'node_modules' || name.startsWith('.') || shouldSkipRuntimeGraphDirectoryName(name))
|
|
2283
2622
|
continue;
|
|
2623
|
+
const full = pathMod.join(dir, name);
|
|
2284
2624
|
try {
|
|
2285
2625
|
const stat = fs.statSync(full);
|
|
2286
2626
|
if (stat.isDirectory())
|
|
2287
2627
|
await walk(full);
|
|
2288
2628
|
else if (stat.isFile()) {
|
|
2289
|
-
if (/\.(vue|ts|js|mjs|tsx|jsx)
|
|
2629
|
+
if (shouldIncludeRuntimeGraphFile(full, /\.(vue|ts|js|mjs|tsx|jsx)$/i)) {
|
|
2290
2630
|
const rel = '/' + pathMod.relative(root, full).split(pathMod.sep).join('/');
|
|
2291
2631
|
// Transform via Vite to gather deps (ignore failures)
|
|
2292
2632
|
try {
|
|
2293
|
-
const transformed = await
|
|
2633
|
+
const transformed = await bulkTransform(rel);
|
|
2294
2634
|
const code = transformed?.code || '';
|
|
2295
2635
|
const deps = [];
|
|
2296
2636
|
// fallback to import relationships via moduleGraph
|
|
@@ -2301,7 +2641,10 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2301
2641
|
deps.push(m.id.split('?')[0]);
|
|
2302
2642
|
}
|
|
2303
2643
|
}
|
|
2304
|
-
|
|
2644
|
+
// bumpVersion: false — the initial walk is a bulk load, not a live
|
|
2645
|
+
// edit. Keeping graphVersion stable during cold boot avoids double
|
|
2646
|
+
// cache-key drift.
|
|
2647
|
+
upsertGraphModule(rel, code, deps, { bumpVersion: false });
|
|
2305
2648
|
}
|
|
2306
2649
|
catch { }
|
|
2307
2650
|
}
|
|
@@ -2314,6 +2657,37 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2314
2657
|
await walk(pathMod.join(root, 'src'));
|
|
2315
2658
|
}
|
|
2316
2659
|
catch { }
|
|
2660
|
+
// Diagnostic summary. Gated behind the verbose flag so the
|
|
2661
|
+
// dev console stays quiet on a normal save. Flip
|
|
2662
|
+
// NS_VITE_VERBOSE=1 to surface slow cold-boot walks; a
|
|
2663
|
+
// `bumpedVersion=no` result is the happy path, `yes`
|
|
2664
|
+
// indicates a regression.
|
|
2665
|
+
if (verbose) {
|
|
2666
|
+
console.info(formatPopulateInitialGraphSummary({
|
|
2667
|
+
moduleCount: graph.size,
|
|
2668
|
+
durationMs: Date.now() - tStart,
|
|
2669
|
+
graphVersion,
|
|
2670
|
+
bumpedVersion: graphVersion !== versionAtStart,
|
|
2671
|
+
}));
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
// Kick off `populateInitialGraph` in the background (non-awaited) so /ns/m
|
|
2675
|
+
// responses are never blocked on a full tree walk. Returns the shared
|
|
2676
|
+
// promise so hot-update code paths can await completion before computing
|
|
2677
|
+
// delta roots for the first HMR event.
|
|
2678
|
+
function ensureInitialGraphPopulationStarted(server) {
|
|
2679
|
+
if (graphInitialPopulationPromise) {
|
|
2680
|
+
return graphInitialPopulationPromise;
|
|
2681
|
+
}
|
|
2682
|
+
if (graph.size) {
|
|
2683
|
+
graphInitialPopulationPromise = Promise.resolve();
|
|
2684
|
+
return graphInitialPopulationPromise;
|
|
2685
|
+
}
|
|
2686
|
+
graphInitialPopulationPromise = populateInitialGraph(server).catch((error) => {
|
|
2687
|
+
if (verbose)
|
|
2688
|
+
console.warn('[hmr-ws][graph] background initial population failed', error);
|
|
2689
|
+
});
|
|
2690
|
+
return graphInitialPopulationPromise;
|
|
2317
2691
|
}
|
|
2318
2692
|
return {
|
|
2319
2693
|
name: 'nativescript-hmr-websocket',
|
|
@@ -2323,6 +2697,163 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2323
2697
|
const httpServer = server.httpServer;
|
|
2324
2698
|
if (!httpServer)
|
|
2325
2699
|
return;
|
|
2700
|
+
const wsAny = server.ws;
|
|
2701
|
+
if (!wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__) {
|
|
2702
|
+
const originalSend = server.ws.send.bind(server.ws);
|
|
2703
|
+
wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__ = true;
|
|
2704
|
+
server.ws.send = ((payload, ...rest) => {
|
|
2705
|
+
pruneAngularReloadSuppressions();
|
|
2706
|
+
if (shouldSuppressViteFullReloadPayload({
|
|
2707
|
+
payload,
|
|
2708
|
+
pendingEntries: pendingAngularReloadSuppressions.values(),
|
|
2709
|
+
root: pluginRoot,
|
|
2710
|
+
})) {
|
|
2711
|
+
if (verbose) {
|
|
2712
|
+
console.log('[hmr-ws][angular] suppressed vite full-reload payload', payload);
|
|
2713
|
+
}
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
return originalSend(payload, ...rest);
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
// Transform concurrency. Historically we defaulted to 1 to avoid
|
|
2720
|
+
// race conditions during HTTP HMR startup, but the shared runner
|
|
2721
|
+
// already has per-URL coalescing and an async-cached result map,
|
|
2722
|
+
// so higher fan-out is safe and dramatically reduces cold-boot
|
|
2723
|
+
// time. We cap at 8 by default to match typical dev machines and
|
|
2724
|
+
// respect Vite's internal worker pool limits. Override via the
|
|
2725
|
+
// `NS_VITE_HMR_TRANSFORM_CONCURRENCY` env var when needed.
|
|
2726
|
+
const configuredTransformConcurrency = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CONCURRENCY || '', 10);
|
|
2727
|
+
const transformConcurrency = Number.isFinite(configuredTransformConcurrency) && configuredTransformConcurrency > 0 ? configuredTransformConcurrency : 8;
|
|
2728
|
+
// Keep transformed code cached for longer across HMR updates so
|
|
2729
|
+
// that unchanged neighbours of an edited file don't re-run
|
|
2730
|
+
// through the Angular/TypeScript/Vite transform pipeline. The
|
|
2731
|
+
// HMR flow explicitly invalidates affected URLs, so a longer TTL
|
|
2732
|
+
// is safe. Override with `NS_VITE_HMR_TRANSFORM_CACHE_MS`.
|
|
2733
|
+
const configuredTransformCacheMs = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CACHE_MS || '', 10);
|
|
2734
|
+
const transformCacheMs = Number.isFinite(configuredTransformCacheMs) && configuredTransformCacheMs >= 0 ? configuredTransformCacheMs : 60000;
|
|
2735
|
+
sharedTransformRequest = createSharedTransformRequestRunner((url) => server.transformRequest(url), (url, timeoutMs) => {
|
|
2736
|
+
console.warn('[ns:m] slow transformRequest for', url, '(>' + timeoutMs + 'ms)');
|
|
2737
|
+
}, {
|
|
2738
|
+
maxConcurrent: transformConcurrency,
|
|
2739
|
+
resultCacheTtlMs: transformCacheMs,
|
|
2740
|
+
getResultCacheKey: (url) => canonicalizeTransformRequestCacheKey(url, pluginRoot || process.cwd()),
|
|
2741
|
+
});
|
|
2742
|
+
// Always-on startup banner — prints once per dev server process
|
|
2743
|
+
// so anyone investigating perf can immediately see which build
|
|
2744
|
+
// is live and what knobs are active.
|
|
2745
|
+
try {
|
|
2746
|
+
let pkgVersion = 'unknown';
|
|
2747
|
+
try {
|
|
2748
|
+
const req = createRequire(import.meta.url);
|
|
2749
|
+
const pkg = req('@nativescript/vite/package.json');
|
|
2750
|
+
if (pkg && typeof pkg.version === 'string')
|
|
2751
|
+
pkgVersion = pkg.version;
|
|
2752
|
+
}
|
|
2753
|
+
catch {
|
|
2754
|
+
// `@nativescript/vite/package.json` is not always exported; fall
|
|
2755
|
+
// back to reading the file from disk next to this module.
|
|
2756
|
+
try {
|
|
2757
|
+
const here = new URL(import.meta.url).pathname;
|
|
2758
|
+
const pkgPath = path.resolve(path.dirname(here), '..', '..', 'package.json');
|
|
2759
|
+
if (existsSync(pkgPath)) {
|
|
2760
|
+
const parsed = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
2761
|
+
if (parsed && typeof parsed.version === 'string')
|
|
2762
|
+
pkgVersion = parsed.version;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
catch { }
|
|
2766
|
+
}
|
|
2767
|
+
if (verbose) {
|
|
2768
|
+
console.info(formatServerStartupBanner({
|
|
2769
|
+
version: pkgVersion,
|
|
2770
|
+
transformConcurrency,
|
|
2771
|
+
transformCacheMs,
|
|
2772
|
+
lazyInitialGraph: true,
|
|
2773
|
+
graphVersion,
|
|
2774
|
+
}));
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
catch { }
|
|
2778
|
+
// Always-on cold-boot request trace. Runs in front of every
|
|
2779
|
+
// other middleware so it catches all NS dev routes (/ns/m/*,
|
|
2780
|
+
// /ns/rt/*, /ns/core/*, /__ns_boot__/*, etc.) with a single
|
|
2781
|
+
// hook. Closes itself after an idle window so HMR edits don't
|
|
2782
|
+
// get rolled into the cold-boot numbers. The idle window is
|
|
2783
|
+
// generous by default (5s) because V8's HTTP ESM resolver
|
|
2784
|
+
// pauses between dep levels while parsing — a too-tight window
|
|
2785
|
+
// was closing after the first wave and under-reporting boot by
|
|
2786
|
+
// 100x. Override via `NS_VITE_HMR_BOOT_TRACE_IDLE_MS` when
|
|
2787
|
+
// profiling something tricky.
|
|
2788
|
+
try {
|
|
2789
|
+
const configuredIdleMs = Number.parseInt(process.env.NS_VITE_HMR_BOOT_TRACE_IDLE_MS || '', 10);
|
|
2790
|
+
const idleWindowMs = Number.isFinite(configuredIdleMs) && configuredIdleMs > 0 ? configuredIdleMs : 5000;
|
|
2791
|
+
const configuredSummaryEvery = Number.parseInt(process.env.NS_VITE_HMR_BOOT_TRACE_PROGRESS_EVERY || '', 10);
|
|
2792
|
+
const summaryEvery = Number.isFinite(configuredSummaryEvery) && configuredSummaryEvery >= 0 ? configuredSummaryEvery : 25;
|
|
2793
|
+
if (!coldBootCounter) {
|
|
2794
|
+
coldBootCounter = createColdBootRequestCounter({
|
|
2795
|
+
summaryEvery,
|
|
2796
|
+
idleWindowMs,
|
|
2797
|
+
// Gated on the verbose flag so cold-boot progress and
|
|
2798
|
+
// the final window-closed summary stay quiet by
|
|
2799
|
+
// default. Flip NS_VITE_VERBOSE=1 to surface them.
|
|
2800
|
+
log: (line) => {
|
|
2801
|
+
if (!verbose)
|
|
2802
|
+
return;
|
|
2803
|
+
console.info(line);
|
|
2804
|
+
},
|
|
2805
|
+
});
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
catch { }
|
|
2809
|
+
server.middlewares.use((req, res, next) => {
|
|
2810
|
+
try {
|
|
2811
|
+
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
2812
|
+
const route = classifyBootRoute(urlObj.pathname);
|
|
2813
|
+
if (route === 'other')
|
|
2814
|
+
return next();
|
|
2815
|
+
if (!coldBootCounter)
|
|
2816
|
+
return next();
|
|
2817
|
+
const handle = coldBootCounter.record(urlObj.pathname);
|
|
2818
|
+
const finishOnce = () => {
|
|
2819
|
+
try {
|
|
2820
|
+
handle.finish();
|
|
2821
|
+
}
|
|
2822
|
+
catch { }
|
|
2823
|
+
};
|
|
2824
|
+
try {
|
|
2825
|
+
res.once('finish', finishOnce);
|
|
2826
|
+
res.once('close', finishOnce);
|
|
2827
|
+
}
|
|
2828
|
+
catch { }
|
|
2829
|
+
}
|
|
2830
|
+
catch { }
|
|
2831
|
+
next();
|
|
2832
|
+
});
|
|
2833
|
+
// Give `populateInitialGraph` a head start. Previously this only
|
|
2834
|
+
// kicked off on the first /ns/m hit, which meant populate was
|
|
2835
|
+
// competing with the device for the same 8 transform slots
|
|
2836
|
+
// throughout the first 4-5 seconds of cold boot. Starting at
|
|
2837
|
+
// `configureServer` time gives populate the full app
|
|
2838
|
+
// build/launch window (typically 2-3s on simulator) as a head
|
|
2839
|
+
// start, so more of its work lands before the device even
|
|
2840
|
+
// connects. Disable via `NS_VITE_HMR_DISABLE_POPULATE=1` when
|
|
2841
|
+
// profiling whether populate is helping or hurting a specific
|
|
2842
|
+
// app.
|
|
2843
|
+
try {
|
|
2844
|
+
const disablePopulate = process.env.NS_VITE_HMR_DISABLE_POPULATE === '1' || process.env.NS_VITE_HMR_DISABLE_POPULATE === 'true';
|
|
2845
|
+
if (disablePopulate) {
|
|
2846
|
+
if (verbose)
|
|
2847
|
+
console.info('[hmr-ws][populate] disabled via NS_VITE_HMR_DISABLE_POPULATE');
|
|
2848
|
+
// Short-circuit: mark as resolved so /ns/m never schedules it and
|
|
2849
|
+
// HMR still works (handleHotUpdate just has no pre-warmed graph).
|
|
2850
|
+
graphInitialPopulationPromise = Promise.resolve();
|
|
2851
|
+
}
|
|
2852
|
+
else {
|
|
2853
|
+
ensureInitialGraphPopulationStarted(server);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
catch { }
|
|
2326
2857
|
// Attempt early vendor manifest bootstrap once per server.
|
|
2327
2858
|
if (!vendorBootstrapDone) {
|
|
2328
2859
|
vendorBootstrapDone = true;
|
|
@@ -2366,20 +2897,63 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2366
2897
|
});
|
|
2367
2898
|
// Additional connection diagnostics
|
|
2368
2899
|
wss.on('connection', (ws, req) => {
|
|
2900
|
+
const role = getHmrSocketRoleFromRequestUrl(req.url);
|
|
2901
|
+
ws.__nsHmrClientRole = role;
|
|
2369
2902
|
try {
|
|
2370
2903
|
if (verbose) {
|
|
2371
2904
|
const ra = req.socket?.remoteAddress;
|
|
2372
2905
|
const rp = req.socket?.remotePort;
|
|
2373
|
-
console.log('[hmr-ws] Client connected', ra + (rp ? ':' + rp : ''));
|
|
2906
|
+
console.log('[hmr-ws] Client connected', { role, remote: ra + (rp ? ':' + rp : '') });
|
|
2374
2907
|
}
|
|
2375
2908
|
}
|
|
2376
2909
|
catch { }
|
|
2910
|
+
ws.on('close', () => {
|
|
2911
|
+
try {
|
|
2912
|
+
if (verbose) {
|
|
2913
|
+
const ra = req.socket?.remoteAddress;
|
|
2914
|
+
const rp = req.socket?.remotePort;
|
|
2915
|
+
console.log('[hmr-ws] Client disconnected', { role, remote: ra + (rp ? ':' + rp : '') });
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
catch { }
|
|
2919
|
+
});
|
|
2377
2920
|
});
|
|
2378
2921
|
wss.on('error', (err) => {
|
|
2922
|
+
console.warn('[hmr-ws] server error:', err?.message || String(err));
|
|
2923
|
+
});
|
|
2924
|
+
// Import map endpoint: GET /ns/import-map.json
|
|
2925
|
+
// Returns the import map + runtime config for __nsConfigureRuntime()
|
|
2926
|
+
server.middlewares.use(async (req, res, next) => {
|
|
2379
2927
|
try {
|
|
2380
|
-
|
|
2928
|
+
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
2929
|
+
if (urlObj.pathname !== '/ns/import-map.json')
|
|
2930
|
+
return next();
|
|
2931
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2932
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
2933
|
+
if (req.method === 'OPTIONS') {
|
|
2934
|
+
res.statusCode = 204;
|
|
2935
|
+
res.end();
|
|
2936
|
+
return;
|
|
2937
|
+
}
|
|
2938
|
+
// Determine origin from request headers or server config
|
|
2939
|
+
const host = req.headers.host || 'localhost:5173';
|
|
2940
|
+
const protocol = 'http';
|
|
2941
|
+
const origin = `${protocol}://${host}`;
|
|
2942
|
+
const runtimeConfig = buildRuntimeConfig({
|
|
2943
|
+
origin,
|
|
2944
|
+
flavor: ACTIVE_STRATEGY?.flavor || 'typescript',
|
|
2945
|
+
});
|
|
2946
|
+
res.setHeader('Content-Type', 'application/json');
|
|
2947
|
+
res.end(JSON.stringify({
|
|
2948
|
+
importMap: JSON.parse(runtimeConfig.importMap),
|
|
2949
|
+
volatilePatterns: runtimeConfig.volatilePatterns,
|
|
2950
|
+
}, null, 2));
|
|
2951
|
+
}
|
|
2952
|
+
catch (err) {
|
|
2953
|
+
console.error('[import-map] error generating import map:', err?.message || err);
|
|
2954
|
+
res.statusCode = 500;
|
|
2955
|
+
res.end(JSON.stringify({ error: 'Failed to generate import map' }));
|
|
2381
2956
|
}
|
|
2382
|
-
catch { }
|
|
2383
2957
|
});
|
|
2384
2958
|
// Dev-only HTTP ESM loader endpoint for device clients
|
|
2385
2959
|
// 1) Legacy JSON module endpoint (kept temporarily): GET /ns-module?path=/abs -> { path, code, additionalFiles }
|
|
@@ -2415,13 +2989,16 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2415
2989
|
// Transform via Vite with variant resolution (same as ws ns:fetch-module)
|
|
2416
2990
|
const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
|
|
2417
2991
|
const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
|
|
2992
|
+
const transformRoot = server.config?.root || process.cwd();
|
|
2993
|
+
const transformWorkspaceRoot = getMonorepoWorkspaceRoot(transformRoot);
|
|
2418
2994
|
const candidates = [];
|
|
2419
2995
|
if (hasExt)
|
|
2420
2996
|
candidates.push(spec);
|
|
2421
2997
|
candidates.push(baseNoExt + '.ts', baseNoExt + '.js', baseNoExt + '.tsx', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', baseNoExt + '.vue', baseNoExt + '/index.ts', baseNoExt + '/index.js', baseNoExt + '/index.tsx', baseNoExt + '/index.jsx', baseNoExt + '/index.mjs');
|
|
2998
|
+
const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, transformRoot, transformWorkspaceRoot);
|
|
2422
2999
|
let transformed = null;
|
|
2423
3000
|
let resolvedCandidate = null;
|
|
2424
|
-
for (const cand of
|
|
3001
|
+
for (const cand of transformCandidates) {
|
|
2425
3002
|
try {
|
|
2426
3003
|
const r = await server.transformRequest(cand);
|
|
2427
3004
|
if (r?.code) {
|
|
@@ -2443,7 +3020,10 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2443
3020
|
code = REQUIRE_GUARD_SNIPPET + code;
|
|
2444
3021
|
// Apply same sanitation/rewrite pipeline used for WS path
|
|
2445
3022
|
code = cleanCode(code);
|
|
2446
|
-
|
|
3023
|
+
// preserveVendorImports=true: vendor imports stay as bare specifiers
|
|
3024
|
+
// for the device-side import map (ns-vendor://) instead of being
|
|
3025
|
+
// transformed to __nsVendorRequire calls with fragile __nsPick lookups.
|
|
3026
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
|
|
2447
3027
|
code = rewriteImports(code, spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
2448
3028
|
code = ensureVariableDynamicImportHelper(code);
|
|
2449
3029
|
// Enforce upstream guarantee: no optimized deps or virtual ids remain
|
|
@@ -2455,18 +3035,6 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2455
3035
|
res.setHeader('Content-Type', 'application/json');
|
|
2456
3036
|
return void res.end(JSON.stringify({ error: e?.message || String(e) }));
|
|
2457
3037
|
}
|
|
2458
|
-
// Optional diagnostics: when ?diag=1, inject simple entry/exit logs to help isolate
|
|
2459
|
-
// execution-time failures on device without changing semantics.
|
|
2460
|
-
try {
|
|
2461
|
-
const wantDiag = urlObj.searchParams.get('diag') === '1';
|
|
2462
|
-
if (wantDiag) {
|
|
2463
|
-
const importerPath = spec.replace(/[?#].*$/, '');
|
|
2464
|
-
const enter = `try { console.log('[sfc][enter]', ${JSON.stringify(importerPath)}, 'hasReq=', (typeof globalThis.__nsRequire==='function'||typeof globalThis.require==='function')); } catch {}`;
|
|
2465
|
-
const exit = `\n;try { console.log('[sfc][loaded]', ${JSON.stringify(importerPath)}); } catch {}`;
|
|
2466
|
-
code = `${enter}\n${code}${exit}`;
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
catch { }
|
|
2470
3038
|
try {
|
|
2471
3039
|
const origin = getServerOrigin(server);
|
|
2472
3040
|
code = ensureVersionedRtImports(code, origin, graphVersion);
|
|
@@ -2499,7 +3067,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2499
3067
|
if (seen.has(depBase))
|
|
2500
3068
|
continue;
|
|
2501
3069
|
seen.add(depBase);
|
|
2502
|
-
const depCandidates = [depBase + '.ts', depBase + '.js', depBase + '.tsx', depBase + '.jsx', depBase + '.mjs', depBase + '.mts', depBase + '.cts', depBase + '.vue', depBase + '/index.ts', depBase + '/index.js', depBase + '/index.tsx', depBase + '/index.jsx', depBase + '/index.mjs'];
|
|
3070
|
+
const depCandidates = filterExistingNodeModulesTransformCandidates(depBase, [depBase + '.ts', depBase + '.js', depBase + '.tsx', depBase + '.jsx', depBase + '.mjs', depBase + '.mts', depBase + '.cts', depBase + '.vue', depBase + '/index.ts', depBase + '/index.js', depBase + '/index.tsx', depBase + '/index.jsx', depBase + '/index.mjs'], transformRoot, transformWorkspaceRoot);
|
|
2503
3071
|
let depTrans = null;
|
|
2504
3072
|
let depResolved = null;
|
|
2505
3073
|
for (const c of depCandidates) {
|
|
@@ -2516,7 +3084,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2516
3084
|
if (depTrans?.code && depResolved) {
|
|
2517
3085
|
let depCode = depTrans.code;
|
|
2518
3086
|
depCode = cleanCode(depCode);
|
|
2519
|
-
depCode = processCodeForDevice(depCode, false);
|
|
3087
|
+
depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
|
|
2520
3088
|
depCode = rewriteImports(depCode, depResolved, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
2521
3089
|
depCode = ensureVariableDynamicImportHelper(depCode);
|
|
2522
3090
|
try {
|
|
@@ -2558,6 +3126,23 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2558
3126
|
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
2559
3127
|
if (!urlObj.pathname.startsWith('/ns/m'))
|
|
2560
3128
|
return next();
|
|
3129
|
+
// Previously we awaited `populateInitialGraph(server)` here so
|
|
3130
|
+
// graphVersion would be non-zero for the first /ns/m request.
|
|
3131
|
+
// That gave deterministic URL tags but blocked the cold boot on a
|
|
3132
|
+
// full src/ tree walk (hundreds of transformRequest calls, 3-6s).
|
|
3133
|
+
//
|
|
3134
|
+
// graphVersion now starts at 1 and stays stable during cold boot
|
|
3135
|
+
// (see `upsertGraphModule`'s bumpVersion option and the inline
|
|
3136
|
+
// comment at the graphVersion declaration). We kick off the
|
|
3137
|
+
// initial population in the background so it doesn't block the
|
|
3138
|
+
// first response. `handleHotUpdate` awaits the same promise so
|
|
3139
|
+
// the first HMR event still sees a fully populated graph.
|
|
3140
|
+
ensureInitialGraphPopulationStarted(server);
|
|
3141
|
+
// Cold-boot counter is now hooked via the leading boot-trace
|
|
3142
|
+
// middleware (see `configureServer` — it records the request
|
|
3143
|
+
// and tracks finish() via res.on('close'/'finish')). This
|
|
3144
|
+
// handler used to record here but that missed the
|
|
3145
|
+
// round-trip timing and didn't track per-route breakdowns.
|
|
2561
3146
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
2562
3147
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
2563
3148
|
// Disable caching for dev ESM endpoints to avoid device-side stale module reuse
|
|
@@ -2567,7 +3152,8 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2567
3152
|
// Support both query (?path=/abs) and path-style (/ns/m/abs)
|
|
2568
3153
|
let spec = urlObj.searchParams.get('path') || '';
|
|
2569
3154
|
// Optional graph version pin for deterministic boot
|
|
2570
|
-
|
|
3155
|
+
let forcedVer = urlObj.searchParams.get('v');
|
|
3156
|
+
let bootTaggedRequest = false;
|
|
2571
3157
|
if (!spec) {
|
|
2572
3158
|
const base = '/ns/m';
|
|
2573
3159
|
let rest = urlObj.pathname.slice(base.length);
|
|
@@ -2585,22 +3171,26 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2585
3171
|
res.end('export {}\n');
|
|
2586
3172
|
return;
|
|
2587
3173
|
}
|
|
3174
|
+
const serverRoot = (server.config?.root || process.cwd());
|
|
3175
|
+
const monorepoWorkspaceRoot = getMonorepoWorkspaceRoot(serverRoot);
|
|
2588
3176
|
spec = spec.replace(/[?#].*$/, '');
|
|
2589
|
-
// Accept path-based HMR
|
|
3177
|
+
// Accept path-based boot/HMR prefixes:
|
|
3178
|
+
// /ns/m/__ns_boot__/b1/<real-spec>
|
|
3179
|
+
// /ns/m/__ns_hmr__/<tag>/<real-spec>
|
|
3180
|
+
// /ns/m/__ns_boot__/b1/__ns_hmr__/<tag>/<real-spec>
|
|
2590
3181
|
// The iOS HTTP ESM loader canonicalizes cache keys by stripping query params,
|
|
2591
3182
|
// so we must carry the cache-buster in the path.
|
|
2592
3183
|
try {
|
|
2593
|
-
const
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
3184
|
+
const decorated = stripDecoratedServePrefixes(spec);
|
|
3185
|
+
spec = decorated.cleanedSpec;
|
|
3186
|
+
bootTaggedRequest = decorated.bootTaggedRequest;
|
|
3187
|
+
forcedVer || (forcedVer = decorated.forcedVer);
|
|
2597
3188
|
}
|
|
2598
3189
|
catch { }
|
|
2599
3190
|
// Normalize absolute filesystem paths back to project-relative ids (e.g. /src/app.ts)
|
|
2600
3191
|
try {
|
|
2601
|
-
const projectRoot = (server.config?.root || process.cwd());
|
|
2602
3192
|
const toPosix = (p) => p.replace(/\\/g, '/');
|
|
2603
|
-
const rootPosix = toPosix(
|
|
3193
|
+
const rootPosix = toPosix(serverRoot);
|
|
2604
3194
|
const specPosix = toPosix(spec);
|
|
2605
3195
|
// If spec is an absolute path under the project root, convert to '/'+relative
|
|
2606
3196
|
const isAbsFs = /^\//.test(specPosix) || /^[A-Za-z]:\//.test(spec); // posix or win drive
|
|
@@ -2615,27 +3205,78 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2615
3205
|
}
|
|
2616
3206
|
}
|
|
2617
3207
|
catch { }
|
|
3208
|
+
// Serve Vite virtual modules (/@id/ prefix). These are internal
|
|
3209
|
+
// virtual modules (e.g., \0nsvite:nsconfig-json for ~/package.json)
|
|
3210
|
+
// that don't exist on disk. Decode the ID and load via plugin container.
|
|
3211
|
+
if (spec.startsWith('/@id/')) {
|
|
3212
|
+
try {
|
|
3213
|
+
// First try Vite's transform pipeline directly
|
|
3214
|
+
const vr = await sharedTransformRequest(spec);
|
|
3215
|
+
if (vr?.code) {
|
|
3216
|
+
res.statusCode = 200;
|
|
3217
|
+
res.end(vr.code);
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
catch { }
|
|
3222
|
+
try {
|
|
3223
|
+
// Fallback: decode the virtual module ID (__x00__ → \0) and
|
|
3224
|
+
// load through the plugin container directly
|
|
3225
|
+
const rawId = spec.slice('/@id/'.length).replace(/__x00__/g, '\0');
|
|
3226
|
+
const loadResult = await server.pluginContainer.load(rawId);
|
|
3227
|
+
if (loadResult) {
|
|
3228
|
+
const code = typeof loadResult === 'string' ? loadResult : loadResult.code;
|
|
3229
|
+
if (code) {
|
|
3230
|
+
res.statusCode = 200;
|
|
3231
|
+
res.end(code);
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
catch { }
|
|
3237
|
+
}
|
|
2618
3238
|
if (spec.startsWith('@/'))
|
|
2619
3239
|
spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
|
|
2620
3240
|
if (spec.startsWith('./'))
|
|
2621
3241
|
spec = spec.slice(1);
|
|
3242
|
+
const blockedNodeModulesReason = getBlockedDeviceNodeModulesReason(spec);
|
|
3243
|
+
if (blockedNodeModulesReason) {
|
|
3244
|
+
res.statusCode = 404;
|
|
3245
|
+
res.end(`// [ns:m] blocked device import\nthrow new Error(${JSON.stringify(`[ns/m] ${blockedNodeModulesReason}`)});\nexport {};\n`);
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
2622
3248
|
if (!spec.startsWith('/'))
|
|
2623
3249
|
spec = '/' + spec;
|
|
2624
3250
|
const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
|
|
2625
3251
|
const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
|
|
2626
3252
|
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'];
|
|
3253
|
+
const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, serverRoot, monorepoWorkspaceRoot);
|
|
2627
3254
|
let transformed = null;
|
|
2628
3255
|
let resolvedCandidate = null;
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
3256
|
+
const rawExplicitModule = tryReadRawExplicitJavaScriptModule(spec, serverRoot);
|
|
3257
|
+
if (rawExplicitModule) {
|
|
3258
|
+
transformed = { code: rawExplicitModule.code };
|
|
3259
|
+
resolvedCandidate = rawExplicitModule.resolvedId;
|
|
3260
|
+
}
|
|
3261
|
+
// Queue and dedupe transformRequest calls so heavy app graphs do not
|
|
3262
|
+
// overwhelm Vite with concurrent work. Slow-transform warnings start only
|
|
3263
|
+
// when the transform actually begins executing, and requests stay pending
|
|
3264
|
+
// until Vite returns a real result.
|
|
3265
|
+
const transformWithTimeout = (url, timeoutMs = 120000) => {
|
|
3266
|
+
return sharedTransformRequest(url, timeoutMs);
|
|
3267
|
+
};
|
|
3268
|
+
if (!transformed?.code) {
|
|
3269
|
+
for (const cand of transformCandidates) {
|
|
3270
|
+
try {
|
|
3271
|
+
const r = await transformWithTimeout(cand);
|
|
3272
|
+
if (r?.code) {
|
|
3273
|
+
transformed = r;
|
|
3274
|
+
resolvedCandidate = cand;
|
|
3275
|
+
break;
|
|
3276
|
+
}
|
|
2636
3277
|
}
|
|
3278
|
+
catch { }
|
|
2637
3279
|
}
|
|
2638
|
-
catch { }
|
|
2639
3280
|
}
|
|
2640
3281
|
// Fallback 1: ask Vite to resolve the id, then transform the resolved id (handles aliases and virtual ids)
|
|
2641
3282
|
if (!transformed?.code) {
|
|
@@ -2643,7 +3284,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2643
3284
|
const rid = await server.pluginContainer?.resolveId?.(spec, undefined);
|
|
2644
3285
|
const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
|
|
2645
3286
|
if (ridStr) {
|
|
2646
|
-
const r = await
|
|
3287
|
+
const r = await transformWithTimeout(ridStr);
|
|
2647
3288
|
if (r?.code) {
|
|
2648
3289
|
transformed = r;
|
|
2649
3290
|
resolvedCandidate = ridStr;
|
|
@@ -2652,27 +3293,55 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2652
3293
|
}
|
|
2653
3294
|
catch { }
|
|
2654
3295
|
}
|
|
2655
|
-
// Fallback
|
|
3296
|
+
// Fallback 1b: if spec is a /node_modules/ path, extract bare specifier
|
|
3297
|
+
// and try resolveId with that. This handles package.json "exports" field
|
|
3298
|
+
// resolution (e.g., solid-js/jsx-runtime → solid-js/dist/solid.js).
|
|
3299
|
+
if (!transformed?.code && spec.includes('/node_modules/')) {
|
|
3300
|
+
try {
|
|
3301
|
+
const nmIdx = spec.lastIndexOf('/node_modules/');
|
|
3302
|
+
const bare = spec.slice(nmIdx + '/node_modules/'.length);
|
|
3303
|
+
if (bare && !bare.startsWith('.')) {
|
|
3304
|
+
const rid = await server.pluginContainer?.resolveId?.(bare, undefined);
|
|
3305
|
+
const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
|
|
3306
|
+
if (ridStr) {
|
|
3307
|
+
const r = await sharedTransformRequest(ridStr);
|
|
3308
|
+
if (r?.code) {
|
|
3309
|
+
transformed = r;
|
|
3310
|
+
resolvedCandidate = ridStr;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
catch { }
|
|
3316
|
+
}
|
|
3317
|
+
// Fallback 2: try /@fs absolute path under project root (Vite file system alias).
|
|
3318
|
+
// In a monorepo with hoisted node_modules the file may live above
|
|
3319
|
+
// `serverRoot`, so try the workspace root next.
|
|
2656
3320
|
if (!transformed?.code) {
|
|
2657
3321
|
try {
|
|
2658
|
-
const projectRoot = (server.config?.root || process.cwd());
|
|
2659
3322
|
const toPosix = (p) => p.replace(/\\/g, '/');
|
|
2660
|
-
const
|
|
2661
|
-
const
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
3323
|
+
const rootsToTry = [serverRoot, ...(monorepoWorkspaceRoot && path.resolve(monorepoWorkspaceRoot) !== path.resolve(serverRoot) ? [monorepoWorkspaceRoot] : [])];
|
|
3324
|
+
for (const root of rootsToTry) {
|
|
3325
|
+
const rootPosix = toPosix(root).replace(/\/$/, '');
|
|
3326
|
+
const absPosix = `${rootPosix}${spec.startsWith('/') ? '' : '/'}${spec}`;
|
|
3327
|
+
const fsId = `/@fs${absPosix}`;
|
|
3328
|
+
if (resolveCandidateFilePath(fsId, serverRoot, monorepoWorkspaceRoot)) {
|
|
3329
|
+
const r = await transformWithTimeout(fsId);
|
|
3330
|
+
if (r?.code) {
|
|
3331
|
+
transformed = r;
|
|
3332
|
+
resolvedCandidate = fsId;
|
|
3333
|
+
break;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
2667
3336
|
}
|
|
2668
3337
|
}
|
|
2669
3338
|
catch { }
|
|
2670
3339
|
}
|
|
2671
3340
|
// Fallback 3: try adding ?import to hint Vite's transform pipeline
|
|
2672
3341
|
if (!transformed?.code) {
|
|
2673
|
-
for (const cand of
|
|
3342
|
+
for (const cand of transformCandidates) {
|
|
2674
3343
|
try {
|
|
2675
|
-
const r = await
|
|
3344
|
+
const r = await transformWithTimeout(`${cand}${cand.includes('?') ? '&' : '?'}import`);
|
|
2676
3345
|
if (r?.code) {
|
|
2677
3346
|
transformed = r;
|
|
2678
3347
|
resolvedCandidate = `${cand}?import`;
|
|
@@ -2682,39 +3351,80 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2682
3351
|
catch { }
|
|
2683
3352
|
}
|
|
2684
3353
|
}
|
|
2685
|
-
//
|
|
2686
|
-
//
|
|
2687
|
-
//
|
|
3354
|
+
// Solid HMR: patch @@solid-refresh's $$refreshESM to do inline patching
|
|
3355
|
+
// during module re-evaluation instead of deferring to hot.accept() callback.
|
|
3356
|
+
// In NativeScript's HTTP ESM environment, accept callbacks are registered
|
|
3357
|
+
// but not invoked by the HMR client. By adding a direct patchRegistry()
|
|
3358
|
+
// call when hot.data already has a stored registry, component updates
|
|
3359
|
+
// apply immediately when the module re-evaluates.
|
|
2688
3360
|
try {
|
|
2689
|
-
if (transformed?.code) {
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
3361
|
+
if (transformed?.code && ACTIVE_STRATEGY?.flavor === 'solid' && (resolvedCandidate || spec || '').includes('@solid-refresh')) {
|
|
3362
|
+
const PATCH_SENTINEL = '/* __ns_solid_refresh_patched__ */';
|
|
3363
|
+
const alreadyPatched = transformed.code.includes(PATCH_SENTINEL);
|
|
3364
|
+
if (verbose) {
|
|
3365
|
+
console.log('[hmr-ws][solid] @solid-refresh patch check:', { spec: resolvedCandidate || spec, alreadyPatched, codeLen: transformed.code.length });
|
|
3366
|
+
}
|
|
3367
|
+
if (!alreadyPatched) {
|
|
3368
|
+
let patchedCode = transformed.code;
|
|
3369
|
+
// Patch 1: Bypass shouldWarnAndDecline() — the vendor-bundled solid-js
|
|
3370
|
+
// may not have the 'development' condition active, making DEV empty/undefined.
|
|
3371
|
+
// In NativeScript HMR mode we are always in dev, so force it to return false.
|
|
3372
|
+
const declineCheck = 'function shouldWarnAndDecline() {';
|
|
3373
|
+
if (patchedCode.includes(declineCheck)) {
|
|
3374
|
+
patchedCode = patchedCode.replace(declineCheck, `${PATCH_SENTINEL}\nfunction shouldWarnAndDecline() { return false; /* NS HMR: always allow refresh */ }\nfunction __original_shouldWarnAndDecline() {`);
|
|
3375
|
+
if (verbose) {
|
|
3376
|
+
console.log('[hmr-ws][solid] bypassed shouldWarnAndDecline() for NativeScript HMR');
|
|
3377
|
+
}
|
|
2702
3378
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
3379
|
+
// Patch 2: Force createMemo path in createProxy.
|
|
3380
|
+
// Without the 'development' condition, $DEVCOMP is not set on components,
|
|
3381
|
+
// so createProxy falls through to `return s(props)` — a direct call with
|
|
3382
|
+
// no reactive subscription. When patchComponent fires update() (the signal
|
|
3383
|
+
// setter), nobody is listening. By forcing the createMemo path, HMRComp
|
|
3384
|
+
// subscribes to the signal and re-renders when the component changes.
|
|
3385
|
+
const proxyCondition = 'if (!s || $DEVCOMP in s) {';
|
|
3386
|
+
if (patchedCode.includes(proxyCondition)) {
|
|
3387
|
+
patchedCode = patchedCode.replace(proxyCondition, 'if (true) { /* NS HMR: always use createMemo for reactive HMR updates */');
|
|
3388
|
+
if (verbose) {
|
|
3389
|
+
console.log('[hmr-ws][solid] forced createMemo path in createProxy for NativeScript HMR');
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
// Patch 3: Inline patchRegistry call so updates apply immediately
|
|
3393
|
+
// on module re-evaluation (accept callbacks are not invoked by the HMR client).
|
|
3394
|
+
// The injected `console.log` helpers run inside the user's runtime
|
|
3395
|
+
// when @solid-refresh re-evaluates a module, so they are a runtime
|
|
3396
|
+
// concern (stripped if the user disables the patch). Keeping them
|
|
3397
|
+
// behind the patch sentinel rather than the dev-server `verbose`
|
|
3398
|
+
// flag is intentional — the patch only runs when Solid HMR fires.
|
|
3399
|
+
const marker = 'hot.data[SOLID_REFRESH] = hot.data[SOLID_REFRESH] || registry;';
|
|
3400
|
+
if (patchedCode.includes(marker)) {
|
|
3401
|
+
const patchCode = [
|
|
3402
|
+
`console.log('[solid-refresh][$$refreshESM] hot.data keys=', hot.data ? Object.keys(hot.data) : 'no-data', 'has=', !!(hot.data && hot.data[SOLID_REFRESH]));`,
|
|
3403
|
+
`if (hot.data[SOLID_REFRESH]) {`,
|
|
3404
|
+
` console.log('[solid-refresh][$$refreshESM] patching: oldComponents=', hot.data[SOLID_REFRESH].components ? hot.data[SOLID_REFRESH].components.size : 0, 'newComponents=', registry.components ? registry.components.size : 0);`,
|
|
3405
|
+
` var _shouldInvalidate = patchRegistry(hot.data[SOLID_REFRESH], registry);`,
|
|
3406
|
+
` console.log('[solid-refresh][$$refreshESM] patchRegistry result: shouldInvalidate=', _shouldInvalidate);`,
|
|
3407
|
+
`} else {`,
|
|
3408
|
+
` console.log('[solid-refresh][$$refreshESM] first load — creating registry, components=', registry.components ? registry.components.size : 0);`,
|
|
3409
|
+
`}`,
|
|
3410
|
+
].join('\n ');
|
|
3411
|
+
patchedCode = patchedCode.replace(marker, `${patchCode}\n ${marker}`);
|
|
3412
|
+
if (verbose) {
|
|
3413
|
+
console.log('[hmr-ws][solid] added inline patchRegistry for NativeScript HMR');
|
|
3414
|
+
}
|
|
2705
3415
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3416
|
+
// Work on a copy to avoid mutating Vite's cached TransformResult
|
|
3417
|
+
transformed = { ...transformed, code: patchedCode };
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
catch { }
|
|
3422
|
+
// NOTE: Path-based cache busting for /ns/m/* imports is applied in the
|
|
3423
|
+
// finalize step below (after rewriteImports adds the /ns/m/ prefix).
|
|
3424
|
+
// The block here only handles TypeScript-specific graph population.
|
|
3425
|
+
try {
|
|
3426
|
+
if (transformed?.code) {
|
|
3427
|
+
const code = transformed.code;
|
|
2718
3428
|
// TypeScript-specific graph population: when TS flavor is active
|
|
2719
3429
|
// and this is an application module under the virtual app root,
|
|
2720
3430
|
// upsert it into the HMR graph so ns:hmr-full-graph is non-empty.
|
|
@@ -2723,15 +3433,14 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2723
3433
|
const id = (resolvedCandidate || spec).replace(/[?#].*$/, '');
|
|
2724
3434
|
// Only track app modules (under APP_VIRTUAL_WITH_SLASH) and ts/js/tsx/jsx/mjs.
|
|
2725
3435
|
const isApp = id.startsWith(APP_VIRTUAL_WITH_SLASH) || id.startsWith('/app/');
|
|
2726
|
-
if (isApp && /\.(ts|tsx|js|jsx|mjs|mts|cts)$/i.test(id)) {
|
|
3436
|
+
if (isApp && /\.(ts|tsx|js|jsx|mjs|mts|cts)$/i.test(id) && !isRuntimeGraphExcludedPath(id)) {
|
|
2727
3437
|
const deps = Array.from(collectImportDependencies(code, id));
|
|
2728
3438
|
if (verbose) {
|
|
2729
|
-
|
|
2730
|
-
console.log('[hmr-ws][ts-graph] candidate', { id, depsCount: deps.length });
|
|
2731
|
-
}
|
|
2732
|
-
catch { }
|
|
3439
|
+
console.log('[hmr-ws][ts-graph] candidate', { id, depsCount: deps.length });
|
|
2733
3440
|
}
|
|
2734
|
-
|
|
3441
|
+
// Serve-time warm-up: no live edit happened, so don't bump
|
|
3442
|
+
// graphVersion.
|
|
3443
|
+
upsertGraphModule(id, code, deps, { bumpVersion: false });
|
|
2735
3444
|
}
|
|
2736
3445
|
}
|
|
2737
3446
|
}
|
|
@@ -2843,7 +3552,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2843
3552
|
if (!transformed?.code) {
|
|
2844
3553
|
// Emit a module that throws with context for easier on-device debugging
|
|
2845
3554
|
try {
|
|
2846
|
-
const tried = Array.from(new Set(candidates)).slice(0, 12);
|
|
3555
|
+
const tried = Array.from(new Set(transformCandidates.length > 0 ? transformCandidates : candidates)).slice(0, 12);
|
|
2847
3556
|
const out = `// [ns:m] transform miss path=${spec} tried=${tried.length}\n` + `throw new Error(${JSON.stringify(`[ns/m] transform failed for ${spec} (tried ${tried.length} candidates).`)});\nexport {};\n`;
|
|
2848
3557
|
res.statusCode = 404;
|
|
2849
3558
|
res.end(out);
|
|
@@ -2860,8 +3569,33 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2860
3569
|
// Prepend guard to capture any URL-based require attempts
|
|
2861
3570
|
code = REQUIRE_GUARD_SNIPPET + code;
|
|
2862
3571
|
code = cleanCode(code);
|
|
2863
|
-
|
|
2864
|
-
code =
|
|
3572
|
+
const isNodeMod = /(?:^|\/)node_modules\//.test(resolvedCandidate || spec || '');
|
|
3573
|
+
code = processCodeForDevice(code, false, true, isNodeMod, resolvedCandidate || spec);
|
|
3574
|
+
// Solid HMR: The NativeScript iOS/Android runtime provides import.meta.hot
|
|
3575
|
+
// natively (via InitializeImportMetaHot in HMRSupport.mm) with C++-backed
|
|
3576
|
+
// persistent hot.data that survives across module re-evaluations.
|
|
3577
|
+
// cleanCode() strips Vite's __vite__createHotContext assignment, which is
|
|
3578
|
+
// correct — the runtime's native hot context is better.
|
|
3579
|
+
const projectRoot = server.config?.root || process.cwd();
|
|
3580
|
+
const serverOrigin = getServerOrigin(server);
|
|
3581
|
+
if (ACTIVE_STRATEGY?.flavor === 'angular') {
|
|
3582
|
+
code = prepareAngularEntryForDevice(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
|
|
3583
|
+
}
|
|
3584
|
+
else {
|
|
3585
|
+
code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
|
|
3586
|
+
}
|
|
3587
|
+
// Expand `export * from "url"` into explicit named re-exports.
|
|
3588
|
+
// NativeScript's HTTP ESM loader may not propagate star-re-exports across
|
|
3589
|
+
// HTTP module boundaries (the namespace object gets direct exports but
|
|
3590
|
+
// misses re-exported names). By expanding to `export { a, b } from "url"`,
|
|
3591
|
+
// the engine sees explicit named exports and resolves them correctly.
|
|
3592
|
+
try {
|
|
3593
|
+
code = await expandStarExports(code, server, server.config?.root || process.cwd(), verbose, sharedTransformRequest);
|
|
3594
|
+
}
|
|
3595
|
+
catch (e) {
|
|
3596
|
+
if (verbose)
|
|
3597
|
+
console.warn('[ns/m] export* expansion failed:', e?.message);
|
|
3598
|
+
}
|
|
2865
3599
|
// Dedupe any /ns/rt named imports that duplicate destructured bindings off default /ns/rt
|
|
2866
3600
|
try {
|
|
2867
3601
|
code = dedupeRtNamedImportsAgainstDestructures(code);
|
|
@@ -2888,6 +3622,28 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2888
3622
|
}
|
|
2889
3623
|
}
|
|
2890
3624
|
catch { }
|
|
3625
|
+
// Final pass: deduplicate/resolve any bare-specifier imports that slipped
|
|
3626
|
+
// through the pipeline (e.g., extracted from JSDoc comments by import-splitting
|
|
3627
|
+
// regexes, or injected by the Angular linker on already-resolved code).
|
|
3628
|
+
try {
|
|
3629
|
+
code = deduplicateLinkerImports(code);
|
|
3630
|
+
}
|
|
3631
|
+
catch { }
|
|
3632
|
+
// CJS/UMD wrapping: if a module uses module.exports but has no ESM export default,
|
|
3633
|
+
// wrap it with CJS shims so the device HTTP ESM loader can consume it.
|
|
3634
|
+
// This handles npm packages that use CommonJS but aren't pre-bundled by Vite.
|
|
3635
|
+
//
|
|
3636
|
+
// Key constraints this must handle:
|
|
3637
|
+
// - CJS modules often declare local vars with the same names as their exports
|
|
3638
|
+
// (e.g. `function createLTTB() {...}; exports.createLTTB = createLTTB;`)
|
|
3639
|
+
// so `export var { createLTTB }` would cause a duplicate declaration.
|
|
3640
|
+
// - UMD modules reference `this` at top level (undefined in ESM) but
|
|
3641
|
+
// typically fall back to `self` or `globalThis`.
|
|
3642
|
+
// - `module`, `exports` must be shims since they don't exist in ESM.
|
|
3643
|
+
try {
|
|
3644
|
+
code = wrapCommonJsModuleForDevice(code, resolvedCandidate || null);
|
|
3645
|
+
}
|
|
3646
|
+
catch { }
|
|
2891
3647
|
try {
|
|
2892
3648
|
assertNoOptimizedArtifacts(code, `NS M ${resolvedCandidate || spec}`);
|
|
2893
3649
|
}
|
|
@@ -2907,35 +3663,81 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2907
3663
|
}
|
|
2908
3664
|
}
|
|
2909
3665
|
catch { }
|
|
3666
|
+
// `/ns/rt` and `/ns/core` URL versioning.
|
|
3667
|
+
//
|
|
3668
|
+
// Older versions of the server emitted `/ns/rt/<ver>` and
|
|
3669
|
+
// `/ns/core/<ver>` so V8's HTTP module cache would see a
|
|
3670
|
+
// fresh URL on every save. The runtime canonicalizer
|
|
3671
|
+
// (`CanonicalizeHttpUrlKey` in HMRSupport.mm) collapses
|
|
3672
|
+
// these version segments to the bare `/ns/rt` and
|
|
3673
|
+
// `/ns/core` keys before lookup, so V8 actually saw a
|
|
3674
|
+
// single cache entry — but the server was doing extra
|
|
3675
|
+
// work to inject a version segment that the runtime then
|
|
3676
|
+
// immediately stripped. Now that the runtime supports
|
|
3677
|
+
// explicit eviction (and these bridge endpoints don't
|
|
3678
|
+
// change at HMR time anyway), the version segment is
|
|
3679
|
+
// purely vestigial.
|
|
3680
|
+
//
|
|
3681
|
+
// Rather than rip the helpers out (which would touch
|
|
3682
|
+
// every ensureVersionedImports caller and risk bumping
|
|
3683
|
+
// older runtimes), we keep them but pass `verNum=0`. The
|
|
3684
|
+
// helpers still normalize URL shape (strip the absolute
|
|
3685
|
+
// origin prefix when present) but emit a stable
|
|
3686
|
+
// `/ns/rt/0` / `/ns/core/0` URL — which collapses to
|
|
3687
|
+
// `/ns/rt` / `/ns/core` in the runtime.
|
|
2910
3688
|
try {
|
|
2911
|
-
const verNum =
|
|
3689
|
+
const verNum = 0;
|
|
2912
3690
|
code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
|
|
2913
3691
|
code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
|
|
2914
3692
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
|
|
2915
3693
|
}
|
|
2916
3694
|
catch { }
|
|
2917
|
-
//
|
|
3695
|
+
// `/ns/m` URL finalize step.
|
|
3696
|
+
//
|
|
3697
|
+
// `rewriteNsMImportPathForHmr` is a canonicalizer: it
|
|
3698
|
+
// strips legacy `__ns_hmr__/<tag>/` segments and adds
|
|
3699
|
+
// `__ns_boot__/b1/` only for boot-tagged requests. The
|
|
3700
|
+
// `ver` parameter is preserved on the signature for API
|
|
3701
|
+
// compatibility but is ignored for app modules (cache
|
|
3702
|
+
// busting is driven by `__nsInvalidateModules`, not URL
|
|
3703
|
+
// versioning). We pass `'v0'` as a stable placeholder —
|
|
3704
|
+
// the canonicalizer emits the same URL regardless of
|
|
3705
|
+
// this value, but a constant placeholder makes the
|
|
3706
|
+
// contract explicit.
|
|
3707
|
+
//
|
|
3708
|
+
// SFC URLs (line below, `/ns/sfc/${verTag}/...`) still
|
|
3709
|
+
// embed a version because the Vue SFC pathway does not
|
|
3710
|
+
// yet have an eviction protocol. The runtime
|
|
3711
|
+
// canonicalizer does NOT strip `/ns/sfc/<ver>/`, so Vue
|
|
3712
|
+
// users still see per-save SFC re-fetches — that's a
|
|
3713
|
+
// known follow-up.
|
|
2918
3714
|
try {
|
|
2919
|
-
const
|
|
3715
|
+
const verTag = (() => {
|
|
3716
|
+
const numeric = getNumericServeVersionTag(forcedVer, Number(graphVersion || 0));
|
|
3717
|
+
return numeric > 0 ? `v${numeric}` : 'v0';
|
|
3718
|
+
})();
|
|
2920
3719
|
const origin = getServerOrigin(server);
|
|
3720
|
+
const rewritePath = (p) => rewriteNsMImportPathForHmr(p, 'v0', bootTaggedRequest);
|
|
3721
|
+
// /ns/m URL forms — all collapse to canonical stable
|
|
3722
|
+
// URLs via the Phase 3a rewriter.
|
|
2921
3723
|
// 1) Static imports: import ... from "/ns/m/..."
|
|
2922
|
-
code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$
|
|
3724
|
+
code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2923
3725
|
// 2) Side-effect imports: import "/ns/m/..."
|
|
2924
|
-
code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$
|
|
3726
|
+
code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2925
3727
|
// 3) Dynamic imports: import("/ns/m/...")
|
|
2926
|
-
code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, `$
|
|
3728
|
+
code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2927
3729
|
// 4) new URL("/ns/m/...", import.meta.url)
|
|
2928
|
-
code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\))/g, `$
|
|
3730
|
+
code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2929
3731
|
// 5) __ns_import(new URL('/ns/m/...', import.meta.url).href)
|
|
2930
|
-
code = code.replace(/(new\s+URL\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*,\s*import\.meta\.url\s*\)\.href)/g, `$
|
|
2931
|
-
// 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href →
|
|
3732
|
+
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}`);
|
|
3733
|
+
// 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href → canonical stable URL.
|
|
2932
3734
|
try {
|
|
2933
|
-
code = code.replace(/new\s+URL\(\s*["'](\/ns\/m\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}${p1}
|
|
3735
|
+
code = code.replace(/new\s+URL\(\s*["'](\/ns\/m\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}${rewritePath(p1)}`)}`);
|
|
2934
3736
|
}
|
|
2935
3737
|
catch { }
|
|
2936
|
-
// 7)
|
|
3738
|
+
// 7) SFC URLs (Vue) — still versioned. See header comment.
|
|
2937
3739
|
try {
|
|
2938
|
-
code = code.replace(/new\s+URL\(\s*["']\/ns\/sfc(\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}/ns/sfc/${
|
|
3740
|
+
code = code.replace(/new\s+URL\(\s*["']\/ns\/sfc(\/[^"'?]+)(?:\?[^"']*)?["']\s*,\s*import\.meta\.url\s*\)\.href/g, (_m, p1) => `${JSON.stringify(`${origin}/ns/sfc/${verTag}${p1}`)}`);
|
|
2939
3741
|
}
|
|
2940
3742
|
catch { }
|
|
2941
3743
|
}
|
|
@@ -2946,13 +3748,25 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2946
3748
|
code = ensureDestructureCoreImports(code);
|
|
2947
3749
|
}
|
|
2948
3750
|
catch { }
|
|
3751
|
+
// Boot-time module graph progress: while the app is still replacing the
|
|
3752
|
+
// placeholder, emit lightweight progress updates as /ns/m modules begin
|
|
3753
|
+
// evaluating. This keeps the overlay moving during large initial graphs.
|
|
3754
|
+
try {
|
|
3755
|
+
if (bootTaggedRequest) {
|
|
3756
|
+
const bootModuleLabel = String(spec || '').replace(/\\/g, '/');
|
|
3757
|
+
const bootProgressSnippet = buildBootProgressSnippet(bootModuleLabel);
|
|
3758
|
+
code = bootProgressSnippet + code;
|
|
3759
|
+
code = hoistTopLevelStaticImports(code);
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
catch { }
|
|
2949
3763
|
// Dev-only: link-check static imports to surface missing bindings early
|
|
2950
3764
|
try {
|
|
2951
3765
|
const devCheck = process.env.NODE_ENV !== 'production';
|
|
2952
3766
|
if (devCheck) {
|
|
2953
3767
|
const ast = babelParse(code, {
|
|
2954
3768
|
sourceType: 'module',
|
|
2955
|
-
plugins:
|
|
3769
|
+
plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
|
|
2956
3770
|
});
|
|
2957
3771
|
const imports = [];
|
|
2958
3772
|
babelTraverse(ast, {
|
|
@@ -3036,6 +3850,15 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3036
3850
|
continue;
|
|
3037
3851
|
const hasDefault = /\bexport\s+default\b/.test(targetCode) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(targetCode);
|
|
3038
3852
|
if (!hasDefault) {
|
|
3853
|
+
// CJS/UMD modules won't have `export default` — they get CJS-wrapped
|
|
3854
|
+
// by the serving pipeline. Only warn, don't fatally block the importer.
|
|
3855
|
+
const hasCjsPattern = /\bmodule\s*\.\s*exports\b/.test(targetCode) || /\bexports\s*\.\s*\w/.test(targetCode);
|
|
3856
|
+
if (hasCjsPattern) {
|
|
3857
|
+
if (verbose) {
|
|
3858
|
+
console.warn(`[ns:m][link-check] CJS module without export default: ${u.pathname} (will be CJS-wrapped at serve time)`);
|
|
3859
|
+
}
|
|
3860
|
+
continue;
|
|
3861
|
+
}
|
|
3039
3862
|
const msg = `[link-check] Missing default export in ${u.pathname}${u.search} (imported by ${resolvedCandidate || spec})`;
|
|
3040
3863
|
// Emit a module that throws to surface the exact offender
|
|
3041
3864
|
res.statusCode = 200;
|
|
@@ -3047,19 +3870,15 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3047
3870
|
}
|
|
3048
3871
|
}
|
|
3049
3872
|
catch (eLC) {
|
|
3050
|
-
|
|
3873
|
+
if (verbose) {
|
|
3051
3874
|
console.warn('[ns:m][link-check] failed', eLC?.message || eLC);
|
|
3052
3875
|
}
|
|
3053
|
-
catch { }
|
|
3054
3876
|
}
|
|
3055
3877
|
res.statusCode = 200;
|
|
3056
3878
|
res.end(code);
|
|
3057
3879
|
}
|
|
3058
3880
|
catch (e) {
|
|
3059
|
-
|
|
3060
|
-
console.warn('[sfc-asm] error serving', req.url, e && e.message ? e.message : e);
|
|
3061
|
-
}
|
|
3062
|
-
catch { }
|
|
3881
|
+
console.warn('[sfc-asm] error serving', req.url, e && e.message ? e.message : e);
|
|
3063
3882
|
res.statusCode = 500;
|
|
3064
3883
|
res.end('export {}\n');
|
|
3065
3884
|
}
|
|
@@ -3092,8 +3911,10 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3092
3911
|
`let __cached_rt = null;\n` +
|
|
3093
3912
|
`let __cached_vm = null;\n` +
|
|
3094
3913
|
`const __RT_REALM_TAG = (globalThis.__NS_RT_REALM__ ||= Math.random().toString(36).slice(2));\n` +
|
|
3095
|
-
//
|
|
3096
|
-
|
|
3914
|
+
// One-shot evaluation marker to confirm the bridge is executed on
|
|
3915
|
+
// device. Gated on __NS_ENV_VERBOSE__ so it stays silent unless
|
|
3916
|
+
// the developer opts in via NS_VITE_VERBOSE / VITE_DEBUG_LOGS.
|
|
3917
|
+
`try { if (!(globalThis.__NS_RT_ONCE__ && globalThis.__NS_RT_ONCE__.eval)) { (globalThis.__NS_RT_ONCE__ ||= {}).eval = true; if (globalThis.__NS_ENV_VERBOSE__) console.log('[ns-rt] evaluated', { rtRealm: __RT_REALM_TAG }); } } catch {}\n` +
|
|
3097
3918
|
`function __ensure(){\n` +
|
|
3098
3919
|
` if (__cached_rt) return __cached_rt;\n` +
|
|
3099
3920
|
` let vm = null;\n` +
|
|
@@ -3185,7 +4006,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3185
4006
|
`export const vShow = (__ensure().vShow);\n` +
|
|
3186
4007
|
`export const createApp = (...a) => (__ensure().createApp)(...a);\n` +
|
|
3187
4008
|
`export const registerElement = (...a) => (__ensure().registerElement)(...a);\n` +
|
|
3188
|
-
`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) {
|
|
4009
|
+
`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) { console.error('[ns-rt] $navigateTo app navigator error', e); throw e; } } console.error('[ns-rt] $navigateTo unavailable: app navigator missing'); throw new Error('$navigateTo unavailable: app navigator missing'); } ;\n` +
|
|
3189
4010
|
`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` +
|
|
3190
4011
|
`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` +
|
|
3191
4012
|
`export default {\n` +
|
|
@@ -3247,35 +4068,262 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3247
4068
|
return next();
|
|
3248
4069
|
}
|
|
3249
4070
|
});
|
|
4071
|
+
// 2.5.1) Catch-all redirect for stray /node_modules/@nativescript/core/*
|
|
4072
|
+
// requests — route them to the /ns/core bridge so they get the same
|
|
4073
|
+
// __DEV__/__IOS__ preamble and specifier rewriting. Without this,
|
|
4074
|
+
// Vite's default /node_modules/ handler serves the raw file, which
|
|
4075
|
+
// references bare __DEV__ and crashes at module eval.
|
|
4076
|
+
server.middlewares.use((req, _res, next) => {
|
|
4077
|
+
try {
|
|
4078
|
+
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
4079
|
+
const coreNmPrefix = '/node_modules/@nativescript/core';
|
|
4080
|
+
if (!urlObj.pathname.startsWith(coreNmPrefix))
|
|
4081
|
+
return next();
|
|
4082
|
+
const sub = urlObj.pathname.slice(coreNmPrefix.length).replace(/^\/+/, '');
|
|
4083
|
+
if (sub === '' || sub === 'index.js' || sub === 'index') {
|
|
4084
|
+
req.url = `/ns/core`;
|
|
4085
|
+
}
|
|
4086
|
+
else {
|
|
4087
|
+
req.url = `/ns/core/${sub}`;
|
|
4088
|
+
}
|
|
4089
|
+
return next();
|
|
4090
|
+
}
|
|
4091
|
+
catch {
|
|
4092
|
+
return next();
|
|
4093
|
+
}
|
|
4094
|
+
});
|
|
3250
4095
|
// 2.6) ESM bridge for @nativescript/core: GET /ns/core[/<ver>][?p=sub/path]
|
|
4096
|
+
//
|
|
4097
|
+
// Since bundle.mjs no longer bundles @nativescript/core (it is
|
|
4098
|
+
// declared external in the rolldown config under HMR), this
|
|
4099
|
+
// endpoint is the ONE place core is evaluated. Every consumer —
|
|
4100
|
+
// bundle.mjs's own `@nativescript/core*` imports (resolved to
|
|
4101
|
+
// full HTTP URLs in the entry virtual module), externalized
|
|
4102
|
+
// vendor packages, HTTP-served app modules — all end up here.
|
|
4103
|
+
// No more proxy bridge, no enumeration, no namespace detection,
|
|
4104
|
+
// no prototype-polluted maps. We just serve Vite's authoritative
|
|
4105
|
+
// transformed module content.
|
|
4106
|
+
//
|
|
4107
|
+
// iOS caches by URL path, so each unique URL is evaluated exactly
|
|
4108
|
+
// once per app lifetime. Every class identity is shared, every
|
|
4109
|
+
// `register()` side effect runs once, every `Application` reference
|
|
4110
|
+
// is the same iosApp singleton. The entire class of "does not
|
|
4111
|
+
// provide an export named X" and "Cannot redefine property" errors
|
|
4112
|
+
// is eliminated by construction.
|
|
3251
4113
|
server.middlewares.use(async (req, res, next) => {
|
|
3252
4114
|
try {
|
|
3253
4115
|
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3254
|
-
|
|
4116
|
+
const coreRequest = parseCoreBridgeRequest(urlObj.pathname, urlObj.searchParams, Number(graphVersion || 0));
|
|
4117
|
+
if (!coreRequest)
|
|
3255
4118
|
return next();
|
|
3256
4119
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
3257
4120
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
3258
4121
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
3259
4122
|
res.setHeader('Pragma', 'no-cache');
|
|
3260
4123
|
res.setHeader('Expires', '0');
|
|
3261
|
-
const
|
|
3262
|
-
const
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
4124
|
+
const { normalizedSub, sub, ver } = coreRequest;
|
|
4125
|
+
const resolveModuleId = async (moduleId) => {
|
|
4126
|
+
const resolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
|
|
4127
|
+
return typeof resolved === 'string' ? resolved : resolved?.id || null;
|
|
4128
|
+
};
|
|
4129
|
+
let modulePath = null;
|
|
4130
|
+
if (sub) {
|
|
4131
|
+
const resolvedSubpath = normalizedSub || sub;
|
|
4132
|
+
modulePath = await resolveRuntimeCoreModulePath(resolvedSubpath, resolveModuleId);
|
|
4133
|
+
if (!modulePath) {
|
|
4134
|
+
modulePath = `/node_modules/@nativescript/core/${resolvedSubpath}`;
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
else {
|
|
4138
|
+
modulePath = (await resolveModuleId('@nativescript/core')) || '/node_modules/@nativescript/core/index.js';
|
|
4139
|
+
}
|
|
4140
|
+
const transformed = await sharedTransformRequest(modulePath);
|
|
4141
|
+
if (!transformed?.code) {
|
|
4142
|
+
res.statusCode = 500;
|
|
4143
|
+
res.setHeader('Content-Type', 'application/json');
|
|
4144
|
+
res.end(JSON.stringify({ error: 'core-transform-failed', modulePath, sub: sub || null }));
|
|
4145
|
+
return;
|
|
4146
|
+
}
|
|
4147
|
+
// Vite's transform output references module IDs with /@fs,
|
|
4148
|
+
// relative specifiers, or absolute project paths. Rewrite
|
|
4149
|
+
// those to URLs iOS can fetch over HTTP.
|
|
4150
|
+
let rewritten = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
|
|
4151
|
+
// Invariant D (CJS/ESM interop shape) — EXPORT-SIDE fix.
|
|
4152
|
+
//
|
|
4153
|
+
// `@nativescript/core/index.js` declares namespace
|
|
4154
|
+
// re-exports like:
|
|
4155
|
+
// export * as Utils from './utils';
|
|
4156
|
+
// The ES spec says these produce Module Namespace Objects
|
|
4157
|
+
// with [[Prototype]] = null. Consumers that reach them
|
|
4158
|
+
// via direct ESM import — `import { Utils } from
|
|
4159
|
+
// '@nativescript/core'` — get the raw null-proto value,
|
|
4160
|
+
// bypassing any CJS `require` shim we install. Most
|
|
4161
|
+
// consumers tolerate this, but CJS-style interop (most
|
|
4162
|
+
// notably zone.js's `patchMethod`) calls
|
|
4163
|
+
// `hasOwnProperty` on the target and crashes on
|
|
4164
|
+
// null-proto.
|
|
4165
|
+
//
|
|
4166
|
+
// We rewrite the re-export to a shape-wrapped const:
|
|
4167
|
+
// import * as __ns_re_Utils__ from './utils';
|
|
4168
|
+
// export const Utils = __NS_CJS_SHAPE__(__ns_re_Utils__);
|
|
4169
|
+
// so the EXPORT itself is a plain object — visible to
|
|
4170
|
+
// both ESM and CJS consumers consistently.
|
|
4171
|
+
//
|
|
4172
|
+
// We only pay the rewrite cost when the module actually
|
|
4173
|
+
// contains namespace re-exports (i.e., the main
|
|
4174
|
+
// `index.js`). Subpaths (`/utils`, `/http`, …) don't
|
|
4175
|
+
// re-export via `export * as`; they expose named
|
|
4176
|
+
// exports directly, so the rewrite is a no-op on them.
|
|
4177
|
+
if (hasNamespaceReExport(rewritten)) {
|
|
4178
|
+
rewritten = rewriteNamespaceReExportsForShape(rewritten);
|
|
4179
|
+
}
|
|
4180
|
+
// Prepend the build-time defines (__DEV__, __IOS__, __ANDROID__,
|
|
4181
|
+
// __APPLE__, …) that @nativescript/core source references directly.
|
|
4182
|
+
// Vite's `define` config substitutes these in user-code transforms but
|
|
4183
|
+
// skips node_modules by default; since core is now external and served
|
|
4184
|
+
// over HTTP from this endpoint, the served transformed code still has
|
|
4185
|
+
// bare identifiers like `if (__DEV__) …`. Without these consts, V8
|
|
4186
|
+
// hits `ReferenceError: __DEV__ is not defined` at module eval because
|
|
4187
|
+
// globalThis.__DEV__ is set by bundle.mjs's body AFTER all static
|
|
4188
|
+
// imports (including these core modules) have resolved.
|
|
4189
|
+
//
|
|
4190
|
+
// We inject LITERAL boolean values based on CLI flags + dev-server
|
|
4191
|
+
// mode rather than reading from globalThis, so the defines are
|
|
4192
|
+
// resolved even before bundle.mjs's body runs.
|
|
4193
|
+
const __cliFlags = getCliFlags() || {};
|
|
4194
|
+
const __platformIsAndroid = !!__cliFlags.android;
|
|
4195
|
+
const __platformIsVisionOS = !!__cliFlags.visionos;
|
|
4196
|
+
const __platformIsIOS = !__platformIsAndroid && !__platformIsVisionOS;
|
|
4197
|
+
const preamble = [
|
|
4198
|
+
`const __ANDROID__ = ${__platformIsAndroid ? 'true' : 'false'};`,
|
|
4199
|
+
`const __IOS__ = ${__platformIsIOS ? 'true' : 'false'};`,
|
|
4200
|
+
`const __VISIONOS__ = ${__platformIsVisionOS ? 'true' : 'false'};`,
|
|
4201
|
+
`const __APPLE__ = __IOS__ || __VISIONOS__;`,
|
|
4202
|
+
`const __DEV__ = ${server.config?.mode === 'development' ? 'true' : 'false'};`,
|
|
4203
|
+
`const __COMMONJS__ = false;`,
|
|
4204
|
+
`const __NS_WEBPACK__ = false;`,
|
|
4205
|
+
`const __NS_ENV_VERBOSE__ = globalThis.__NS_ENV_VERBOSE__ !== undefined ? !!globalThis.__NS_ENV_VERBOSE__ : false;`,
|
|
4206
|
+
`const __CSS_PARSER__ = 'css-tree';`,
|
|
4207
|
+
`const __UI_USE_XML_PARSER__ = true;`,
|
|
4208
|
+
`const __UI_USE_EXTERNAL_RENDERER__ = false;`,
|
|
4209
|
+
`const __TEST__ = false;`,
|
|
4210
|
+
].join('\n');
|
|
4211
|
+
// Boot-time instrumentation + module self-registration.
|
|
4212
|
+
//
|
|
4213
|
+
// - URL canonicalization: the same logical module must
|
|
4214
|
+
// always resolve to byte-identical URLs across every
|
|
4215
|
+
// emitter. The /ns/core handler records the first URL
|
|
4216
|
+
// seen for each canonical sub (or '' for main) in
|
|
4217
|
+
// `globalThis.__NS_CORE_FIRST_URL__` and fails hard on
|
|
4218
|
+
// mismatch so drift in any emitter surfaces
|
|
4219
|
+
// immediately, before the realm splits.
|
|
4220
|
+
// - CJS/ESM boot order: CommonJS
|
|
4221
|
+
// `require('@nativescript/core/...')` calls from
|
|
4222
|
+
// vendor install() hooks must resolve to the SAME
|
|
4223
|
+
// ESM namespace that ran this side-effect preamble.
|
|
4224
|
+
// The registration below keys the namespace object
|
|
4225
|
+
// under BOTH the bare specifier and the canonical
|
|
4226
|
+
// subpath (and raw subpath for back-compat) so the
|
|
4227
|
+
// vendor shim's `createRequire` and the main-entry
|
|
4228
|
+
// `_nsReq` hit on any lookup form.
|
|
4229
|
+
const rawSub = normalizedSub || sub || '';
|
|
4230
|
+
const canonicalSub = normalizeCoreSubCanonical(rawSub);
|
|
4231
|
+
const registrationKeySet = new Set();
|
|
4232
|
+
registrationKeySet.add(canonicalSub ? `@nativescript/core/${canonicalSub}` : '@nativescript/core');
|
|
4233
|
+
registrationKeySet.add(canonicalSub);
|
|
4234
|
+
if (rawSub && rawSub !== canonicalSub) {
|
|
4235
|
+
registrationKeySet.add(`@nativescript/core/${rawSub}`);
|
|
4236
|
+
registrationKeySet.add(rawSub);
|
|
4237
|
+
}
|
|
4238
|
+
const registrationKeys = Array.from(registrationKeySet).map((k) => JSON.stringify(k));
|
|
4239
|
+
const canonicalUrl = `${getServerOrigin(server)}` + (canonicalSub ? `/ns/core/${canonicalSub}` : '/ns/core');
|
|
4240
|
+
const instrumentationHeader = [
|
|
4241
|
+
`/* @nativescript/core bridge — canonical URL: ${canonicalUrl} */`,
|
|
4242
|
+
`try { if (typeof globalThis !== 'undefined') {`,
|
|
4243
|
+
` const __nsFirst = globalThis.__NS_CORE_FIRST_URL__ || (globalThis.__NS_CORE_FIRST_URL__ = Object.create(null));`,
|
|
4244
|
+
` const __nsSeen = globalThis.__NS_CORE_FETCHED_URLS__ || (globalThis.__NS_CORE_FETCHED_URLS__ = []);`,
|
|
4245
|
+
` const __nsKey = ${JSON.stringify(canonicalSub)};`,
|
|
4246
|
+
` const __nsUrl = ${JSON.stringify(canonicalUrl)};`,
|
|
4247
|
+
` __nsSeen.push(__nsUrl);`,
|
|
4248
|
+
` if (typeof __nsFirst[__nsKey] === 'string' && __nsFirst[__nsKey] !== __nsUrl) {`,
|
|
4249
|
+
` throw new Error('[ns-core] URL drift for sub=' + __nsKey + ': first=' + __nsFirst[__nsKey] + ' now=' + __nsUrl);`,
|
|
4250
|
+
` }`,
|
|
4251
|
+
` if (!__nsFirst[__nsKey]) __nsFirst[__nsKey] = __nsUrl;`,
|
|
4252
|
+
` globalThis.__NS_CORE_EVAL_COUNT__ = (globalThis.__NS_CORE_EVAL_COUNT__ || 0) + 1;`,
|
|
4253
|
+
`} } catch (e) { console.warn('[ns-core] instrumentation failed:', (e && e.message) || e); }`,
|
|
4254
|
+
].join('\n');
|
|
4255
|
+
// CJS/ESM interop shape — REGISTRATION side.
|
|
4256
|
+
//
|
|
4257
|
+
// The actual shape installer runs earlier in the module
|
|
4258
|
+
// body (between preamble and selfImport; see
|
|
4259
|
+
// buildShapeInstallHeader). At this point we just read
|
|
4260
|
+
// globalThis.__NS_CJS_SHAPE__ and apply it to the self
|
|
4261
|
+
// namespace before registering under the CJS key space.
|
|
4262
|
+
//
|
|
4263
|
+
// Why shape self at registration: consumers that reach
|
|
4264
|
+
// `@nativescript/core` via `require()` (legacy vendors,
|
|
4265
|
+
// `globalThis.require` shim) look up the registry. They
|
|
4266
|
+
// expect a plain object (Object.prototype in chain) so
|
|
4267
|
+
// `.hasOwnProperty` / `.toString` work. Shaping once on
|
|
4268
|
+
// registration — the shape function is identity-preserving
|
|
4269
|
+
// via WeakMap — gives a stable, shared, CJS-compatible
|
|
4270
|
+
// view without copying on every require.
|
|
4271
|
+
const registrationFooter = [
|
|
4272
|
+
`try { if (typeof globalThis !== 'undefined') {`,
|
|
4273
|
+
` const __nsReg = globalThis.__NS_CORE_MODULES__ || (globalThis.__NS_CORE_MODULES__ = Object.create(null));`,
|
|
4274
|
+
` const __nsShapeFn = typeof globalThis.__NS_CJS_SHAPE__ === 'function' ? globalThis.__NS_CJS_SHAPE__ : function (x) { return x; };`,
|
|
4275
|
+
` const __nsSelfRaw = (typeof __ns_core_self_ns__ !== 'undefined') ? __ns_core_self_ns__ : { default: undefined };`,
|
|
4276
|
+
` const __nsSelf = __nsShapeFn(__nsSelfRaw);`,
|
|
4277
|
+
...registrationKeys.map((k) => ` __nsReg[${k}] = __nsSelf;`),
|
|
4278
|
+
`} } catch (e) { console.warn('[ns-core] self-register failed:', (e && e.message) || e); }`,
|
|
4279
|
+
].join('\n');
|
|
4280
|
+
// Bind `import * as __ns_core_self_ns__` to the module's
|
|
4281
|
+
// own export namespace so the footer can stash it into
|
|
4282
|
+
// the registry. Self-import is a no-op at eval time —
|
|
4283
|
+
// V8 resolves it to the module record we're already
|
|
4284
|
+
// evaluating and the final namespace is the same object
|
|
4285
|
+
// the registry receives. We use the CANONICAL URL here
|
|
4286
|
+
// so the self-import participates in Invariant A along
|
|
4287
|
+
// with every other @nativescript/core URL.
|
|
4288
|
+
const canonicalUrlForSelf = canonicalSub ? `/ns/core/${canonicalSub}` : '/ns/core';
|
|
4289
|
+
const selfImport = `import * as __ns_core_self_ns__ from ${JSON.stringify(canonicalUrlForSelf)};`;
|
|
4290
|
+
// Invariant D — SHAPE INSTALLER.
|
|
4291
|
+
//
|
|
4292
|
+
// Emits idempotent body-code that installs
|
|
4293
|
+
// globalThis.__NS_CJS_SHAPE__ BEFORE `rewritten`'s body
|
|
4294
|
+
// runs. This matters because the rewrite step above may
|
|
4295
|
+
// have produced statements like
|
|
4296
|
+
// `export const Utils = (typeof globalThis.__NS_CJS_SHAPE__ ...)(__ns_re_Utils__);`
|
|
4297
|
+
// that execute during module evaluation. Without the
|
|
4298
|
+
// installer running first, the ternary falls back to
|
|
4299
|
+
// identity — still safe, but the null-proto namespace
|
|
4300
|
+
// leaks through and consumers that expect a plain
|
|
4301
|
+
// object would still crash.
|
|
4302
|
+
//
|
|
4303
|
+
// Placement is important: BEFORE selfImport in the
|
|
4304
|
+
// concatenation. ESM imports are hoisted regardless of
|
|
4305
|
+
// textual position, but body code executes in source
|
|
4306
|
+
// order. Placing the installer first guarantees it
|
|
4307
|
+
// runs before any body statement in `rewritten`.
|
|
4308
|
+
//
|
|
4309
|
+
// Install is idempotent: `|| (globalThis.X = ...)` so
|
|
4310
|
+
// whichever /ns/core module evaluates first wins and
|
|
4311
|
+
// every subsequent module becomes a no-op.
|
|
4312
|
+
const shapeInstallHeader = buildShapeInstallHeader();
|
|
4313
|
+
// Invariant D — DEFAULT EXPORT BRIDGE.
|
|
4314
|
+
//
|
|
4315
|
+
// See `buildDefaultExportFooter` in ns-core-cjs-shape.ts
|
|
4316
|
+
// for the full rationale (consumer matrix, skip conditions,
|
|
4317
|
+
// why the default isn't shaped). The short version:
|
|
4318
|
+
// upstream rewrites turn `import { X } from '@nativescript/core'`
|
|
4319
|
+
// into a DEFAULT import, and the bridge has to provide one.
|
|
4320
|
+
const defaultExportFooter = buildDefaultExportFooter(rewritten);
|
|
4321
|
+
const moduleCode = [instrumentationHeader, preamble, shapeInstallHeader, selfImport, rewritten, defaultExportFooter, registrationFooter].join('\n');
|
|
3275
4322
|
res.statusCode = 200;
|
|
3276
|
-
res.end(
|
|
4323
|
+
res.end(moduleCode);
|
|
3277
4324
|
}
|
|
3278
4325
|
catch (e) {
|
|
4326
|
+
console.warn('[ns-core-bridge] serve failed:', e?.message);
|
|
3279
4327
|
next();
|
|
3280
4328
|
}
|
|
3281
4329
|
});
|
|
@@ -3285,14 +4333,11 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3285
4333
|
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3286
4334
|
if (!(urlObj.pathname === '/ns/entry-rt'))
|
|
3287
4335
|
return next();
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
console.log('[hmr-http] GET /ns/entry-rt from', ra + (rp ? ':' + rp : ''));
|
|
3293
|
-
}
|
|
4336
|
+
if (verbose) {
|
|
4337
|
+
const ra = req.socket?.remoteAddress;
|
|
4338
|
+
const rp = req.socket?.remotePort;
|
|
4339
|
+
console.log('[hmr-http] GET /ns/entry-rt from', ra + (rp ? ':' + rp : ''));
|
|
3294
4340
|
}
|
|
3295
|
-
catch { }
|
|
3296
4341
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
3297
4342
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
3298
4343
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
@@ -3300,18 +4345,41 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3300
4345
|
res.setHeader('Expires', '0');
|
|
3301
4346
|
let content = '';
|
|
3302
4347
|
try {
|
|
3303
|
-
const
|
|
3304
|
-
const entryRtPath =
|
|
3305
|
-
|
|
3306
|
-
content = fs.readFileSync(entryRtPath, 'utf-8');
|
|
4348
|
+
const _req = createRequire(import.meta.url);
|
|
4349
|
+
const entryRtPath = _req.resolve('@nativescript/vite/hmr/entry-runtime.js');
|
|
4350
|
+
content = readFileSync(entryRtPath, 'utf-8');
|
|
3307
4351
|
}
|
|
3308
4352
|
catch (e) {
|
|
3309
|
-
|
|
4353
|
+
// .js not found (source tree without build) — transform .ts on the fly
|
|
4354
|
+
try {
|
|
4355
|
+
const tsPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'entry-runtime.ts');
|
|
4356
|
+
if (existsSync(tsPath)) {
|
|
4357
|
+
const tsSource = readFileSync(tsPath, 'utf-8');
|
|
4358
|
+
const result = babelCore.transformSync(tsSource, {
|
|
4359
|
+
filename: tsPath,
|
|
4360
|
+
plugins: [[pluginTransformTypescript, { isTSX: false, allowDeclareFields: true }]],
|
|
4361
|
+
sourceType: 'module',
|
|
4362
|
+
});
|
|
4363
|
+
if (result?.code) {
|
|
4364
|
+
content = result.code;
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
catch (e2) {
|
|
4369
|
+
if (verbose)
|
|
4370
|
+
console.warn('[hmr-http] entry-runtime.ts transform failed', e2);
|
|
4371
|
+
}
|
|
4372
|
+
if (!content) {
|
|
4373
|
+
content = 'export default async function start(){ console.error("[/ns/entry-rt] not found"); }\n';
|
|
4374
|
+
}
|
|
3310
4375
|
}
|
|
4376
|
+
if (verbose)
|
|
4377
|
+
console.log('[hmr-http] /ns/entry-rt serving', content.length, 'bytes');
|
|
3311
4378
|
res.statusCode = 200;
|
|
3312
4379
|
res.end(content);
|
|
3313
4380
|
}
|
|
3314
4381
|
catch (e) {
|
|
4382
|
+
console.warn('[hmr-http] /ns/entry-rt error', e);
|
|
3315
4383
|
next();
|
|
3316
4384
|
}
|
|
3317
4385
|
});
|
|
@@ -3514,10 +4582,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3514
4582
|
}
|
|
3515
4583
|
if (!transformed?.code) {
|
|
3516
4584
|
if (verbose) {
|
|
3517
|
-
|
|
3518
|
-
console.warn(`[sfc][serve] transform miss for`, fullSpec);
|
|
3519
|
-
}
|
|
3520
|
-
catch { }
|
|
4585
|
+
console.warn(`[sfc][serve] transform miss for`, fullSpec);
|
|
3521
4586
|
}
|
|
3522
4587
|
// Emit an erroring module to surface the failure at import site with helpful hints
|
|
3523
4588
|
try {
|
|
@@ -3633,10 +4698,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3633
4698
|
}
|
|
3634
4699
|
catch (eTplSelf) {
|
|
3635
4700
|
if (verbose) {
|
|
3636
|
-
|
|
3637
|
-
console.warn('[sfc][template][self-compile][fail]', fullSpec, eTplSelf?.message);
|
|
3638
|
-
}
|
|
3639
|
-
catch { }
|
|
4701
|
+
console.warn('[sfc][template][self-compile][fail]', fullSpec, eTplSelf?.message);
|
|
3640
4702
|
}
|
|
3641
4703
|
code = transformed.code || 'export {}\n';
|
|
3642
4704
|
code = processTemplateVariantMinimal(code);
|
|
@@ -3741,7 +4803,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3741
4803
|
code = outCode;
|
|
3742
4804
|
}
|
|
3743
4805
|
catch { }
|
|
3744
|
-
code = processCodeForDevice(code, false);
|
|
4806
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(fullSpec), fullSpec);
|
|
3745
4807
|
// Transform static .vue imports into static imports from the assembler (no TLA) via AST
|
|
3746
4808
|
try {
|
|
3747
4809
|
const importerPath = fullSpec.replace(/[?#].*$/, '');
|
|
@@ -3803,10 +4865,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3803
4865
|
}
|
|
3804
4866
|
catch (eTsVar) {
|
|
3805
4867
|
if (verbose) {
|
|
3806
|
-
|
|
3807
|
-
console.warn('[sfc][variant:script][babel-ts][fail]', fullSpec, eTsVar?.message);
|
|
3808
|
-
}
|
|
3809
|
-
catch { }
|
|
4868
|
+
console.warn('[sfc][variant:script][babel-ts][fail]', fullSpec, eTsVar?.message);
|
|
3810
4869
|
}
|
|
3811
4870
|
}
|
|
3812
4871
|
}
|
|
@@ -3870,10 +4929,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3870
4929
|
const kind = isVariant ? `variant:${variantType || 'unknown'}` : 'full';
|
|
3871
4930
|
const sig = `// [sfc] kind=${kind} path=${importerPath} len=${code.length} default=${hasDefault} wrapped=${false}\n`;
|
|
3872
4931
|
if (verbose) {
|
|
3873
|
-
|
|
3874
|
-
console.log(`[sfc][serve] ${fullSpec} kind=${kind} default=${hasDefault} bytes=${code.length}`);
|
|
3875
|
-
}
|
|
3876
|
-
catch { }
|
|
4932
|
+
console.log(`[sfc][serve] ${fullSpec} kind=${kind} default=${hasDefault} bytes=${code.length}`);
|
|
3877
4933
|
}
|
|
3878
4934
|
// Ensure script variants always provide a default export if they declare a component
|
|
3879
4935
|
if (!hasDefault) {
|
|
@@ -3942,16 +4998,10 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3942
4998
|
// 6) Object property forms (rare in template output) render: (...) => {}
|
|
3943
4999
|
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);
|
|
3944
5000
|
if (hasRender && verbose) {
|
|
3945
|
-
|
|
3946
|
-
console.log('[sfc-meta] detected render for', base);
|
|
3947
|
-
}
|
|
3948
|
-
catch { }
|
|
5001
|
+
console.log('[sfc-meta] detected render for', base);
|
|
3949
5002
|
}
|
|
3950
5003
|
else if (!hasRender && verbose) {
|
|
3951
|
-
|
|
3952
|
-
console.warn('[sfc-meta] render NOT detected for', base);
|
|
3953
|
-
}
|
|
3954
|
-
catch { }
|
|
5004
|
+
console.warn('[sfc-meta] render NOT detected for', base);
|
|
3955
5005
|
}
|
|
3956
5006
|
const hash = createHash('md5').update(base).digest('hex').slice(0, 8);
|
|
3957
5007
|
const payload = {
|
|
@@ -4085,10 +5135,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4085
5135
|
}
|
|
4086
5136
|
catch (eScript) {
|
|
4087
5137
|
if (verbose) {
|
|
4088
|
-
|
|
4089
|
-
console.warn('[sfc-asm][compileScript] failed', base, eScript?.message);
|
|
4090
|
-
}
|
|
4091
|
-
catch { }
|
|
5138
|
+
console.warn('[sfc-asm][compileScript] failed', base, eScript?.message);
|
|
4092
5139
|
}
|
|
4093
5140
|
// Retry without inlineTemplate
|
|
4094
5141
|
try {
|
|
@@ -4104,10 +5151,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4104
5151
|
}
|
|
4105
5152
|
catch (eNoInline) {
|
|
4106
5153
|
if (verbose) {
|
|
4107
|
-
|
|
4108
|
-
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4109
|
-
}
|
|
4110
|
-
catch { }
|
|
5154
|
+
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4111
5155
|
}
|
|
4112
5156
|
}
|
|
4113
5157
|
}
|
|
@@ -4138,10 +5182,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4138
5182
|
}
|
|
4139
5183
|
catch (eNoInline) {
|
|
4140
5184
|
if (verbose) {
|
|
4141
|
-
|
|
4142
|
-
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4143
|
-
}
|
|
4144
|
-
catch { }
|
|
5185
|
+
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4145
5186
|
}
|
|
4146
5187
|
}
|
|
4147
5188
|
}
|
|
@@ -4164,20 +5205,14 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4164
5205
|
});
|
|
4165
5206
|
compiledTplCode = (ct && (ct.code || '')) || '';
|
|
4166
5207
|
if (ct?.errors?.length && verbose) {
|
|
4167
|
-
|
|
4168
|
-
console.warn('[sfc-asm][compileTemplate][errors]', base, ct.errors);
|
|
4169
|
-
}
|
|
4170
|
-
catch { }
|
|
5208
|
+
console.warn('[sfc-asm][compileTemplate][errors]', base, ct.errors);
|
|
4171
5209
|
}
|
|
4172
5210
|
}
|
|
4173
5211
|
}
|
|
4174
5212
|
catch (eTpl) {
|
|
4175
5213
|
templateErr = eTpl;
|
|
4176
5214
|
if (verbose) {
|
|
4177
|
-
|
|
4178
|
-
console.warn('[sfc-asm][compileTemplate] failed', base, eTpl?.message);
|
|
4179
|
-
}
|
|
4180
|
-
catch { }
|
|
5215
|
+
console.warn('[sfc-asm][compileTemplate] failed', base, eTpl?.message);
|
|
4181
5216
|
}
|
|
4182
5217
|
// Fallback: use the variant-transformed template code if available
|
|
4183
5218
|
try {
|
|
@@ -4240,10 +5275,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4240
5275
|
}
|
|
4241
5276
|
catch (eExtract) {
|
|
4242
5277
|
if (verbose) {
|
|
4243
|
-
|
|
4244
|
-
console.warn('[sfc-asm][extractTemplateRender] failed', base, eExtract?.message);
|
|
4245
|
-
}
|
|
4246
|
-
catch { }
|
|
5278
|
+
console.warn('[sfc-asm][extractTemplateRender] failed', base, eExtract?.message);
|
|
4247
5279
|
}
|
|
4248
5280
|
}
|
|
4249
5281
|
}
|
|
@@ -4293,11 +5325,10 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4293
5325
|
parts.push(scriptTransformed);
|
|
4294
5326
|
parts.push(renderDecl);
|
|
4295
5327
|
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){}`);
|
|
4296
|
-
parts.push(`// diagnostic: hadScriptDefaultPre=${hadScriptDefaultPre} triedInlineTemplate=${triedInlineTemplate} renderOk=${renderOk} tplBytes=${compiledTplCode.length} scriptBytes=${(compiledScript || '').length} templateErr=${templateErr ? templateErr?.message : ''}`);
|
|
4297
5328
|
parts.push(`export function render(){ const f = (typeof __ns_getRender==='function' ? __ns_getRender() : (__ns_sfc__ && __ns_sfc__.render)); return typeof f==='function' ? f.apply(this, arguments) : undefined; }`);
|
|
4298
5329
|
parts.push(`export default __ns_sfc__`);
|
|
4299
5330
|
let inlineCode = parts.filter(Boolean).join('\n');
|
|
4300
|
-
inlineCode = processCodeForDevice(inlineCode, false);
|
|
5331
|
+
inlineCode = processCodeForDevice(inlineCode, false, true);
|
|
4301
5332
|
try {
|
|
4302
5333
|
inlineCode = ensureVersionedCoreImports(inlineCode, getServerOrigin(server), Number(ver));
|
|
4303
5334
|
}
|
|
@@ -4322,10 +5353,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4322
5353
|
}
|
|
4323
5354
|
catch (eTs) {
|
|
4324
5355
|
if (verbose) {
|
|
4325
|
-
|
|
4326
|
-
console.warn('[sfc-asm][babel-ts][fail]', base, eTs?.message);
|
|
4327
|
-
}
|
|
4328
|
-
catch { }
|
|
5356
|
+
console.warn('[sfc-asm][babel-ts][fail]', base, eTs?.message);
|
|
4329
5357
|
}
|
|
4330
5358
|
}
|
|
4331
5359
|
// Hoist imports + strip residual TS via AST
|
|
@@ -4335,18 +5363,12 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4335
5363
|
importLines = astRes.imports;
|
|
4336
5364
|
scriptTransformed = astRes.body;
|
|
4337
5365
|
if (astRes.diagnostics.length && verbose) {
|
|
4338
|
-
|
|
4339
|
-
console.warn('[sfc-asm][ast]', base, astRes.diagnostics.join('; '));
|
|
4340
|
-
}
|
|
4341
|
-
catch { }
|
|
5366
|
+
console.warn('[sfc-asm][ast]', base, astRes.diagnostics.join('; '));
|
|
4342
5367
|
}
|
|
4343
5368
|
}
|
|
4344
5369
|
catch (eAst) {
|
|
4345
5370
|
if (verbose) {
|
|
4346
|
-
|
|
4347
|
-
console.warn('[sfc-asm][ast][fail]', base, eAst?.message);
|
|
4348
|
-
}
|
|
4349
|
-
catch { }
|
|
5371
|
+
console.warn('[sfc-asm][ast][fail]', base, eAst?.message);
|
|
4350
5372
|
}
|
|
4351
5373
|
}
|
|
4352
5374
|
// Ensure renderDecl ends with closing brace ONLY for function declaration forms
|
|
@@ -4372,12 +5394,11 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4372
5394
|
outParts.push(renderDecl);
|
|
4373
5395
|
}
|
|
4374
5396
|
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){}`);
|
|
4375
|
-
outParts.push(`// diagnostic: hadScriptDefaultPre=${hadScriptDefaultPre} triedInlineTemplate=${triedInlineTemplate} renderOk=${renderOk} tplBytes=${compiledTplCode.length} scriptBytes=${(compiledScript || '').length} templateErr=${templateErr ? templateErr?.message : ''}`);
|
|
4376
5397
|
// Export named render as a function that resolves lazily
|
|
4377
5398
|
outParts.push('export function render(){ const f = (typeof __ns_getRender==="function" ? __ns_getRender() : (typeof __ns_render==="function" ? __ns_render : (__ns_sfc__ && __ns_sfc__.render))); return typeof f === "function" ? f.apply(this, arguments) : undefined; }');
|
|
4378
5399
|
outParts.push('export default __ns_sfc__');
|
|
4379
5400
|
let inlineCode2 = outParts.filter(Boolean).join('\n');
|
|
4380
|
-
inlineCode2 = processCodeForDevice(inlineCode2, false);
|
|
5401
|
+
inlineCode2 = processCodeForDevice(inlineCode2, false, true);
|
|
4381
5402
|
try {
|
|
4382
5403
|
inlineCode2 = ensureVersionedCoreImports(inlineCode2, getServerOrigin(server), Number(ver));
|
|
4383
5404
|
}
|
|
@@ -4673,12 +5694,11 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4673
5694
|
}
|
|
4674
5695
|
let asm;
|
|
4675
5696
|
if (inlineOk) {
|
|
4676
|
-
const diagLine = `// diagnostic:inlineOk ver=${ver} inlineBlock=${!!(inlineBlock && inlineBlock.trim())} helperBindingsLen=${helperBindings.length} renderDeclLen=${renderDecl.length}`;
|
|
4677
5697
|
if (inlineBlock && inlineBlock.trim()) {
|
|
4678
|
-
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__
|
|
5698
|
+
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');
|
|
4679
5699
|
}
|
|
4680
5700
|
else {
|
|
4681
|
-
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__
|
|
5701
|
+
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');
|
|
4682
5702
|
}
|
|
4683
5703
|
}
|
|
4684
5704
|
else {
|
|
@@ -4689,7 +5709,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4689
5709
|
}
|
|
4690
5710
|
// Run full device processing so helper aliasing and globals are consistent in this path too
|
|
4691
5711
|
let code = REQUIRE_GUARD_SNIPPET + asm;
|
|
4692
|
-
code = processCodeForDevice(code, false);
|
|
5712
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(base), base);
|
|
4693
5713
|
try {
|
|
4694
5714
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), Number(ver));
|
|
4695
5715
|
}
|
|
@@ -4862,7 +5882,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4862
5882
|
let code = transformed.code;
|
|
4863
5883
|
// Reuse existing sanitation chain (lightweight)
|
|
4864
5884
|
code = cleanCode(code);
|
|
4865
|
-
code = processCodeForDevice(code, false);
|
|
5885
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
|
|
4866
5886
|
try {
|
|
4867
5887
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
|
|
4868
5888
|
}
|
|
@@ -4917,7 +5937,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4917
5937
|
if (depTrans?.code && depResolved) {
|
|
4918
5938
|
let depCode = depTrans.code;
|
|
4919
5939
|
depCode = cleanCode(depCode);
|
|
4920
|
-
depCode = processCodeForDevice(depCode, false);
|
|
5940
|
+
depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
|
|
4921
5941
|
try {
|
|
4922
5942
|
depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
|
|
4923
5943
|
}
|
|
@@ -4950,8 +5970,8 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4950
5970
|
ts: Date.now(),
|
|
4951
5971
|
delta: true,
|
|
4952
5972
|
};
|
|
4953
|
-
wss
|
|
4954
|
-
if (c
|
|
5973
|
+
wss?.clients.forEach((c) => {
|
|
5974
|
+
if (isSocketClientOpen(c)) {
|
|
4955
5975
|
try {
|
|
4956
5976
|
c.send(JSON.stringify(single));
|
|
4957
5977
|
}
|
|
@@ -4990,32 +6010,33 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4990
6010
|
if (verbose)
|
|
4991
6011
|
console.warn('[hmr-ws][graph] initial population failed', e);
|
|
4992
6012
|
}
|
|
4993
|
-
// Send SFC registry on
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
}
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
6013
|
+
// Send SFC registry on every connection (not just the first).
|
|
6014
|
+
// When the NativeScript app restarts (e.g. CLI auto-reload), the new
|
|
6015
|
+
// JS context has an empty sfcArtifactMap. Without the registry the
|
|
6016
|
+
// rescue-mount cannot find the root .vue component.
|
|
6017
|
+
try {
|
|
6018
|
+
await ACTIVE_STRATEGY.buildRegistry({
|
|
6019
|
+
server,
|
|
6020
|
+
sfcFileMap,
|
|
6021
|
+
depFileMap,
|
|
6022
|
+
wss: wss,
|
|
6023
|
+
verbose,
|
|
6024
|
+
helpers: {
|
|
6025
|
+
cleanCode,
|
|
6026
|
+
collectImportDependencies,
|
|
6027
|
+
isCoreGlobalsReference,
|
|
6028
|
+
isNativeScriptCoreModule,
|
|
6029
|
+
isNativeScriptPluginModule,
|
|
6030
|
+
resolveVendorFromCandidate,
|
|
6031
|
+
createHash: (value) => createHash('md5').update(value).digest('hex'),
|
|
6032
|
+
rewriteImports,
|
|
6033
|
+
processSfcCode,
|
|
6034
|
+
},
|
|
6035
|
+
});
|
|
6036
|
+
registrySent = true;
|
|
6037
|
+
}
|
|
6038
|
+
catch (error) {
|
|
6039
|
+
console.warn('[hmr-ws] Failed to send registry:', error);
|
|
5019
6040
|
}
|
|
5020
6041
|
emitFullGraph(ws);
|
|
5021
6042
|
// After sending registry & graph also send current module manifest if any
|
|
@@ -5038,16 +6059,134 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5038
6059
|
if (!wss) {
|
|
5039
6060
|
return;
|
|
5040
6061
|
}
|
|
6062
|
+
if (isRuntimeGraphExcludedPath(file)) {
|
|
6063
|
+
return;
|
|
6064
|
+
}
|
|
6065
|
+
// Always-on update timing. Captures the four phases (await,
|
|
6066
|
+
// framework, broadcast, total) plus invalidated module count
|
|
6067
|
+
// and recipient count. Emitted at the end of this function via
|
|
6068
|
+
// `emitHmrUpdateSummary()`. Single line, always-on so a
|
|
6069
|
+
// 6-second `.ts` save is immediately visible without flipping
|
|
6070
|
+
// verbose.
|
|
6071
|
+
const updateRoot = server.config.root || process.cwd();
|
|
6072
|
+
const updateRel = (() => {
|
|
6073
|
+
try {
|
|
6074
|
+
return '/' + path.posix.normalize(path.relative(updateRoot, file)).split(path.sep).join('/');
|
|
6075
|
+
}
|
|
6076
|
+
catch {
|
|
6077
|
+
return file;
|
|
6078
|
+
}
|
|
6079
|
+
})();
|
|
6080
|
+
const updateMetrics = {
|
|
6081
|
+
file: updateRel,
|
|
6082
|
+
kind: classifyHmrUpdateKind(file),
|
|
6083
|
+
t0: Date.now(),
|
|
6084
|
+
tAfterAwait: 0,
|
|
6085
|
+
tAfterFramework: 0,
|
|
6086
|
+
tEnd: 0,
|
|
6087
|
+
invalidated: 0,
|
|
6088
|
+
recipients: 0,
|
|
6089
|
+
// Narrowing diagnostic — populated by the angular branch when
|
|
6090
|
+
// the changed file is `.ts`, otherwise remains undefined and is
|
|
6091
|
+
// omitted from the summary line entirely.
|
|
6092
|
+
narrowed: undefined,
|
|
6093
|
+
emitted: false,
|
|
6094
|
+
};
|
|
6095
|
+
// Broadcast a "pending" notification at the very start of
|
|
6096
|
+
// handleHotUpdate so the client can show the HMR-applying
|
|
6097
|
+
// overlay BEFORE we spend time on graph updates / transforms /
|
|
6098
|
+
// dependency analysis (typically 7–200ms on a warm cache).
|
|
6099
|
+
// Without this, the overlay only appears at `ns:angular-update`
|
|
6100
|
+
// broadcast time and the user perceives a "delayed" reaction
|
|
6101
|
+
// to their save.
|
|
6102
|
+
//
|
|
6103
|
+
// Fire-and-forget: a failed pending broadcast must never
|
|
6104
|
+
// hold up the actual update. The client treats receipt of
|
|
6105
|
+
// `ns:angular-update` (or `ns:css-updates`) as authoritative;
|
|
6106
|
+
// the pending message is purely a UX hint.
|
|
6107
|
+
try {
|
|
6108
|
+
const pendingPayload = JSON.stringify(createHmrPendingMessage({
|
|
6109
|
+
origin: getServerOrigin(server),
|
|
6110
|
+
path: updateMetrics.file,
|
|
6111
|
+
kind: updateMetrics.kind,
|
|
6112
|
+
timestamp: updateMetrics.t0,
|
|
6113
|
+
}));
|
|
6114
|
+
wss.clients.forEach((client) => {
|
|
6115
|
+
if (isSocketClientOpen(client)) {
|
|
6116
|
+
try {
|
|
6117
|
+
client.send(pendingPayload);
|
|
6118
|
+
}
|
|
6119
|
+
catch { }
|
|
6120
|
+
}
|
|
6121
|
+
});
|
|
6122
|
+
}
|
|
6123
|
+
catch { }
|
|
6124
|
+
const emitHmrUpdateSummary = () => {
|
|
6125
|
+
if (updateMetrics.emitted)
|
|
6126
|
+
return;
|
|
6127
|
+
updateMetrics.emitted = true;
|
|
6128
|
+
updateMetrics.tEnd = Date.now();
|
|
6129
|
+
try {
|
|
6130
|
+
const awaitMs = (updateMetrics.tAfterAwait || updateMetrics.t0) - updateMetrics.t0;
|
|
6131
|
+
const frameworkMs = (updateMetrics.tAfterFramework || updateMetrics.tAfterAwait || updateMetrics.t0) - (updateMetrics.tAfterAwait || updateMetrics.t0);
|
|
6132
|
+
const broadcastMs = updateMetrics.tEnd - (updateMetrics.tAfterFramework || updateMetrics.tAfterAwait || updateMetrics.t0);
|
|
6133
|
+
const totalMs = updateMetrics.tEnd - updateMetrics.t0;
|
|
6134
|
+
console.info(formatHmrUpdateSummary({
|
|
6135
|
+
file: updateMetrics.file,
|
|
6136
|
+
kind: updateMetrics.kind,
|
|
6137
|
+
awaitMs,
|
|
6138
|
+
frameworkMs,
|
|
6139
|
+
broadcastMs,
|
|
6140
|
+
totalMs,
|
|
6141
|
+
invalidated: updateMetrics.invalidated,
|
|
6142
|
+
recipients: updateMetrics.recipients,
|
|
6143
|
+
narrowed: updateMetrics.narrowed,
|
|
6144
|
+
}));
|
|
6145
|
+
}
|
|
6146
|
+
catch { }
|
|
6147
|
+
};
|
|
6148
|
+
// The first /ns/m request kicks off populateInitialGraph in the
|
|
6149
|
+
// background. If an HMR update races in before that walk
|
|
6150
|
+
// completes, we'd lose transitive-importer data. Await
|
|
6151
|
+
// completion here so the delta computation below always sees a
|
|
6152
|
+
// populated graph.
|
|
6153
|
+
if (graphInitialPopulationPromise) {
|
|
6154
|
+
try {
|
|
6155
|
+
await graphInitialPopulationPromise;
|
|
6156
|
+
}
|
|
6157
|
+
catch { }
|
|
6158
|
+
}
|
|
6159
|
+
updateMetrics.tAfterAwait = Date.now();
|
|
5041
6160
|
// Graph update for this file change (wrapped to avoid aborting rest of handler)
|
|
5042
6161
|
try {
|
|
5043
|
-
const
|
|
5044
|
-
if (
|
|
5045
|
-
const
|
|
5046
|
-
|
|
5047
|
-
.
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
6162
|
+
const skipAngularHtmlGraphUpdate = ACTIVE_STRATEGY.flavor === 'angular' && /\.(html|htm)$/i.test(file);
|
|
6163
|
+
if (!skipAngularHtmlGraphUpdate) {
|
|
6164
|
+
const graphTargets = collectGraphUpdateModulesForHotUpdate({
|
|
6165
|
+
file,
|
|
6166
|
+
flavor: ACTIVE_STRATEGY.flavor,
|
|
6167
|
+
modules: ctx.modules,
|
|
6168
|
+
getModuleById: (id) => server.moduleGraph.getModuleById(id),
|
|
6169
|
+
verbose,
|
|
6170
|
+
});
|
|
6171
|
+
for (const mod of graphTargets) {
|
|
6172
|
+
if (!mod?.id)
|
|
6173
|
+
continue;
|
|
6174
|
+
try {
|
|
6175
|
+
const deps = Array.from(mod.importedModules || [])
|
|
6176
|
+
.map((m) => (m.id || '').replace(/\?.*$/, ''))
|
|
6177
|
+
.filter(Boolean);
|
|
6178
|
+
const transformed = await server.transformRequest(mod.id);
|
|
6179
|
+
const code = transformed?.code || '';
|
|
6180
|
+
upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps, {
|
|
6181
|
+
emitDeltaOnInsert: true,
|
|
6182
|
+
broadcastDelta: ACTIVE_STRATEGY.flavor !== 'angular',
|
|
6183
|
+
});
|
|
6184
|
+
}
|
|
6185
|
+
catch (error) {
|
|
6186
|
+
if (verbose)
|
|
6187
|
+
console.warn('[hmr-ws][v2] failed graph update target', mod.id, error);
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
5051
6190
|
}
|
|
5052
6191
|
}
|
|
5053
6192
|
catch (e) {
|
|
@@ -5068,6 +6207,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5068
6207
|
console.log(`[hmr-ws] Hot update for: ${file}`);
|
|
5069
6208
|
// Handle CSS updates
|
|
5070
6209
|
if (file.endsWith('.css')) {
|
|
6210
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
5071
6211
|
try {
|
|
5072
6212
|
let rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
5073
6213
|
const origin = getServerOrigin(server);
|
|
@@ -5084,14 +6224,16 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5084
6224
|
],
|
|
5085
6225
|
};
|
|
5086
6226
|
wss.clients.forEach((client) => {
|
|
5087
|
-
if (client
|
|
6227
|
+
if (isSocketClientOpen(client)) {
|
|
5088
6228
|
client.send(JSON.stringify(msg));
|
|
6229
|
+
updateMetrics.recipients += 1;
|
|
5089
6230
|
}
|
|
5090
6231
|
});
|
|
5091
6232
|
}
|
|
5092
6233
|
catch (error) {
|
|
5093
6234
|
console.warn('[hmr-ws] CSS update failed:', error);
|
|
5094
6235
|
}
|
|
6236
|
+
emitHmrUpdateSummary();
|
|
5095
6237
|
return;
|
|
5096
6238
|
}
|
|
5097
6239
|
// Framework-specific hot update handling
|
|
@@ -5099,31 +6241,281 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5099
6241
|
// For Angular, react to component TS or external template HTML changes under /src
|
|
5100
6242
|
const isHtml = file.endsWith('.html');
|
|
5101
6243
|
const isTs = file.endsWith('.ts');
|
|
6244
|
+
const angularHotUpdateRoots = collectAngularHotUpdateRoots({
|
|
6245
|
+
file,
|
|
6246
|
+
modules: ctx.modules,
|
|
6247
|
+
getModuleById: (id) => server.moduleGraph.getModuleById(id),
|
|
6248
|
+
getModulesByFile: (targetFile) => server.moduleGraph.getModulesByFile?.(targetFile),
|
|
6249
|
+
});
|
|
6250
|
+
if (verbose) {
|
|
6251
|
+
console.info(`[ns-hmr-diag][server] hot-update file=${file} isHtml=${isHtml} isTs=${isTs} ctxModules=${Array.from(ctx.modules || []).length} hotUpdateRoots=${angularHotUpdateRoots.length} (${angularHotUpdateRoots
|
|
6252
|
+
.map((m) => m?.id ?? '(none)')
|
|
6253
|
+
.slice(0, 8)
|
|
6254
|
+
.join(', ')}${angularHotUpdateRoots.length > 8 ? ', …' : ''})`);
|
|
6255
|
+
}
|
|
5102
6256
|
if (!(isHtml || isTs))
|
|
5103
6257
|
return;
|
|
6258
|
+
updateMetrics.invalidated += angularHotUpdateRoots.length;
|
|
6259
|
+
if (angularHotUpdateRoots.length) {
|
|
6260
|
+
for (const mod of angularHotUpdateRoots) {
|
|
6261
|
+
try {
|
|
6262
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6263
|
+
}
|
|
6264
|
+
catch (invalidationError) {
|
|
6265
|
+
if (verbose) {
|
|
6266
|
+
console.warn('[hmr-ws][angular] hot-update root invalidation failed', mod?.id, invalidationError);
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
if (verbose) {
|
|
6271
|
+
console.log('[hmr-ws][angular] invalidated hot-update root modules:', angularHotUpdateRoots.length);
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
const angularTransitiveInvalidationRoots = (angularHotUpdateRoots.length ? angularHotUpdateRoots : ctx.modules);
|
|
6275
|
+
// Read the source for `.ts/.tsx/.js/.jsx` edits so
|
|
6276
|
+
// `shouldInvalidateAngularTransitiveImporters` can
|
|
6277
|
+
// distinguish leaf modules (constants/utils) from real
|
|
6278
|
+
// Angular files. If `ctx.read()` throws (file deleted, race
|
|
6279
|
+
// against the watcher), `angularChangedSource` stays
|
|
6280
|
+
// undefined and we fall back to the conservative "always
|
|
6281
|
+
// invalidate transitively" behavior.
|
|
6282
|
+
let angularChangedSource;
|
|
6283
|
+
if (isTs) {
|
|
6284
|
+
try {
|
|
6285
|
+
angularChangedSource = await ctx.read();
|
|
6286
|
+
}
|
|
6287
|
+
catch {
|
|
6288
|
+
angularChangedSource = undefined;
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
const angularNeedsTransitive = shouldInvalidateAngularTransitiveImporters({
|
|
6292
|
+
flavor: ACTIVE_STRATEGY.flavor,
|
|
6293
|
+
file,
|
|
6294
|
+
source: angularChangedSource,
|
|
6295
|
+
});
|
|
6296
|
+
// Surface the narrowing decision on every `.ts` Angular hot
|
|
6297
|
+
// update (HTML routes always invalidate transitively and
|
|
6298
|
+
// aren't subject to narrowing, so we leave them as
|
|
6299
|
+
// `undefined` — the field is omitted from the summary line).
|
|
6300
|
+
// The boolean is the inverse of `angularNeedsTransitive`
|
|
6301
|
+
// because "needs transitive" is the broad (un-narrowed)
|
|
6302
|
+
// behavior.
|
|
6303
|
+
if (isTs) {
|
|
6304
|
+
updateMetrics.narrowed = !angularNeedsTransitive;
|
|
6305
|
+
}
|
|
6306
|
+
// Stable URL + Explicit Invalidation:
|
|
6307
|
+
//
|
|
6308
|
+
// Compute the transitive importer closure ONCE here and reuse
|
|
6309
|
+
// it for (a) `server.moduleGraph.invalidateModule` (so Vite's
|
|
6310
|
+
// transform pipeline re-runs on next request), (b) the shared
|
|
6311
|
+
// transform-request cache, and (c) the runtime eviction set
|
|
6312
|
+
// we broadcast in `ns:angular-update`. Consolidating this
|
|
6313
|
+
// removes a redundant graph walk and guarantees the three
|
|
6314
|
+
// consumers see the exact same set of importers (otherwise a
|
|
6315
|
+
// late module-graph mutation between calls could leave an
|
|
6316
|
+
// asymmetric narrowed/broad mix).
|
|
6317
|
+
//
|
|
6318
|
+
// We separate Vite-transform narrowing from runtime eviction:
|
|
6319
|
+
// `angularNeedsTransitive` answers the question "does the
|
|
6320
|
+
// changed file's symbol shape change such that importers
|
|
6321
|
+
// must be re-transformed by Vite?". The runtime, however,
|
|
6322
|
+
// has a stricter requirement: ESM live bindings only refresh
|
|
6323
|
+
// if the importing module re-evaluates inside V8. A
|
|
6324
|
+
// constants file with no Angular decorator does NOT need a
|
|
6325
|
+
// Vite re-transform of its importers (their compiled JS is
|
|
6326
|
+
// identical), but its importers still hold stale bindings to
|
|
6327
|
+
// the OLD constants Module record. After eviction + re-import
|
|
6328
|
+
// of `main.ts`, V8 sees the cached importers, returns them
|
|
6329
|
+
// unchanged, and they continue to read the OLD values. The
|
|
6330
|
+
// user-visible symptom: HMR completes successfully, logs are
|
|
6331
|
+
// clean, but the simulator does not reflect the change.
|
|
6332
|
+
//
|
|
6333
|
+
// The fix: ALWAYS compute the transitive importer closure
|
|
6334
|
+
// for runtime eviction. Only skip Vite's
|
|
6335
|
+
// `moduleGraph.invalidate` + transform-cache purge when
|
|
6336
|
+
// `angularNeedsTransitive` is false — those are the genuine
|
|
6337
|
+
// narrowing wins (saves re-transform work on the server).
|
|
6338
|
+
// The eviction set always includes importers so V8 re-fetches
|
|
6339
|
+
// and re-binds them.
|
|
6340
|
+
if (verbose) {
|
|
6341
|
+
console.info(`[ns-hmr-diag][server] angularNeedsTransitive=${angularNeedsTransitive} (file=${path.basename(file)})`);
|
|
6342
|
+
}
|
|
6343
|
+
let transitiveImporters = [];
|
|
6344
|
+
try {
|
|
6345
|
+
transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
6346
|
+
modules: angularTransitiveInvalidationRoots,
|
|
6347
|
+
isExcluded: (id) => id.includes('/node_modules/'),
|
|
6348
|
+
maxDepth: 16,
|
|
6349
|
+
});
|
|
6350
|
+
if (verbose) {
|
|
6351
|
+
console.info(`[ns-hmr-diag][server] transitiveImporters count=${transitiveImporters.length} firstN=`, transitiveImporters.slice(0, 16).map((m) => m?.id ?? '(none)'));
|
|
6352
|
+
}
|
|
6353
|
+
if (angularNeedsTransitive) {
|
|
6354
|
+
updateMetrics.invalidated += transitiveImporters.length;
|
|
6355
|
+
for (const mod of transitiveImporters) {
|
|
6356
|
+
try {
|
|
6357
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6358
|
+
}
|
|
6359
|
+
catch (invalidationError) {
|
|
6360
|
+
if (verbose) {
|
|
6361
|
+
console.warn('[hmr-ws][angular] transitive importer invalidation failed', mod?.id, invalidationError);
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
}
|
|
6365
|
+
if (verbose && transitiveImporters.length) {
|
|
6366
|
+
console.log('[hmr-ws][angular] invalidated transitive importers:', transitiveImporters.length);
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
else if (isTs && typeof angularChangedSource === 'string') {
|
|
6370
|
+
// Surfacing this log unconditionally lets the user
|
|
6371
|
+
// immediately confirm whether narrowing fired for a
|
|
6372
|
+
// given `.ts` edit (the summary line below still
|
|
6373
|
+
// emits `narrowed=yes`/`no`, but having both makes
|
|
6374
|
+
// the decision easier to spot in noisy logs and lets
|
|
6375
|
+
// the user diff scenarios without flipping
|
|
6376
|
+
// `NS_HMR_VERBOSE=true`).
|
|
6377
|
+
//
|
|
6378
|
+
// Narrowing means "skip Vite re-transform" (the
|
|
6379
|
+
// importers still get evicted from the V8 module
|
|
6380
|
+
// registry so live bindings refresh). The importer
|
|
6381
|
+
// count is appended so the distinction is visible.
|
|
6382
|
+
console.log(`[hmr-ws][angular] narrowed transitive invalidation (no @Component/@Directive/@Pipe/@Injectable/@NgModule): ${updateRel} — Vite transform skipped, runtime eviction includes ${transitiveImporters.length} importer(s)`);
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6385
|
+
catch (error) {
|
|
6386
|
+
if (verbose)
|
|
6387
|
+
console.warn('[hmr-ws][angular] transitive importer collection failed', error);
|
|
6388
|
+
}
|
|
6389
|
+
try {
|
|
6390
|
+
// Purge shared transform cache for the changed file +
|
|
6391
|
+
// hot-update roots unconditionally (their transform
|
|
6392
|
+
// output IS different now). Transitive importers are
|
|
6393
|
+
// only purged when narrowing decides their output may
|
|
6394
|
+
// have changed; otherwise their cached transforms are
|
|
6395
|
+
// still valid (compiled JS is identical even though the
|
|
6396
|
+
// runtime must re-evaluate them to refresh ESM bindings).
|
|
6397
|
+
const transformCacheInvalidationUrls = new Set(collectAngularTransformCacheInvalidationUrls({
|
|
6398
|
+
file,
|
|
6399
|
+
isTs,
|
|
6400
|
+
hotUpdateRoots: angularHotUpdateRoots,
|
|
6401
|
+
transitiveImporters: angularNeedsTransitive ? transitiveImporters : [],
|
|
6402
|
+
projectRoot: server.config.root || process.cwd(),
|
|
6403
|
+
}));
|
|
6404
|
+
if (transformCacheInvalidationUrls.size) {
|
|
6405
|
+
sharedTransformRequest.invalidateMany(transformCacheInvalidationUrls);
|
|
6406
|
+
if (verbose) {
|
|
6407
|
+
console.log('[hmr-ws][angular] purged shared transform cache entries:', transformCacheInvalidationUrls.size);
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
6411
|
+
catch (error) {
|
|
6412
|
+
if (verbose)
|
|
6413
|
+
console.warn('[hmr-ws][angular] shared transform cache purge failed', error);
|
|
6414
|
+
}
|
|
6415
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
5104
6416
|
try {
|
|
5105
6417
|
const root = server.config.root || process.cwd();
|
|
5106
6418
|
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6419
|
+
rememberAngularReloadSuppression(root, file);
|
|
5107
6420
|
const origin = getServerOrigin(server);
|
|
6421
|
+
const bootstrapEntryRel = getBootstrapEntryRelPath();
|
|
6422
|
+
// Stable URL + Explicit Invalidation:
|
|
6423
|
+
//
|
|
6424
|
+
// `evictPaths` is the canonical list of `/ns/m/<rel>` URLs
|
|
6425
|
+
// the runtime must drop from `g_moduleRegistry` before
|
|
6426
|
+
// re-importing `importerEntry`. Older versions of the
|
|
6427
|
+
// server signaled invalidation by bumping a global
|
|
6428
|
+
// `graphVersion` counter and embedding it in every URL —
|
|
6429
|
+
// but V8 keys the module registry by full URL, so a v1 →
|
|
6430
|
+
// v2 bump effectively flushed the entire dependency
|
|
6431
|
+
// graph from the cache and forced the runtime to
|
|
6432
|
+
// re-fetch + re-eval every transitively-imported module
|
|
6433
|
+
// on each save (~3s HMR cycles, dominated by Vite's
|
|
6434
|
+
// single-threaded transform pipeline). The new model:
|
|
6435
|
+
//
|
|
6436
|
+
// 1. URLs are stable: `/ns/m/<rel>` everywhere, no `vN`.
|
|
6437
|
+
// 2. The server walks the inverse-dependency closure and
|
|
6438
|
+
// sends only the modules that actually need to be
|
|
6439
|
+
// re-evaluated (typically O(1) for component edits,
|
|
6440
|
+
// or the changed file + entry for narrowed edits).
|
|
6441
|
+
// 3. The client calls `__nsInvalidateModules(evictPaths)`
|
|
6442
|
+
// and re-imports `importerEntry`, which causes V8 to
|
|
6443
|
+
// refetch ONLY those modules. Everything else stays
|
|
6444
|
+
// hot in the registry.
|
|
6445
|
+
//
|
|
6446
|
+
// Invariants enforced by `collectAngularEvictionUrls`:
|
|
6447
|
+
// - Always includes the changed file (so the new source
|
|
6448
|
+
// is fetched).
|
|
6449
|
+
// - Always includes `importerEntry` (so re-import
|
|
6450
|
+
// re-evaluates).
|
|
6451
|
+
// - Excludes node_modules (vendor packages are stable).
|
|
6452
|
+
// - Excludes virtual / runtime-graph-excluded ids.
|
|
6453
|
+
// - Origin-prefixed: `http://host:port/ns/m/<rel>`.
|
|
6454
|
+
let evictPaths = [];
|
|
6455
|
+
try {
|
|
6456
|
+
evictPaths = collectAngularEvictionUrls({
|
|
6457
|
+
file,
|
|
6458
|
+
hotUpdateRoots: angularHotUpdateRoots,
|
|
6459
|
+
transitiveImporters,
|
|
6460
|
+
projectRoot: root,
|
|
6461
|
+
origin,
|
|
6462
|
+
bootstrapEntry: bootstrapEntryRel,
|
|
6463
|
+
});
|
|
6464
|
+
}
|
|
6465
|
+
catch (error) {
|
|
6466
|
+
if (verbose) {
|
|
6467
|
+
console.warn('[ns-hmr-diag][server] eviction set computation failed', error);
|
|
6468
|
+
}
|
|
6469
|
+
}
|
|
6470
|
+
if (verbose) {
|
|
6471
|
+
try {
|
|
6472
|
+
const tsRel = rel.replace(/\.(html|htm)$/i, '.ts');
|
|
6473
|
+
const jsRel = rel.replace(/\.(html|htm)$/i, '.js');
|
|
6474
|
+
const containsRelatedTs = evictPaths.some((u) => u.endsWith(tsRel));
|
|
6475
|
+
const containsRelatedJs = evictPaths.some((u) => u.endsWith(jsRel));
|
|
6476
|
+
const sample = evictPaths.slice(0, 32);
|
|
6477
|
+
console.info(`[ns-hmr-diag][server] evict-set count=${evictPaths.length} importerEntry=${bootstrapEntryRel ?? '(none)'} containsRelatedTs=${containsRelatedTs} containsRelatedJs=${containsRelatedJs} firstN=`, sample);
|
|
6478
|
+
if (evictPaths.length > sample.length) {
|
|
6479
|
+
console.info(`[ns-hmr-diag][server] evict-set hidden=${evictPaths.length - sample.length} (showed first ${sample.length})`);
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
catch { }
|
|
6483
|
+
}
|
|
5108
6484
|
const msg = {
|
|
5109
6485
|
type: 'ns:angular-update',
|
|
5110
6486
|
origin,
|
|
5111
6487
|
path: rel,
|
|
6488
|
+
version: graphVersion,
|
|
5112
6489
|
timestamp: Date.now(),
|
|
6490
|
+
evictPaths,
|
|
6491
|
+
importerEntry: bootstrapEntryRel,
|
|
5113
6492
|
};
|
|
6493
|
+
if (verbose) {
|
|
6494
|
+
console.log('[hmr-ws][angular] broadcasting update', Array.from(wss.clients || []).map((client) => ({
|
|
6495
|
+
role: getHmrSocketRole(client),
|
|
6496
|
+
readyState: client.readyState,
|
|
6497
|
+
openState: client.OPEN,
|
|
6498
|
+
})));
|
|
6499
|
+
}
|
|
5114
6500
|
wss.clients.forEach((client) => {
|
|
5115
|
-
if (client
|
|
6501
|
+
if (isSocketClientOpen(client)) {
|
|
5116
6502
|
client.send(JSON.stringify(msg));
|
|
6503
|
+
updateMetrics.recipients += 1;
|
|
5117
6504
|
}
|
|
5118
6505
|
});
|
|
5119
6506
|
}
|
|
5120
6507
|
catch (error) {
|
|
5121
6508
|
console.warn('[hmr-ws][angular] update failed:', error);
|
|
5122
6509
|
}
|
|
6510
|
+
emitHmrUpdateSummary();
|
|
6511
|
+
if (shouldSuppressDefaultViteHotUpdate({ flavor: ACTIVE_STRATEGY.flavor, file })) {
|
|
6512
|
+
return [];
|
|
6513
|
+
}
|
|
5123
6514
|
return;
|
|
5124
6515
|
}
|
|
5125
6516
|
// TypeScript flavor: emit generic graph delta for app XML/TS/style changes
|
|
5126
6517
|
if (ACTIVE_STRATEGY.flavor === 'typescript') {
|
|
6518
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
5127
6519
|
try {
|
|
5128
6520
|
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
5129
6521
|
if (verbose)
|
|
@@ -5131,15 +6523,52 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5131
6523
|
// Treat the changed file itself as a graph module with no deps. We only
|
|
5132
6524
|
// care that its hash/identity changes so the client sees a delta and can
|
|
5133
6525
|
// perform a TS root reset. Code is not used for execution here.
|
|
5134
|
-
upsertGraphModule(rel, '', []);
|
|
5135
|
-
const gm = graph.get(normalizeGraphId(rel));
|
|
5136
|
-
if (gm)
|
|
5137
|
-
emitDelta([gm], []);
|
|
6526
|
+
upsertGraphModule(rel, '', [], { emitDeltaOnInsert: true });
|
|
5138
6527
|
}
|
|
5139
6528
|
catch (e) {
|
|
5140
6529
|
if (verbose)
|
|
5141
6530
|
console.warn('[hmr-ws][ts] failed to emit delta for', file, e);
|
|
5142
6531
|
}
|
|
6532
|
+
emitHmrUpdateSummary();
|
|
6533
|
+
return;
|
|
6534
|
+
}
|
|
6535
|
+
// Solid flavor: emit graph delta for app TSX/TS/JSX file changes.
|
|
6536
|
+
// The common graph-update block above (moduleGraph lookup) may have
|
|
6537
|
+
// already emitted a delta if the file was in Vite's module graph.
|
|
6538
|
+
// This handler ensures a delta is emitted even if the module wasn't
|
|
6539
|
+
// found (e.g. new file, or moduleGraph mismatch), and provides
|
|
6540
|
+
// Solid-specific logging. The client-side processQueue handles
|
|
6541
|
+
// propagation from non-component .ts files to .tsx component boundaries.
|
|
6542
|
+
if (ACTIVE_STRATEGY.flavor === 'solid') {
|
|
6543
|
+
const isSolidFile = /\.(tsx?|jsx?)$/i.test(file);
|
|
6544
|
+
if (!isSolidFile)
|
|
6545
|
+
return;
|
|
6546
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
6547
|
+
try {
|
|
6548
|
+
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6549
|
+
if (verbose)
|
|
6550
|
+
console.log('[hmr-ws][solid] app file hot update', { file, rel });
|
|
6551
|
+
// If the common block already upserted (hash changed), this will
|
|
6552
|
+
// detect unchanged hash and no-op. If the common block missed it
|
|
6553
|
+
// (module not in Vite's graph), this forces the delta emission.
|
|
6554
|
+
const normalizedId = normalizeGraphId(rel);
|
|
6555
|
+
const existing = graph.get(normalizedId);
|
|
6556
|
+
if (!existing) {
|
|
6557
|
+
// Module not in graph yet — force upsert with timestamp-based
|
|
6558
|
+
// hash so the client sees a change.
|
|
6559
|
+
upsertGraphModule(rel, `/* solid-hmr ${Date.now()} */`, [], { emitDeltaOnInsert: true });
|
|
6560
|
+
}
|
|
6561
|
+
// Log what we're sending so devs can trace the flow on the server side.
|
|
6562
|
+
if (verbose) {
|
|
6563
|
+
const gm = graph.get(normalizedId);
|
|
6564
|
+
console.log('[hmr-ws][solid] delta module', { id: gm?.id, hash: gm?.hash });
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
catch (e) {
|
|
6568
|
+
if (verbose)
|
|
6569
|
+
console.warn('[hmr-ws][solid] failed to handle hot update for', file, e);
|
|
6570
|
+
}
|
|
6571
|
+
emitHmrUpdateSummary();
|
|
5143
6572
|
return;
|
|
5144
6573
|
}
|
|
5145
6574
|
// Handle .vue file updates
|
|
@@ -5148,7 +6577,8 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5148
6577
|
console.log('[hmr-ws] Not a .vue file, skipping');
|
|
5149
6578
|
return;
|
|
5150
6579
|
}
|
|
5151
|
-
|
|
6580
|
+
if (verbose)
|
|
6581
|
+
console.log('[hmr-ws] Processing .vue file update...');
|
|
5152
6582
|
try {
|
|
5153
6583
|
const root = server.config.root || process.cwd();
|
|
5154
6584
|
let rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
@@ -5249,6 +6679,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5249
6679
|
// Rewrite ONLY .vue imports (everything else is now inlined)
|
|
5250
6680
|
const projectRoot = server.config.root || process.cwd();
|
|
5251
6681
|
code = rewriteImports(code, rel, sfcFileMap, depFileMap, projectRoot, opts.verbose, undefined);
|
|
6682
|
+
upsertGraphModule(rel, code, [...deps, ...vueDeps]);
|
|
5252
6683
|
// Add HMR runtime prelude (CRITICAL for runtime)
|
|
5253
6684
|
const hmrPrelude = `
|
|
5254
6685
|
// Embedded HMR Runtime for NativeScript runtime
|
|
@@ -5291,7 +6722,7 @@ if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
|
5291
6722
|
if (typeof spec === 'string' && /^(?:https?:)\/\//.test(spec)) {
|
|
5292
6723
|
const err = new Error('[ns-hmr][require-guard] require of URL: ' + spec + ' via ' + label);
|
|
5293
6724
|
const stack = err.stack || '';
|
|
5294
|
-
|
|
6725
|
+
console.error(err.message + '\n' + stack);
|
|
5295
6726
|
try { g.__NS_REQUIRE_GUARD_LAST__ = { spec, stack, label, ts: Date.now() }; } catch {}
|
|
5296
6727
|
}
|
|
5297
6728
|
} catch {}
|
|
@@ -5324,7 +6755,7 @@ if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
|
5324
6755
|
version: graphVersion,
|
|
5325
6756
|
};
|
|
5326
6757
|
wss.clients.forEach((client) => {
|
|
5327
|
-
if (client
|
|
6758
|
+
if (isSocketClientOpen(client)) {
|
|
5328
6759
|
client.send(JSON.stringify(registryUpdateMsg));
|
|
5329
6760
|
}
|
|
5330
6761
|
});
|
|
@@ -5424,6 +6855,10 @@ if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
|
5424
6855
|
console.warn('[hmr-ws] HMR update failed:', error);
|
|
5425
6856
|
console.error(error);
|
|
5426
6857
|
}
|
|
6858
|
+
// Vue path emits update summary at the end of the function so
|
|
6859
|
+
// every framework branch gets exactly one log line. Idempotent
|
|
6860
|
+
// — if any branch already emitted, this is a no-op.
|
|
6861
|
+
emitHmrUpdateSummary();
|
|
5427
6862
|
// CRITICAL: Return empty array to prevent Vite's default HMR
|
|
5428
6863
|
return [];
|
|
5429
6864
|
},
|
|
@@ -5495,4 +6930,6 @@ function getServerOrigin(server) {
|
|
|
5495
6930
|
// Test-only export: allow unit tests to run the sanitizer on snippets without booting a server
|
|
5496
6931
|
// Safe in production builds; this is a named export that tests can import explicitly.
|
|
5497
6932
|
export const __test_processCodeForDevice = processCodeForDevice;
|
|
6933
|
+
export const __test_resolveVendorRouting = resolveVendorRouting;
|
|
6934
|
+
export const __test_getBlockedDeviceNodeModulesReason = getBlockedDeviceNodeModulesReason;
|
|
5498
6935
|
//# sourceMappingURL=websocket.js.map
|