@nativescript/vite 8.0.0-alpha.1 → 8.0.0-alpha.11
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 +27 -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 +375 -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 +767 -24
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.d.ts +5 -0
- package/hmr/client/utils.js +283 -12
- 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 +2492 -798
- 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
193
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
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+/, '')}`;
|
|
83
217
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
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');
|
|
86
226
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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;
|
|
114
|
-
}
|
|
115
|
-
export function ensureNativeScriptModuleBindings(code) {
|
|
116
|
-
// Proceed even if a vendor manifest isn't available; we'll still vendor-bind
|
|
117
|
-
// likely NativeScript plugin-style specifiers (e.g., 'pinia', '@scope/pkg')
|
|
118
|
-
// via require() so device can resolve them from the app bundle.
|
|
119
|
-
const importRegex = /(^|\n)\s*import\s+([\s\S]*?)\s+from\s+["']([^"']+)["'];?/gm;
|
|
120
|
-
const sideEffectRegex = /(^|\n)\s*import\s+["']([^"']+)["'];?/gm;
|
|
121
|
-
// Collect non-vendor imports so we can hoist them above the injected vendor prelude.
|
|
122
|
-
// This ensures any residual ESM imports (like SFCs) remain at the true top-level for parsers
|
|
123
|
-
// that require imports to precede other statements.
|
|
124
|
-
const preservedImports = [];
|
|
125
|
-
const modules = new Map();
|
|
126
|
-
const getModuleBinding = (canonical) => {
|
|
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) {
|
|
@@ -1825,16 +2002,50 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1825
2002
|
// This can surface from upstream alias mishaps; mapping it here avoids device-side
|
|
1826
2003
|
// "instantiate failed @" errors.
|
|
1827
2004
|
if (spec === '@') {
|
|
1828
|
-
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
1829
|
-
if (verbose) {
|
|
1830
|
-
|
|
1831
|
-
console.warn(`[rewrite] mapped bare '@' spec to stub: ${stub}`);
|
|
1832
|
-
}
|
|
1833
|
-
catch { }
|
|
2005
|
+
const stub = `/ns/m/__invalid_at__.mjs`;
|
|
2006
|
+
if (verbose) {
|
|
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
|
-
console.log('[hmr-ws][graph] size', graph.size);
|
|
2241
|
-
}
|
|
2242
|
-
catch { }
|
|
2564
|
+
console.log('[hmr-ws][graph] upsert', { id, deps: normDeps, hash, graphVersion, classification, bumpVersion });
|
|
2565
|
+
console.log('[hmr-ws][graph] size', graph.size);
|
|
2243
2566
|
}
|
|
2244
|
-
|
|
2567
|
+
if (shouldBroadcastGraphUpsertDelta(classification, options?.emitDeltaOnInsert === true, options?.broadcastDelta !== false)) {
|
|
2568
|
+
emitDelta([gm], []);
|
|
2569
|
+
}
|
|
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,139 @@ 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 2b (NS HMR escape hatch): expose `source` on the proxy
|
|
3393
|
+
// itself via a non-enumerable, well-known symbol-ish key. This lets
|
|
3394
|
+
// our HMR remount path bypass solid-refresh's proxy chain and call
|
|
3395
|
+
// the freshly-patched underlying Home function directly. solid-refresh's
|
|
3396
|
+
// proxy wraps everything in nested createMemo's, which under
|
|
3397
|
+
// universal-renderer + nested-context (TanStack RouterContextProvider)
|
|
3398
|
+
// causes accumulating zombie memos that all subscribe to M_initial.source
|
|
3399
|
+
// — the visible symptom is "every other save applies".
|
|
3400
|
+
//
|
|
3401
|
+
// With this hatch we can do `proxy.__$$ns_resolve()` to obtain the
|
|
3402
|
+
// current underlying component (e.g. M_initial.proxy after first save,
|
|
3403
|
+
// or the actual Home function on the deepest hop) and then call
|
|
3404
|
+
// untrack(() => extract until we reach the actual function), then mount
|
|
3405
|
+
// against THAT function — no HMRComp memo proxy chain at the page level.
|
|
3406
|
+
// NS HMR escape hatch: stash `source` on the HMRComp function
|
|
3407
|
+
// itself AND short-circuit the Proxy's `get` handler so that
|
|
3408
|
+
// `proxy.__$ns_resolveSource` returns our exposed function.
|
|
3409
|
+
// Without the get-handler short-circuit, accessing the property
|
|
3410
|
+
// on the Proxy goes through `return source()[property]` — which
|
|
3411
|
+
// asks the *current source value* (which may itself be another
|
|
3412
|
+
// solid-refresh proxy or the underlying user function) for the
|
|
3413
|
+
// property. The user function doesn't have `__$ns_resolveSource`,
|
|
3414
|
+
// and a chained proxy would re-enter its own get handler. Either
|
|
3415
|
+
// way we end up with `undefined` at the page-level remount and
|
|
3416
|
+
// can't unwrap.
|
|
3417
|
+
//
|
|
3418
|
+
// NOTE: `$$` in String.prototype.replace replacement is treated
|
|
3419
|
+
// as a literal `$`. We use a single `$` to avoid that footgun.
|
|
3420
|
+
// Match only the unique opening fragment to avoid getting tripped up
|
|
3421
|
+
// by whitespace differences after the AST normalizer ran.
|
|
3422
|
+
const newProxyMarker = `if (property === 'location' || property === 'name') {`;
|
|
3423
|
+
if (patchedCode.includes(newProxyMarker)) {
|
|
3424
|
+
// 1. Inject `__$ns_resolveSource` as a property on the HMRComp
|
|
3425
|
+
// function itself (so its closure captures `source`).
|
|
3426
|
+
// CRITICAL: assign at module-eval time (right after HMRComp
|
|
3427
|
+
// is defined / before `return new Proxy(...)`), NOT inside
|
|
3428
|
+
// the HMRComp body — the body only runs when the proxy is
|
|
3429
|
+
// called, so before first call the property is undefined
|
|
3430
|
+
// and the page-level remount unwrap finds nothing.
|
|
3431
|
+
const setupMarker = `setComponentProperty(HMRComp, 'name', refreshName);`;
|
|
3432
|
+
patchedCode = patchedCode.replace(setupMarker, `HMRComp.__$ns_resolveSource = function() { return source(); }; ${setupMarker}`);
|
|
3433
|
+
// 2. Make the Proxy `get` handler short-circuit our property
|
|
3434
|
+
// so callers can do `proxy.__$ns_resolveSource()` without
|
|
3435
|
+
// going through `source()[property]` (which would unwrap one
|
|
3436
|
+
// hop early or reach the user function which doesn't have it).
|
|
3437
|
+
patchedCode = patchedCode.replace(newProxyMarker, `if (property === '__$ns_resolveSource') { return HMRComp.__$ns_resolveSource; } ${newProxyMarker}`);
|
|
3438
|
+
if (verbose) {
|
|
3439
|
+
console.log('[hmr-ws][solid] exposed __$ns_resolveSource on createProxy for NS HMR escape hatch');
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
// Patch 3: Inline patchRegistry call so updates apply immediately
|
|
3443
|
+
// on module re-evaluation (accept callbacks are not invoked by the HMR client).
|
|
3444
|
+
//
|
|
3445
|
+
// The injected diagnostic logs are gated on
|
|
3446
|
+
// `globalThis.__NS_ENV_VERBOSE__` so they're silent in
|
|
3447
|
+
// normal use but resurface immediately when the user
|
|
3448
|
+
// re-runs with verbose logging enabled. The flag is
|
|
3449
|
+
// seeded by `mainEntryPlugin` from the same `verbose`
|
|
3450
|
+
// option that drives this server-side log gating.
|
|
3451
|
+
// Without these the visible HMR signal is just "did it
|
|
3452
|
+
// apply" — with them, devs can answer "did `hot.data`
|
|
3453
|
+
// persist", "did `patchRegistry` actually swap the
|
|
3454
|
+
// proxy's signal source", and "did the registry
|
|
3455
|
+
// component count change" without reaching for an
|
|
3456
|
+
// inspector.
|
|
3457
|
+
const marker = 'hot.data[SOLID_REFRESH] = hot.data[SOLID_REFRESH] || registry;';
|
|
3458
|
+
if (patchedCode.includes(marker)) {
|
|
3459
|
+
const patchCode = [
|
|
3460
|
+
`var __nsRefreshVerbose = (typeof globalThis !== 'undefined') && !!globalThis.__NS_ENV_VERBOSE__;`,
|
|
3461
|
+
`if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] hot.data has SOLID_REFRESH=', !!(hot.data && hot.data[SOLID_REFRESH]), 'registry components=', registry.components ? registry.components.size : 0);`,
|
|
3462
|
+
`if (hot.data[SOLID_REFRESH]) {`,
|
|
3463
|
+
` var __nsOldComponents = hot.data[SOLID_REFRESH].components ? hot.data[SOLID_REFRESH].components.size : 0;`,
|
|
3464
|
+
` var __nsShouldInvalidate = patchRegistry(hot.data[SOLID_REFRESH], registry);`,
|
|
3465
|
+
` if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] patched: oldComponents=', __nsOldComponents, 'newComponents=', registry.components ? registry.components.size : 0, 'shouldInvalidate=', __nsShouldInvalidate);`,
|
|
3466
|
+
`} else {`,
|
|
3467
|
+
` if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] first load — no prior registry to patch');`,
|
|
3468
|
+
`}`,
|
|
3469
|
+
].join('\n ');
|
|
3470
|
+
patchedCode = patchedCode.replace(marker, `${patchCode}\n ${marker}`);
|
|
3471
|
+
if (verbose) {
|
|
3472
|
+
console.log('[hmr-ws][solid] added inline patchRegistry (with diagnostics) for NativeScript HMR');
|
|
3473
|
+
}
|
|
2705
3474
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3475
|
+
// Work on a copy to avoid mutating Vite's cached TransformResult
|
|
3476
|
+
transformed = { ...transformed, code: patchedCode };
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
catch { }
|
|
3481
|
+
// NOTE: Path-based cache busting for /ns/m/* imports is applied in the
|
|
3482
|
+
// finalize step below (after rewriteImports adds the /ns/m/ prefix).
|
|
3483
|
+
// The block here only handles TypeScript-specific graph population.
|
|
3484
|
+
try {
|
|
3485
|
+
if (transformed?.code) {
|
|
3486
|
+
const code = transformed.code;
|
|
2718
3487
|
// TypeScript-specific graph population: when TS flavor is active
|
|
2719
3488
|
// and this is an application module under the virtual app root,
|
|
2720
3489
|
// upsert it into the HMR graph so ns:hmr-full-graph is non-empty.
|
|
@@ -2723,15 +3492,14 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2723
3492
|
const id = (resolvedCandidate || spec).replace(/[?#].*$/, '');
|
|
2724
3493
|
// Only track app modules (under APP_VIRTUAL_WITH_SLASH) and ts/js/tsx/jsx/mjs.
|
|
2725
3494
|
const isApp = id.startsWith(APP_VIRTUAL_WITH_SLASH) || id.startsWith('/app/');
|
|
2726
|
-
if (isApp && /\.(ts|tsx|js|jsx|mjs|mts|cts)$/i.test(id)) {
|
|
3495
|
+
if (isApp && /\.(ts|tsx|js|jsx|mjs|mts|cts)$/i.test(id) && !isRuntimeGraphExcludedPath(id)) {
|
|
2727
3496
|
const deps = Array.from(collectImportDependencies(code, id));
|
|
2728
3497
|
if (verbose) {
|
|
2729
|
-
|
|
2730
|
-
console.log('[hmr-ws][ts-graph] candidate', { id, depsCount: deps.length });
|
|
2731
|
-
}
|
|
2732
|
-
catch { }
|
|
3498
|
+
console.log('[hmr-ws][ts-graph] candidate', { id, depsCount: deps.length });
|
|
2733
3499
|
}
|
|
2734
|
-
|
|
3500
|
+
// Serve-time warm-up: no live edit happened, so don't bump
|
|
3501
|
+
// graphVersion.
|
|
3502
|
+
upsertGraphModule(id, code, deps, { bumpVersion: false });
|
|
2735
3503
|
}
|
|
2736
3504
|
}
|
|
2737
3505
|
}
|
|
@@ -2843,7 +3611,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2843
3611
|
if (!transformed?.code) {
|
|
2844
3612
|
// Emit a module that throws with context for easier on-device debugging
|
|
2845
3613
|
try {
|
|
2846
|
-
const tried = Array.from(new Set(candidates)).slice(0, 12);
|
|
3614
|
+
const tried = Array.from(new Set(transformCandidates.length > 0 ? transformCandidates : candidates)).slice(0, 12);
|
|
2847
3615
|
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
3616
|
res.statusCode = 404;
|
|
2849
3617
|
res.end(out);
|
|
@@ -2860,8 +3628,33 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2860
3628
|
// Prepend guard to capture any URL-based require attempts
|
|
2861
3629
|
code = REQUIRE_GUARD_SNIPPET + code;
|
|
2862
3630
|
code = cleanCode(code);
|
|
2863
|
-
|
|
2864
|
-
code =
|
|
3631
|
+
const isNodeMod = /(?:^|\/)node_modules\//.test(resolvedCandidate || spec || '');
|
|
3632
|
+
code = processCodeForDevice(code, false, true, isNodeMod, resolvedCandidate || spec);
|
|
3633
|
+
// Solid HMR: The NativeScript iOS/Android runtime provides import.meta.hot
|
|
3634
|
+
// natively (via InitializeImportMetaHot in HMRSupport.mm) with C++-backed
|
|
3635
|
+
// persistent hot.data that survives across module re-evaluations.
|
|
3636
|
+
// cleanCode() strips Vite's __vite__createHotContext assignment, which is
|
|
3637
|
+
// correct — the runtime's native hot context is better.
|
|
3638
|
+
const projectRoot = server.config?.root || process.cwd();
|
|
3639
|
+
const serverOrigin = getServerOrigin(server);
|
|
3640
|
+
if (ACTIVE_STRATEGY?.flavor === 'angular') {
|
|
3641
|
+
code = prepareAngularEntryForDevice(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
|
|
3642
|
+
}
|
|
3643
|
+
else {
|
|
3644
|
+
code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, projectRoot, !!verbose, undefined, serverOrigin, true);
|
|
3645
|
+
}
|
|
3646
|
+
// Expand `export * from "url"` into explicit named re-exports.
|
|
3647
|
+
// NativeScript's HTTP ESM loader may not propagate star-re-exports across
|
|
3648
|
+
// HTTP module boundaries (the namespace object gets direct exports but
|
|
3649
|
+
// misses re-exported names). By expanding to `export { a, b } from "url"`,
|
|
3650
|
+
// the engine sees explicit named exports and resolves them correctly.
|
|
3651
|
+
try {
|
|
3652
|
+
code = await expandStarExports(code, server, server.config?.root || process.cwd(), verbose, sharedTransformRequest);
|
|
3653
|
+
}
|
|
3654
|
+
catch (e) {
|
|
3655
|
+
if (verbose)
|
|
3656
|
+
console.warn('[ns/m] export* expansion failed:', e?.message);
|
|
3657
|
+
}
|
|
2865
3658
|
// Dedupe any /ns/rt named imports that duplicate destructured bindings off default /ns/rt
|
|
2866
3659
|
try {
|
|
2867
3660
|
code = dedupeRtNamedImportsAgainstDestructures(code);
|
|
@@ -2888,6 +3681,28 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2888
3681
|
}
|
|
2889
3682
|
}
|
|
2890
3683
|
catch { }
|
|
3684
|
+
// Final pass: deduplicate/resolve any bare-specifier imports that slipped
|
|
3685
|
+
// through the pipeline (e.g., extracted from JSDoc comments by import-splitting
|
|
3686
|
+
// regexes, or injected by the Angular linker on already-resolved code).
|
|
3687
|
+
try {
|
|
3688
|
+
code = deduplicateLinkerImports(code);
|
|
3689
|
+
}
|
|
3690
|
+
catch { }
|
|
3691
|
+
// CJS/UMD wrapping: if a module uses module.exports but has no ESM export default,
|
|
3692
|
+
// wrap it with CJS shims so the device HTTP ESM loader can consume it.
|
|
3693
|
+
// This handles npm packages that use CommonJS but aren't pre-bundled by Vite.
|
|
3694
|
+
//
|
|
3695
|
+
// Key constraints this must handle:
|
|
3696
|
+
// - CJS modules often declare local vars with the same names as their exports
|
|
3697
|
+
// (e.g. `function createLTTB() {...}; exports.createLTTB = createLTTB;`)
|
|
3698
|
+
// so `export var { createLTTB }` would cause a duplicate declaration.
|
|
3699
|
+
// - UMD modules reference `this` at top level (undefined in ESM) but
|
|
3700
|
+
// typically fall back to `self` or `globalThis`.
|
|
3701
|
+
// - `module`, `exports` must be shims since they don't exist in ESM.
|
|
3702
|
+
try {
|
|
3703
|
+
code = wrapCommonJsModuleForDevice(code, resolvedCandidate || null);
|
|
3704
|
+
}
|
|
3705
|
+
catch { }
|
|
2891
3706
|
try {
|
|
2892
3707
|
assertNoOptimizedArtifacts(code, `NS M ${resolvedCandidate || spec}`);
|
|
2893
3708
|
}
|
|
@@ -2907,35 +3722,81 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2907
3722
|
}
|
|
2908
3723
|
}
|
|
2909
3724
|
catch { }
|
|
3725
|
+
// `/ns/rt` and `/ns/core` URL versioning.
|
|
3726
|
+
//
|
|
3727
|
+
// Older versions of the server emitted `/ns/rt/<ver>` and
|
|
3728
|
+
// `/ns/core/<ver>` so V8's HTTP module cache would see a
|
|
3729
|
+
// fresh URL on every save. The runtime canonicalizer
|
|
3730
|
+
// (`CanonicalizeHttpUrlKey` in HMRSupport.mm) collapses
|
|
3731
|
+
// these version segments to the bare `/ns/rt` and
|
|
3732
|
+
// `/ns/core` keys before lookup, so V8 actually saw a
|
|
3733
|
+
// single cache entry — but the server was doing extra
|
|
3734
|
+
// work to inject a version segment that the runtime then
|
|
3735
|
+
// immediately stripped. Now that the runtime supports
|
|
3736
|
+
// explicit eviction (and these bridge endpoints don't
|
|
3737
|
+
// change at HMR time anyway), the version segment is
|
|
3738
|
+
// purely vestigial.
|
|
3739
|
+
//
|
|
3740
|
+
// Rather than rip the helpers out (which would touch
|
|
3741
|
+
// every ensureVersionedImports caller and risk bumping
|
|
3742
|
+
// older runtimes), we keep them but pass `verNum=0`. The
|
|
3743
|
+
// helpers still normalize URL shape (strip the absolute
|
|
3744
|
+
// origin prefix when present) but emit a stable
|
|
3745
|
+
// `/ns/rt/0` / `/ns/core/0` URL — which collapses to
|
|
3746
|
+
// `/ns/rt` / `/ns/core` in the runtime.
|
|
2910
3747
|
try {
|
|
2911
|
-
const verNum =
|
|
3748
|
+
const verNum = 0;
|
|
2912
3749
|
code = ensureVersionedRtImports(code, getServerOrigin(server), verNum);
|
|
2913
3750
|
code = ACTIVE_STRATEGY.ensureVersionedImports(code, getServerOrigin(server), verNum);
|
|
2914
3751
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
|
|
2915
3752
|
}
|
|
2916
3753
|
catch { }
|
|
2917
|
-
//
|
|
3754
|
+
// `/ns/m` URL finalize step.
|
|
3755
|
+
//
|
|
3756
|
+
// `rewriteNsMImportPathForHmr` is a canonicalizer: it
|
|
3757
|
+
// strips legacy `__ns_hmr__/<tag>/` segments and adds
|
|
3758
|
+
// `__ns_boot__/b1/` only for boot-tagged requests. The
|
|
3759
|
+
// `ver` parameter is preserved on the signature for API
|
|
3760
|
+
// compatibility but is ignored for app modules (cache
|
|
3761
|
+
// busting is driven by `__nsInvalidateModules`, not URL
|
|
3762
|
+
// versioning). We pass `'v0'` as a stable placeholder —
|
|
3763
|
+
// the canonicalizer emits the same URL regardless of
|
|
3764
|
+
// this value, but a constant placeholder makes the
|
|
3765
|
+
// contract explicit.
|
|
3766
|
+
//
|
|
3767
|
+
// SFC URLs (line below, `/ns/sfc/${verTag}/...`) still
|
|
3768
|
+
// embed a version because the Vue SFC pathway does not
|
|
3769
|
+
// yet have an eviction protocol. The runtime
|
|
3770
|
+
// canonicalizer does NOT strip `/ns/sfc/<ver>/`, so Vue
|
|
3771
|
+
// users still see per-save SFC re-fetches — that's a
|
|
3772
|
+
// known follow-up.
|
|
2918
3773
|
try {
|
|
2919
|
-
const
|
|
3774
|
+
const verTag = (() => {
|
|
3775
|
+
const numeric = getNumericServeVersionTag(forcedVer, Number(graphVersion || 0));
|
|
3776
|
+
return numeric > 0 ? `v${numeric}` : 'v0';
|
|
3777
|
+
})();
|
|
2920
3778
|
const origin = getServerOrigin(server);
|
|
3779
|
+
const rewritePath = (p) => rewriteNsMImportPathForHmr(p, 'v0', bootTaggedRequest);
|
|
3780
|
+
// /ns/m URL forms — all collapse to canonical stable
|
|
3781
|
+
// URLs via the Phase 3a rewriter.
|
|
2921
3782
|
// 1) Static imports: import ... from "/ns/m/..."
|
|
2922
|
-
code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$
|
|
3783
|
+
code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2923
3784
|
// 2) Side-effect imports: import "/ns/m/..."
|
|
2924
|
-
code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$
|
|
3785
|
+
code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2925
3786
|
// 3) Dynamic imports: import("/ns/m/...")
|
|
2926
|
-
code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, `$
|
|
3787
|
+
code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2927
3788
|
// 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, `$
|
|
3789
|
+
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
3790
|
// 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 →
|
|
3791
|
+
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}`);
|
|
3792
|
+
// 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href → canonical stable URL.
|
|
2932
3793
|
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}
|
|
3794
|
+
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
3795
|
}
|
|
2935
3796
|
catch { }
|
|
2936
|
-
// 7)
|
|
3797
|
+
// 7) SFC URLs (Vue) — still versioned. See header comment.
|
|
2937
3798
|
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/${
|
|
3799
|
+
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
3800
|
}
|
|
2940
3801
|
catch { }
|
|
2941
3802
|
}
|
|
@@ -2946,13 +3807,25 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2946
3807
|
code = ensureDestructureCoreImports(code);
|
|
2947
3808
|
}
|
|
2948
3809
|
catch { }
|
|
3810
|
+
// Boot-time module graph progress: while the app is still replacing the
|
|
3811
|
+
// placeholder, emit lightweight progress updates as /ns/m modules begin
|
|
3812
|
+
// evaluating. This keeps the overlay moving during large initial graphs.
|
|
3813
|
+
try {
|
|
3814
|
+
if (bootTaggedRequest) {
|
|
3815
|
+
const bootModuleLabel = String(spec || '').replace(/\\/g, '/');
|
|
3816
|
+
const bootProgressSnippet = buildBootProgressSnippet(bootModuleLabel);
|
|
3817
|
+
code = bootProgressSnippet + code;
|
|
3818
|
+
code = hoistTopLevelStaticImports(code);
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
catch { }
|
|
2949
3822
|
// Dev-only: link-check static imports to surface missing bindings early
|
|
2950
3823
|
try {
|
|
2951
3824
|
const devCheck = process.env.NODE_ENV !== 'production';
|
|
2952
3825
|
if (devCheck) {
|
|
2953
3826
|
const ast = babelParse(code, {
|
|
2954
3827
|
sourceType: 'module',
|
|
2955
|
-
plugins:
|
|
3828
|
+
plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
|
|
2956
3829
|
});
|
|
2957
3830
|
const imports = [];
|
|
2958
3831
|
babelTraverse(ast, {
|
|
@@ -3036,6 +3909,15 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3036
3909
|
continue;
|
|
3037
3910
|
const hasDefault = /\bexport\s+default\b/.test(targetCode) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(targetCode);
|
|
3038
3911
|
if (!hasDefault) {
|
|
3912
|
+
// CJS/UMD modules won't have `export default` — they get CJS-wrapped
|
|
3913
|
+
// by the serving pipeline. Only warn, don't fatally block the importer.
|
|
3914
|
+
const hasCjsPattern = /\bmodule\s*\.\s*exports\b/.test(targetCode) || /\bexports\s*\.\s*\w/.test(targetCode);
|
|
3915
|
+
if (hasCjsPattern) {
|
|
3916
|
+
if (verbose) {
|
|
3917
|
+
console.warn(`[ns:m][link-check] CJS module without export default: ${u.pathname} (will be CJS-wrapped at serve time)`);
|
|
3918
|
+
}
|
|
3919
|
+
continue;
|
|
3920
|
+
}
|
|
3039
3921
|
const msg = `[link-check] Missing default export in ${u.pathname}${u.search} (imported by ${resolvedCandidate || spec})`;
|
|
3040
3922
|
// Emit a module that throws to surface the exact offender
|
|
3041
3923
|
res.statusCode = 200;
|
|
@@ -3047,19 +3929,15 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3047
3929
|
}
|
|
3048
3930
|
}
|
|
3049
3931
|
catch (eLC) {
|
|
3050
|
-
|
|
3932
|
+
if (verbose) {
|
|
3051
3933
|
console.warn('[ns:m][link-check] failed', eLC?.message || eLC);
|
|
3052
3934
|
}
|
|
3053
|
-
catch { }
|
|
3054
3935
|
}
|
|
3055
3936
|
res.statusCode = 200;
|
|
3056
3937
|
res.end(code);
|
|
3057
3938
|
}
|
|
3058
3939
|
catch (e) {
|
|
3059
|
-
|
|
3060
|
-
console.warn('[sfc-asm] error serving', req.url, e && e.message ? e.message : e);
|
|
3061
|
-
}
|
|
3062
|
-
catch { }
|
|
3940
|
+
console.warn('[sfc-asm] error serving', req.url, e && e.message ? e.message : e);
|
|
3063
3941
|
res.statusCode = 500;
|
|
3064
3942
|
res.end('export {}\n');
|
|
3065
3943
|
}
|
|
@@ -3092,8 +3970,10 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3092
3970
|
`let __cached_rt = null;\n` +
|
|
3093
3971
|
`let __cached_vm = null;\n` +
|
|
3094
3972
|
`const __RT_REALM_TAG = (globalThis.__NS_RT_REALM__ ||= Math.random().toString(36).slice(2));\n` +
|
|
3095
|
-
//
|
|
3096
|
-
|
|
3973
|
+
// One-shot evaluation marker to confirm the bridge is executed on
|
|
3974
|
+
// device. Gated on __NS_ENV_VERBOSE__ so it stays silent unless
|
|
3975
|
+
// the developer opts in via NS_VITE_VERBOSE / VITE_DEBUG_LOGS.
|
|
3976
|
+
`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
3977
|
`function __ensure(){\n` +
|
|
3098
3978
|
` if (__cached_rt) return __cached_rt;\n` +
|
|
3099
3979
|
` let vm = null;\n` +
|
|
@@ -3185,7 +4065,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3185
4065
|
`export const vShow = (__ensure().vShow);\n` +
|
|
3186
4066
|
`export const createApp = (...a) => (__ensure().createApp)(...a);\n` +
|
|
3187
4067
|
`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) {
|
|
4068
|
+
`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
4069
|
`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
4070
|
`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
4071
|
`export default {\n` +
|
|
@@ -3247,35 +4127,262 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3247
4127
|
return next();
|
|
3248
4128
|
}
|
|
3249
4129
|
});
|
|
4130
|
+
// 2.5.1) Catch-all redirect for stray /node_modules/@nativescript/core/*
|
|
4131
|
+
// requests — route them to the /ns/core bridge so they get the same
|
|
4132
|
+
// __DEV__/__IOS__ preamble and specifier rewriting. Without this,
|
|
4133
|
+
// Vite's default /node_modules/ handler serves the raw file, which
|
|
4134
|
+
// references bare __DEV__ and crashes at module eval.
|
|
4135
|
+
server.middlewares.use((req, _res, next) => {
|
|
4136
|
+
try {
|
|
4137
|
+
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
4138
|
+
const coreNmPrefix = '/node_modules/@nativescript/core';
|
|
4139
|
+
if (!urlObj.pathname.startsWith(coreNmPrefix))
|
|
4140
|
+
return next();
|
|
4141
|
+
const sub = urlObj.pathname.slice(coreNmPrefix.length).replace(/^\/+/, '');
|
|
4142
|
+
if (sub === '' || sub === 'index.js' || sub === 'index') {
|
|
4143
|
+
req.url = `/ns/core`;
|
|
4144
|
+
}
|
|
4145
|
+
else {
|
|
4146
|
+
req.url = `/ns/core/${sub}`;
|
|
4147
|
+
}
|
|
4148
|
+
return next();
|
|
4149
|
+
}
|
|
4150
|
+
catch {
|
|
4151
|
+
return next();
|
|
4152
|
+
}
|
|
4153
|
+
});
|
|
3250
4154
|
// 2.6) ESM bridge for @nativescript/core: GET /ns/core[/<ver>][?p=sub/path]
|
|
4155
|
+
//
|
|
4156
|
+
// Since bundle.mjs no longer bundles @nativescript/core (it is
|
|
4157
|
+
// declared external in the rolldown config under HMR), this
|
|
4158
|
+
// endpoint is the ONE place core is evaluated. Every consumer —
|
|
4159
|
+
// bundle.mjs's own `@nativescript/core*` imports (resolved to
|
|
4160
|
+
// full HTTP URLs in the entry virtual module), externalized
|
|
4161
|
+
// vendor packages, HTTP-served app modules — all end up here.
|
|
4162
|
+
// No more proxy bridge, no enumeration, no namespace detection,
|
|
4163
|
+
// no prototype-polluted maps. We just serve Vite's authoritative
|
|
4164
|
+
// transformed module content.
|
|
4165
|
+
//
|
|
4166
|
+
// iOS caches by URL path, so each unique URL is evaluated exactly
|
|
4167
|
+
// once per app lifetime. Every class identity is shared, every
|
|
4168
|
+
// `register()` side effect runs once, every `Application` reference
|
|
4169
|
+
// is the same iosApp singleton. The entire class of "does not
|
|
4170
|
+
// provide an export named X" and "Cannot redefine property" errors
|
|
4171
|
+
// is eliminated by construction.
|
|
3251
4172
|
server.middlewares.use(async (req, res, next) => {
|
|
3252
4173
|
try {
|
|
3253
4174
|
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3254
|
-
|
|
4175
|
+
const coreRequest = parseCoreBridgeRequest(urlObj.pathname, urlObj.searchParams, Number(graphVersion || 0));
|
|
4176
|
+
if (!coreRequest)
|
|
3255
4177
|
return next();
|
|
3256
4178
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
3257
4179
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
3258
4180
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
3259
4181
|
res.setHeader('Pragma', 'no-cache');
|
|
3260
4182
|
res.setHeader('Expires', '0');
|
|
3261
|
-
const
|
|
3262
|
-
const
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
4183
|
+
const { normalizedSub, sub, ver } = coreRequest;
|
|
4184
|
+
const resolveModuleId = async (moduleId) => {
|
|
4185
|
+
const resolved = await server.pluginContainer?.resolveId?.(moduleId, undefined);
|
|
4186
|
+
return typeof resolved === 'string' ? resolved : resolved?.id || null;
|
|
4187
|
+
};
|
|
4188
|
+
let modulePath = null;
|
|
4189
|
+
if (sub) {
|
|
4190
|
+
const resolvedSubpath = normalizedSub || sub;
|
|
4191
|
+
modulePath = await resolveRuntimeCoreModulePath(resolvedSubpath, resolveModuleId);
|
|
4192
|
+
if (!modulePath) {
|
|
4193
|
+
modulePath = `/node_modules/@nativescript/core/${resolvedSubpath}`;
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
else {
|
|
4197
|
+
modulePath = (await resolveModuleId('@nativescript/core')) || '/node_modules/@nativescript/core/index.js';
|
|
4198
|
+
}
|
|
4199
|
+
const transformed = await sharedTransformRequest(modulePath);
|
|
4200
|
+
if (!transformed?.code) {
|
|
4201
|
+
res.statusCode = 500;
|
|
4202
|
+
res.setHeader('Content-Type', 'application/json');
|
|
4203
|
+
res.end(JSON.stringify({ error: 'core-transform-failed', modulePath, sub: sub || null }));
|
|
4204
|
+
return;
|
|
4205
|
+
}
|
|
4206
|
+
// Vite's transform output references module IDs with /@fs,
|
|
4207
|
+
// relative specifiers, or absolute project paths. Rewrite
|
|
4208
|
+
// those to URLs iOS can fetch over HTTP.
|
|
4209
|
+
let rewritten = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
|
|
4210
|
+
// Invariant D (CJS/ESM interop shape) — EXPORT-SIDE fix.
|
|
4211
|
+
//
|
|
4212
|
+
// `@nativescript/core/index.js` declares namespace
|
|
4213
|
+
// re-exports like:
|
|
4214
|
+
// export * as Utils from './utils';
|
|
4215
|
+
// The ES spec says these produce Module Namespace Objects
|
|
4216
|
+
// with [[Prototype]] = null. Consumers that reach them
|
|
4217
|
+
// via direct ESM import — `import { Utils } from
|
|
4218
|
+
// '@nativescript/core'` — get the raw null-proto value,
|
|
4219
|
+
// bypassing any CJS `require` shim we install. Most
|
|
4220
|
+
// consumers tolerate this, but CJS-style interop (most
|
|
4221
|
+
// notably zone.js's `patchMethod`) calls
|
|
4222
|
+
// `hasOwnProperty` on the target and crashes on
|
|
4223
|
+
// null-proto.
|
|
4224
|
+
//
|
|
4225
|
+
// We rewrite the re-export to a shape-wrapped const:
|
|
4226
|
+
// import * as __ns_re_Utils__ from './utils';
|
|
4227
|
+
// export const Utils = __NS_CJS_SHAPE__(__ns_re_Utils__);
|
|
4228
|
+
// so the EXPORT itself is a plain object — visible to
|
|
4229
|
+
// both ESM and CJS consumers consistently.
|
|
4230
|
+
//
|
|
4231
|
+
// We only pay the rewrite cost when the module actually
|
|
4232
|
+
// contains namespace re-exports (i.e., the main
|
|
4233
|
+
// `index.js`). Subpaths (`/utils`, `/http`, …) don't
|
|
4234
|
+
// re-export via `export * as`; they expose named
|
|
4235
|
+
// exports directly, so the rewrite is a no-op on them.
|
|
4236
|
+
if (hasNamespaceReExport(rewritten)) {
|
|
4237
|
+
rewritten = rewriteNamespaceReExportsForShape(rewritten);
|
|
4238
|
+
}
|
|
4239
|
+
// Prepend the build-time defines (__DEV__, __IOS__, __ANDROID__,
|
|
4240
|
+
// __APPLE__, …) that @nativescript/core source references directly.
|
|
4241
|
+
// Vite's `define` config substitutes these in user-code transforms but
|
|
4242
|
+
// skips node_modules by default; since core is now external and served
|
|
4243
|
+
// over HTTP from this endpoint, the served transformed code still has
|
|
4244
|
+
// bare identifiers like `if (__DEV__) …`. Without these consts, V8
|
|
4245
|
+
// hits `ReferenceError: __DEV__ is not defined` at module eval because
|
|
4246
|
+
// globalThis.__DEV__ is set by bundle.mjs's body AFTER all static
|
|
4247
|
+
// imports (including these core modules) have resolved.
|
|
4248
|
+
//
|
|
4249
|
+
// We inject LITERAL boolean values based on CLI flags + dev-server
|
|
4250
|
+
// mode rather than reading from globalThis, so the defines are
|
|
4251
|
+
// resolved even before bundle.mjs's body runs.
|
|
4252
|
+
const __cliFlags = getCliFlags() || {};
|
|
4253
|
+
const __platformIsAndroid = !!__cliFlags.android;
|
|
4254
|
+
const __platformIsVisionOS = !!__cliFlags.visionos;
|
|
4255
|
+
const __platformIsIOS = !__platformIsAndroid && !__platformIsVisionOS;
|
|
4256
|
+
const preamble = [
|
|
4257
|
+
`const __ANDROID__ = ${__platformIsAndroid ? 'true' : 'false'};`,
|
|
4258
|
+
`const __IOS__ = ${__platformIsIOS ? 'true' : 'false'};`,
|
|
4259
|
+
`const __VISIONOS__ = ${__platformIsVisionOS ? 'true' : 'false'};`,
|
|
4260
|
+
`const __APPLE__ = __IOS__ || __VISIONOS__;`,
|
|
4261
|
+
`const __DEV__ = ${server.config?.mode === 'development' ? 'true' : 'false'};`,
|
|
4262
|
+
`const __COMMONJS__ = false;`,
|
|
4263
|
+
`const __NS_WEBPACK__ = false;`,
|
|
4264
|
+
`const __NS_ENV_VERBOSE__ = globalThis.__NS_ENV_VERBOSE__ !== undefined ? !!globalThis.__NS_ENV_VERBOSE__ : false;`,
|
|
4265
|
+
`const __CSS_PARSER__ = 'css-tree';`,
|
|
4266
|
+
`const __UI_USE_XML_PARSER__ = true;`,
|
|
4267
|
+
`const __UI_USE_EXTERNAL_RENDERER__ = false;`,
|
|
4268
|
+
`const __TEST__ = false;`,
|
|
4269
|
+
].join('\n');
|
|
4270
|
+
// Boot-time instrumentation + module self-registration.
|
|
4271
|
+
//
|
|
4272
|
+
// - URL canonicalization: the same logical module must
|
|
4273
|
+
// always resolve to byte-identical URLs across every
|
|
4274
|
+
// emitter. The /ns/core handler records the first URL
|
|
4275
|
+
// seen for each canonical sub (or '' for main) in
|
|
4276
|
+
// `globalThis.__NS_CORE_FIRST_URL__` and fails hard on
|
|
4277
|
+
// mismatch so drift in any emitter surfaces
|
|
4278
|
+
// immediately, before the realm splits.
|
|
4279
|
+
// - CJS/ESM boot order: CommonJS
|
|
4280
|
+
// `require('@nativescript/core/...')` calls from
|
|
4281
|
+
// vendor install() hooks must resolve to the SAME
|
|
4282
|
+
// ESM namespace that ran this side-effect preamble.
|
|
4283
|
+
// The registration below keys the namespace object
|
|
4284
|
+
// under BOTH the bare specifier and the canonical
|
|
4285
|
+
// subpath (and raw subpath for back-compat) so the
|
|
4286
|
+
// vendor shim's `createRequire` and the main-entry
|
|
4287
|
+
// `_nsReq` hit on any lookup form.
|
|
4288
|
+
const rawSub = normalizedSub || sub || '';
|
|
4289
|
+
const canonicalSub = normalizeCoreSubCanonical(rawSub);
|
|
4290
|
+
const registrationKeySet = new Set();
|
|
4291
|
+
registrationKeySet.add(canonicalSub ? `@nativescript/core/${canonicalSub}` : '@nativescript/core');
|
|
4292
|
+
registrationKeySet.add(canonicalSub);
|
|
4293
|
+
if (rawSub && rawSub !== canonicalSub) {
|
|
4294
|
+
registrationKeySet.add(`@nativescript/core/${rawSub}`);
|
|
4295
|
+
registrationKeySet.add(rawSub);
|
|
4296
|
+
}
|
|
4297
|
+
const registrationKeys = Array.from(registrationKeySet).map((k) => JSON.stringify(k));
|
|
4298
|
+
const canonicalUrl = `${getServerOrigin(server)}` + (canonicalSub ? `/ns/core/${canonicalSub}` : '/ns/core');
|
|
4299
|
+
const instrumentationHeader = [
|
|
4300
|
+
`/* @nativescript/core bridge — canonical URL: ${canonicalUrl} */`,
|
|
4301
|
+
`try { if (typeof globalThis !== 'undefined') {`,
|
|
4302
|
+
` const __nsFirst = globalThis.__NS_CORE_FIRST_URL__ || (globalThis.__NS_CORE_FIRST_URL__ = Object.create(null));`,
|
|
4303
|
+
` const __nsSeen = globalThis.__NS_CORE_FETCHED_URLS__ || (globalThis.__NS_CORE_FETCHED_URLS__ = []);`,
|
|
4304
|
+
` const __nsKey = ${JSON.stringify(canonicalSub)};`,
|
|
4305
|
+
` const __nsUrl = ${JSON.stringify(canonicalUrl)};`,
|
|
4306
|
+
` __nsSeen.push(__nsUrl);`,
|
|
4307
|
+
` if (typeof __nsFirst[__nsKey] === 'string' && __nsFirst[__nsKey] !== __nsUrl) {`,
|
|
4308
|
+
` throw new Error('[ns-core] URL drift for sub=' + __nsKey + ': first=' + __nsFirst[__nsKey] + ' now=' + __nsUrl);`,
|
|
4309
|
+
` }`,
|
|
4310
|
+
` if (!__nsFirst[__nsKey]) __nsFirst[__nsKey] = __nsUrl;`,
|
|
4311
|
+
` globalThis.__NS_CORE_EVAL_COUNT__ = (globalThis.__NS_CORE_EVAL_COUNT__ || 0) + 1;`,
|
|
4312
|
+
`} } catch (e) { console.warn('[ns-core] instrumentation failed:', (e && e.message) || e); }`,
|
|
4313
|
+
].join('\n');
|
|
4314
|
+
// CJS/ESM interop shape — REGISTRATION side.
|
|
4315
|
+
//
|
|
4316
|
+
// The actual shape installer runs earlier in the module
|
|
4317
|
+
// body (between preamble and selfImport; see
|
|
4318
|
+
// buildShapeInstallHeader). At this point we just read
|
|
4319
|
+
// globalThis.__NS_CJS_SHAPE__ and apply it to the self
|
|
4320
|
+
// namespace before registering under the CJS key space.
|
|
4321
|
+
//
|
|
4322
|
+
// Why shape self at registration: consumers that reach
|
|
4323
|
+
// `@nativescript/core` via `require()` (legacy vendors,
|
|
4324
|
+
// `globalThis.require` shim) look up the registry. They
|
|
4325
|
+
// expect a plain object (Object.prototype in chain) so
|
|
4326
|
+
// `.hasOwnProperty` / `.toString` work. Shaping once on
|
|
4327
|
+
// registration — the shape function is identity-preserving
|
|
4328
|
+
// via WeakMap — gives a stable, shared, CJS-compatible
|
|
4329
|
+
// view without copying on every require.
|
|
4330
|
+
const registrationFooter = [
|
|
4331
|
+
`try { if (typeof globalThis !== 'undefined') {`,
|
|
4332
|
+
` const __nsReg = globalThis.__NS_CORE_MODULES__ || (globalThis.__NS_CORE_MODULES__ = Object.create(null));`,
|
|
4333
|
+
` const __nsShapeFn = typeof globalThis.__NS_CJS_SHAPE__ === 'function' ? globalThis.__NS_CJS_SHAPE__ : function (x) { return x; };`,
|
|
4334
|
+
` const __nsSelfRaw = (typeof __ns_core_self_ns__ !== 'undefined') ? __ns_core_self_ns__ : { default: undefined };`,
|
|
4335
|
+
` const __nsSelf = __nsShapeFn(__nsSelfRaw);`,
|
|
4336
|
+
...registrationKeys.map((k) => ` __nsReg[${k}] = __nsSelf;`),
|
|
4337
|
+
`} } catch (e) { console.warn('[ns-core] self-register failed:', (e && e.message) || e); }`,
|
|
4338
|
+
].join('\n');
|
|
4339
|
+
// Bind `import * as __ns_core_self_ns__` to the module's
|
|
4340
|
+
// own export namespace so the footer can stash it into
|
|
4341
|
+
// the registry. Self-import is a no-op at eval time —
|
|
4342
|
+
// V8 resolves it to the module record we're already
|
|
4343
|
+
// evaluating and the final namespace is the same object
|
|
4344
|
+
// the registry receives. We use the CANONICAL URL here
|
|
4345
|
+
// so the self-import participates in Invariant A along
|
|
4346
|
+
// with every other @nativescript/core URL.
|
|
4347
|
+
const canonicalUrlForSelf = canonicalSub ? `/ns/core/${canonicalSub}` : '/ns/core';
|
|
4348
|
+
const selfImport = `import * as __ns_core_self_ns__ from ${JSON.stringify(canonicalUrlForSelf)};`;
|
|
4349
|
+
// Invariant D — SHAPE INSTALLER.
|
|
4350
|
+
//
|
|
4351
|
+
// Emits idempotent body-code that installs
|
|
4352
|
+
// globalThis.__NS_CJS_SHAPE__ BEFORE `rewritten`'s body
|
|
4353
|
+
// runs. This matters because the rewrite step above may
|
|
4354
|
+
// have produced statements like
|
|
4355
|
+
// `export const Utils = (typeof globalThis.__NS_CJS_SHAPE__ ...)(__ns_re_Utils__);`
|
|
4356
|
+
// that execute during module evaluation. Without the
|
|
4357
|
+
// installer running first, the ternary falls back to
|
|
4358
|
+
// identity — still safe, but the null-proto namespace
|
|
4359
|
+
// leaks through and consumers that expect a plain
|
|
4360
|
+
// object would still crash.
|
|
4361
|
+
//
|
|
4362
|
+
// Placement is important: BEFORE selfImport in the
|
|
4363
|
+
// concatenation. ESM imports are hoisted regardless of
|
|
4364
|
+
// textual position, but body code executes in source
|
|
4365
|
+
// order. Placing the installer first guarantees it
|
|
4366
|
+
// runs before any body statement in `rewritten`.
|
|
4367
|
+
//
|
|
4368
|
+
// Install is idempotent: `|| (globalThis.X = ...)` so
|
|
4369
|
+
// whichever /ns/core module evaluates first wins and
|
|
4370
|
+
// every subsequent module becomes a no-op.
|
|
4371
|
+
const shapeInstallHeader = buildShapeInstallHeader();
|
|
4372
|
+
// Invariant D — DEFAULT EXPORT BRIDGE.
|
|
4373
|
+
//
|
|
4374
|
+
// See `buildDefaultExportFooter` in ns-core-cjs-shape.ts
|
|
4375
|
+
// for the full rationale (consumer matrix, skip conditions,
|
|
4376
|
+
// why the default isn't shaped). The short version:
|
|
4377
|
+
// upstream rewrites turn `import { X } from '@nativescript/core'`
|
|
4378
|
+
// into a DEFAULT import, and the bridge has to provide one.
|
|
4379
|
+
const defaultExportFooter = buildDefaultExportFooter(rewritten);
|
|
4380
|
+
const moduleCode = [instrumentationHeader, preamble, shapeInstallHeader, selfImport, rewritten, defaultExportFooter, registrationFooter].join('\n');
|
|
3275
4381
|
res.statusCode = 200;
|
|
3276
|
-
res.end(
|
|
4382
|
+
res.end(moduleCode);
|
|
3277
4383
|
}
|
|
3278
4384
|
catch (e) {
|
|
4385
|
+
console.warn('[ns-core-bridge] serve failed:', e?.message);
|
|
3279
4386
|
next();
|
|
3280
4387
|
}
|
|
3281
4388
|
});
|
|
@@ -3285,14 +4392,11 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3285
4392
|
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3286
4393
|
if (!(urlObj.pathname === '/ns/entry-rt'))
|
|
3287
4394
|
return next();
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
console.log('[hmr-http] GET /ns/entry-rt from', ra + (rp ? ':' + rp : ''));
|
|
3293
|
-
}
|
|
4395
|
+
if (verbose) {
|
|
4396
|
+
const ra = req.socket?.remoteAddress;
|
|
4397
|
+
const rp = req.socket?.remotePort;
|
|
4398
|
+
console.log('[hmr-http] GET /ns/entry-rt from', ra + (rp ? ':' + rp : ''));
|
|
3294
4399
|
}
|
|
3295
|
-
catch { }
|
|
3296
4400
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
3297
4401
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
3298
4402
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
@@ -3300,18 +4404,41 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3300
4404
|
res.setHeader('Expires', '0');
|
|
3301
4405
|
let content = '';
|
|
3302
4406
|
try {
|
|
3303
|
-
const
|
|
3304
|
-
const entryRtPath =
|
|
3305
|
-
|
|
3306
|
-
content = fs.readFileSync(entryRtPath, 'utf-8');
|
|
4407
|
+
const _req = createRequire(import.meta.url);
|
|
4408
|
+
const entryRtPath = _req.resolve('@nativescript/vite/hmr/entry-runtime.js');
|
|
4409
|
+
content = readFileSync(entryRtPath, 'utf-8');
|
|
3307
4410
|
}
|
|
3308
4411
|
catch (e) {
|
|
3309
|
-
|
|
4412
|
+
// .js not found (source tree without build) — transform .ts on the fly
|
|
4413
|
+
try {
|
|
4414
|
+
const tsPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'entry-runtime.ts');
|
|
4415
|
+
if (existsSync(tsPath)) {
|
|
4416
|
+
const tsSource = readFileSync(tsPath, 'utf-8');
|
|
4417
|
+
const result = babelCore.transformSync(tsSource, {
|
|
4418
|
+
filename: tsPath,
|
|
4419
|
+
plugins: [[pluginTransformTypescript, { isTSX: false, allowDeclareFields: true }]],
|
|
4420
|
+
sourceType: 'module',
|
|
4421
|
+
});
|
|
4422
|
+
if (result?.code) {
|
|
4423
|
+
content = result.code;
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
catch (e2) {
|
|
4428
|
+
if (verbose)
|
|
4429
|
+
console.warn('[hmr-http] entry-runtime.ts transform failed', e2);
|
|
4430
|
+
}
|
|
4431
|
+
if (!content) {
|
|
4432
|
+
content = 'export default async function start(){ console.error("[/ns/entry-rt] not found"); }\n';
|
|
4433
|
+
}
|
|
3310
4434
|
}
|
|
4435
|
+
if (verbose)
|
|
4436
|
+
console.log('[hmr-http] /ns/entry-rt serving', content.length, 'bytes');
|
|
3311
4437
|
res.statusCode = 200;
|
|
3312
4438
|
res.end(content);
|
|
3313
4439
|
}
|
|
3314
4440
|
catch (e) {
|
|
4441
|
+
console.warn('[hmr-http] /ns/entry-rt error', e);
|
|
3315
4442
|
next();
|
|
3316
4443
|
}
|
|
3317
4444
|
});
|
|
@@ -3514,10 +4641,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3514
4641
|
}
|
|
3515
4642
|
if (!transformed?.code) {
|
|
3516
4643
|
if (verbose) {
|
|
3517
|
-
|
|
3518
|
-
console.warn(`[sfc][serve] transform miss for`, fullSpec);
|
|
3519
|
-
}
|
|
3520
|
-
catch { }
|
|
4644
|
+
console.warn(`[sfc][serve] transform miss for`, fullSpec);
|
|
3521
4645
|
}
|
|
3522
4646
|
// Emit an erroring module to surface the failure at import site with helpful hints
|
|
3523
4647
|
try {
|
|
@@ -3633,10 +4757,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3633
4757
|
}
|
|
3634
4758
|
catch (eTplSelf) {
|
|
3635
4759
|
if (verbose) {
|
|
3636
|
-
|
|
3637
|
-
console.warn('[sfc][template][self-compile][fail]', fullSpec, eTplSelf?.message);
|
|
3638
|
-
}
|
|
3639
|
-
catch { }
|
|
4760
|
+
console.warn('[sfc][template][self-compile][fail]', fullSpec, eTplSelf?.message);
|
|
3640
4761
|
}
|
|
3641
4762
|
code = transformed.code || 'export {}\n';
|
|
3642
4763
|
code = processTemplateVariantMinimal(code);
|
|
@@ -3741,7 +4862,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3741
4862
|
code = outCode;
|
|
3742
4863
|
}
|
|
3743
4864
|
catch { }
|
|
3744
|
-
code = processCodeForDevice(code, false);
|
|
4865
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(fullSpec), fullSpec);
|
|
3745
4866
|
// Transform static .vue imports into static imports from the assembler (no TLA) via AST
|
|
3746
4867
|
try {
|
|
3747
4868
|
const importerPath = fullSpec.replace(/[?#].*$/, '');
|
|
@@ -3803,10 +4924,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3803
4924
|
}
|
|
3804
4925
|
catch (eTsVar) {
|
|
3805
4926
|
if (verbose) {
|
|
3806
|
-
|
|
3807
|
-
console.warn('[sfc][variant:script][babel-ts][fail]', fullSpec, eTsVar?.message);
|
|
3808
|
-
}
|
|
3809
|
-
catch { }
|
|
4927
|
+
console.warn('[sfc][variant:script][babel-ts][fail]', fullSpec, eTsVar?.message);
|
|
3810
4928
|
}
|
|
3811
4929
|
}
|
|
3812
4930
|
}
|
|
@@ -3870,10 +4988,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3870
4988
|
const kind = isVariant ? `variant:${variantType || 'unknown'}` : 'full';
|
|
3871
4989
|
const sig = `// [sfc] kind=${kind} path=${importerPath} len=${code.length} default=${hasDefault} wrapped=${false}\n`;
|
|
3872
4990
|
if (verbose) {
|
|
3873
|
-
|
|
3874
|
-
console.log(`[sfc][serve] ${fullSpec} kind=${kind} default=${hasDefault} bytes=${code.length}`);
|
|
3875
|
-
}
|
|
3876
|
-
catch { }
|
|
4991
|
+
console.log(`[sfc][serve] ${fullSpec} kind=${kind} default=${hasDefault} bytes=${code.length}`);
|
|
3877
4992
|
}
|
|
3878
4993
|
// Ensure script variants always provide a default export if they declare a component
|
|
3879
4994
|
if (!hasDefault) {
|
|
@@ -3942,16 +5057,10 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3942
5057
|
// 6) Object property forms (rare in template output) render: (...) => {}
|
|
3943
5058
|
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
5059
|
if (hasRender && verbose) {
|
|
3945
|
-
|
|
3946
|
-
console.log('[sfc-meta] detected render for', base);
|
|
3947
|
-
}
|
|
3948
|
-
catch { }
|
|
5060
|
+
console.log('[sfc-meta] detected render for', base);
|
|
3949
5061
|
}
|
|
3950
5062
|
else if (!hasRender && verbose) {
|
|
3951
|
-
|
|
3952
|
-
console.warn('[sfc-meta] render NOT detected for', base);
|
|
3953
|
-
}
|
|
3954
|
-
catch { }
|
|
5063
|
+
console.warn('[sfc-meta] render NOT detected for', base);
|
|
3955
5064
|
}
|
|
3956
5065
|
const hash = createHash('md5').update(base).digest('hex').slice(0, 8);
|
|
3957
5066
|
const payload = {
|
|
@@ -4085,10 +5194,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4085
5194
|
}
|
|
4086
5195
|
catch (eScript) {
|
|
4087
5196
|
if (verbose) {
|
|
4088
|
-
|
|
4089
|
-
console.warn('[sfc-asm][compileScript] failed', base, eScript?.message);
|
|
4090
|
-
}
|
|
4091
|
-
catch { }
|
|
5197
|
+
console.warn('[sfc-asm][compileScript] failed', base, eScript?.message);
|
|
4092
5198
|
}
|
|
4093
5199
|
// Retry without inlineTemplate
|
|
4094
5200
|
try {
|
|
@@ -4104,10 +5210,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4104
5210
|
}
|
|
4105
5211
|
catch (eNoInline) {
|
|
4106
5212
|
if (verbose) {
|
|
4107
|
-
|
|
4108
|
-
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4109
|
-
}
|
|
4110
|
-
catch { }
|
|
5213
|
+
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4111
5214
|
}
|
|
4112
5215
|
}
|
|
4113
5216
|
}
|
|
@@ -4138,10 +5241,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4138
5241
|
}
|
|
4139
5242
|
catch (eNoInline) {
|
|
4140
5243
|
if (verbose) {
|
|
4141
|
-
|
|
4142
|
-
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4143
|
-
}
|
|
4144
|
-
catch { }
|
|
5244
|
+
console.warn('[sfc-asm][compileScript][no-inline-fallback] failed', base, eNoInline?.message);
|
|
4145
5245
|
}
|
|
4146
5246
|
}
|
|
4147
5247
|
}
|
|
@@ -4164,20 +5264,14 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4164
5264
|
});
|
|
4165
5265
|
compiledTplCode = (ct && (ct.code || '')) || '';
|
|
4166
5266
|
if (ct?.errors?.length && verbose) {
|
|
4167
|
-
|
|
4168
|
-
console.warn('[sfc-asm][compileTemplate][errors]', base, ct.errors);
|
|
4169
|
-
}
|
|
4170
|
-
catch { }
|
|
5267
|
+
console.warn('[sfc-asm][compileTemplate][errors]', base, ct.errors);
|
|
4171
5268
|
}
|
|
4172
5269
|
}
|
|
4173
5270
|
}
|
|
4174
5271
|
catch (eTpl) {
|
|
4175
5272
|
templateErr = eTpl;
|
|
4176
5273
|
if (verbose) {
|
|
4177
|
-
|
|
4178
|
-
console.warn('[sfc-asm][compileTemplate] failed', base, eTpl?.message);
|
|
4179
|
-
}
|
|
4180
|
-
catch { }
|
|
5274
|
+
console.warn('[sfc-asm][compileTemplate] failed', base, eTpl?.message);
|
|
4181
5275
|
}
|
|
4182
5276
|
// Fallback: use the variant-transformed template code if available
|
|
4183
5277
|
try {
|
|
@@ -4240,10 +5334,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4240
5334
|
}
|
|
4241
5335
|
catch (eExtract) {
|
|
4242
5336
|
if (verbose) {
|
|
4243
|
-
|
|
4244
|
-
console.warn('[sfc-asm][extractTemplateRender] failed', base, eExtract?.message);
|
|
4245
|
-
}
|
|
4246
|
-
catch { }
|
|
5337
|
+
console.warn('[sfc-asm][extractTemplateRender] failed', base, eExtract?.message);
|
|
4247
5338
|
}
|
|
4248
5339
|
}
|
|
4249
5340
|
}
|
|
@@ -4293,11 +5384,10 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4293
5384
|
parts.push(scriptTransformed);
|
|
4294
5385
|
parts.push(renderDecl);
|
|
4295
5386
|
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
5387
|
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
5388
|
parts.push(`export default __ns_sfc__`);
|
|
4299
5389
|
let inlineCode = parts.filter(Boolean).join('\n');
|
|
4300
|
-
inlineCode = processCodeForDevice(inlineCode, false);
|
|
5390
|
+
inlineCode = processCodeForDevice(inlineCode, false, true);
|
|
4301
5391
|
try {
|
|
4302
5392
|
inlineCode = ensureVersionedCoreImports(inlineCode, getServerOrigin(server), Number(ver));
|
|
4303
5393
|
}
|
|
@@ -4322,10 +5412,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4322
5412
|
}
|
|
4323
5413
|
catch (eTs) {
|
|
4324
5414
|
if (verbose) {
|
|
4325
|
-
|
|
4326
|
-
console.warn('[sfc-asm][babel-ts][fail]', base, eTs?.message);
|
|
4327
|
-
}
|
|
4328
|
-
catch { }
|
|
5415
|
+
console.warn('[sfc-asm][babel-ts][fail]', base, eTs?.message);
|
|
4329
5416
|
}
|
|
4330
5417
|
}
|
|
4331
5418
|
// Hoist imports + strip residual TS via AST
|
|
@@ -4335,18 +5422,12 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4335
5422
|
importLines = astRes.imports;
|
|
4336
5423
|
scriptTransformed = astRes.body;
|
|
4337
5424
|
if (astRes.diagnostics.length && verbose) {
|
|
4338
|
-
|
|
4339
|
-
console.warn('[sfc-asm][ast]', base, astRes.diagnostics.join('; '));
|
|
4340
|
-
}
|
|
4341
|
-
catch { }
|
|
5425
|
+
console.warn('[sfc-asm][ast]', base, astRes.diagnostics.join('; '));
|
|
4342
5426
|
}
|
|
4343
5427
|
}
|
|
4344
5428
|
catch (eAst) {
|
|
4345
5429
|
if (verbose) {
|
|
4346
|
-
|
|
4347
|
-
console.warn('[sfc-asm][ast][fail]', base, eAst?.message);
|
|
4348
|
-
}
|
|
4349
|
-
catch { }
|
|
5430
|
+
console.warn('[sfc-asm][ast][fail]', base, eAst?.message);
|
|
4350
5431
|
}
|
|
4351
5432
|
}
|
|
4352
5433
|
// Ensure renderDecl ends with closing brace ONLY for function declaration forms
|
|
@@ -4372,12 +5453,11 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4372
5453
|
outParts.push(renderDecl);
|
|
4373
5454
|
}
|
|
4374
5455
|
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
5456
|
// Export named render as a function that resolves lazily
|
|
4377
5457
|
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
5458
|
outParts.push('export default __ns_sfc__');
|
|
4379
5459
|
let inlineCode2 = outParts.filter(Boolean).join('\n');
|
|
4380
|
-
inlineCode2 = processCodeForDevice(inlineCode2, false);
|
|
5460
|
+
inlineCode2 = processCodeForDevice(inlineCode2, false, true);
|
|
4381
5461
|
try {
|
|
4382
5462
|
inlineCode2 = ensureVersionedCoreImports(inlineCode2, getServerOrigin(server), Number(ver));
|
|
4383
5463
|
}
|
|
@@ -4673,12 +5753,11 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4673
5753
|
}
|
|
4674
5754
|
let asm;
|
|
4675
5755
|
if (inlineOk) {
|
|
4676
|
-
const diagLine = `// diagnostic:inlineOk ver=${ver} inlineBlock=${!!(inlineBlock && inlineBlock.trim())} helperBindingsLen=${helperBindings.length} renderDeclLen=${renderDecl.length}`;
|
|
4677
5756
|
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__
|
|
5757
|
+
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
5758
|
}
|
|
4680
5759
|
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__
|
|
5760
|
+
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
5761
|
}
|
|
4683
5762
|
}
|
|
4684
5763
|
else {
|
|
@@ -4689,7 +5768,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4689
5768
|
}
|
|
4690
5769
|
// Run full device processing so helper aliasing and globals are consistent in this path too
|
|
4691
5770
|
let code = REQUIRE_GUARD_SNIPPET + asm;
|
|
4692
|
-
code = processCodeForDevice(code, false);
|
|
5771
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(base), base);
|
|
4693
5772
|
try {
|
|
4694
5773
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), Number(ver));
|
|
4695
5774
|
}
|
|
@@ -4862,7 +5941,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4862
5941
|
let code = transformed.code;
|
|
4863
5942
|
// Reuse existing sanitation chain (lightweight)
|
|
4864
5943
|
code = cleanCode(code);
|
|
4865
|
-
code = processCodeForDevice(code, false);
|
|
5944
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
|
|
4866
5945
|
try {
|
|
4867
5946
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
|
|
4868
5947
|
}
|
|
@@ -4917,7 +5996,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4917
5996
|
if (depTrans?.code && depResolved) {
|
|
4918
5997
|
let depCode = depTrans.code;
|
|
4919
5998
|
depCode = cleanCode(depCode);
|
|
4920
|
-
depCode = processCodeForDevice(depCode, false);
|
|
5999
|
+
depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
|
|
4921
6000
|
try {
|
|
4922
6001
|
depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
|
|
4923
6002
|
}
|
|
@@ -4950,8 +6029,8 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4950
6029
|
ts: Date.now(),
|
|
4951
6030
|
delta: true,
|
|
4952
6031
|
};
|
|
4953
|
-
wss
|
|
4954
|
-
if (c
|
|
6032
|
+
wss?.clients.forEach((c) => {
|
|
6033
|
+
if (isSocketClientOpen(c)) {
|
|
4955
6034
|
try {
|
|
4956
6035
|
c.send(JSON.stringify(single));
|
|
4957
6036
|
}
|
|
@@ -4990,32 +6069,33 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4990
6069
|
if (verbose)
|
|
4991
6070
|
console.warn('[hmr-ws][graph] initial population failed', e);
|
|
4992
6071
|
}
|
|
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
|
-
|
|
6072
|
+
// Send SFC registry on every connection (not just the first).
|
|
6073
|
+
// When the NativeScript app restarts (e.g. CLI auto-reload), the new
|
|
6074
|
+
// JS context has an empty sfcArtifactMap. Without the registry the
|
|
6075
|
+
// rescue-mount cannot find the root .vue component.
|
|
6076
|
+
try {
|
|
6077
|
+
await ACTIVE_STRATEGY.buildRegistry({
|
|
6078
|
+
server,
|
|
6079
|
+
sfcFileMap,
|
|
6080
|
+
depFileMap,
|
|
6081
|
+
wss: wss,
|
|
6082
|
+
verbose,
|
|
6083
|
+
helpers: {
|
|
6084
|
+
cleanCode,
|
|
6085
|
+
collectImportDependencies,
|
|
6086
|
+
isCoreGlobalsReference,
|
|
6087
|
+
isNativeScriptCoreModule,
|
|
6088
|
+
isNativeScriptPluginModule,
|
|
6089
|
+
resolveVendorFromCandidate,
|
|
6090
|
+
createHash: (value) => createHash('md5').update(value).digest('hex'),
|
|
6091
|
+
rewriteImports,
|
|
6092
|
+
processSfcCode,
|
|
6093
|
+
},
|
|
6094
|
+
});
|
|
6095
|
+
registrySent = true;
|
|
6096
|
+
}
|
|
6097
|
+
catch (error) {
|
|
6098
|
+
console.warn('[hmr-ws] Failed to send registry:', error);
|
|
5019
6099
|
}
|
|
5020
6100
|
emitFullGraph(ws);
|
|
5021
6101
|
// After sending registry & graph also send current module manifest if any
|
|
@@ -5038,16 +6118,148 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5038
6118
|
if (!wss) {
|
|
5039
6119
|
return;
|
|
5040
6120
|
}
|
|
6121
|
+
if (isRuntimeGraphExcludedPath(file)) {
|
|
6122
|
+
return;
|
|
6123
|
+
}
|
|
6124
|
+
// Always-on update timing. Captures the four phases (await,
|
|
6125
|
+
// framework, broadcast, total) plus invalidated module count
|
|
6126
|
+
// and recipient count. Emitted at the end of this function via
|
|
6127
|
+
// `emitHmrUpdateSummary()`. Single line, always-on so a
|
|
6128
|
+
// 6-second `.ts` save is immediately visible without flipping
|
|
6129
|
+
// verbose.
|
|
6130
|
+
const updateRoot = server.config.root || process.cwd();
|
|
6131
|
+
const updateRel = (() => {
|
|
6132
|
+
try {
|
|
6133
|
+
return '/' + path.posix.normalize(path.relative(updateRoot, file)).split(path.sep).join('/');
|
|
6134
|
+
}
|
|
6135
|
+
catch {
|
|
6136
|
+
return file;
|
|
6137
|
+
}
|
|
6138
|
+
})();
|
|
6139
|
+
const updateMetrics = {
|
|
6140
|
+
file: updateRel,
|
|
6141
|
+
kind: classifyHmrUpdateKind(file),
|
|
6142
|
+
t0: Date.now(),
|
|
6143
|
+
tAfterAwait: 0,
|
|
6144
|
+
tAfterFramework: 0,
|
|
6145
|
+
tEnd: 0,
|
|
6146
|
+
invalidated: 0,
|
|
6147
|
+
recipients: 0,
|
|
6148
|
+
// Narrowing diagnostic — populated by the angular branch when
|
|
6149
|
+
// the changed file is `.ts`, otherwise remains undefined and is
|
|
6150
|
+
// omitted from the summary line entirely.
|
|
6151
|
+
narrowed: undefined,
|
|
6152
|
+
emitted: false,
|
|
6153
|
+
};
|
|
6154
|
+
// Broadcast a "pending" notification at the very start of
|
|
6155
|
+
// handleHotUpdate so the client can show the HMR-applying
|
|
6156
|
+
// overlay BEFORE we spend time on graph updates / transforms /
|
|
6157
|
+
// dependency analysis (typically 7–200ms on a warm cache).
|
|
6158
|
+
// Without this, the overlay only appears at `ns:angular-update`
|
|
6159
|
+
// broadcast time and the user perceives a "delayed" reaction
|
|
6160
|
+
// to their save.
|
|
6161
|
+
//
|
|
6162
|
+
// Fire-and-forget: a failed pending broadcast must never
|
|
6163
|
+
// hold up the actual update. The client treats receipt of
|
|
6164
|
+
// `ns:angular-update` (or `ns:css-updates`) as authoritative;
|
|
6165
|
+
// the pending message is purely a UX hint.
|
|
6166
|
+
try {
|
|
6167
|
+
const pendingPayload = JSON.stringify(createHmrPendingMessage({
|
|
6168
|
+
origin: getServerOrigin(server),
|
|
6169
|
+
path: updateMetrics.file,
|
|
6170
|
+
kind: updateMetrics.kind,
|
|
6171
|
+
timestamp: updateMetrics.t0,
|
|
6172
|
+
}));
|
|
6173
|
+
wss.clients.forEach((client) => {
|
|
6174
|
+
if (isSocketClientOpen(client)) {
|
|
6175
|
+
try {
|
|
6176
|
+
client.send(pendingPayload);
|
|
6177
|
+
}
|
|
6178
|
+
catch { }
|
|
6179
|
+
}
|
|
6180
|
+
});
|
|
6181
|
+
}
|
|
6182
|
+
catch { }
|
|
6183
|
+
const emitHmrUpdateSummary = () => {
|
|
6184
|
+
if (updateMetrics.emitted)
|
|
6185
|
+
return;
|
|
6186
|
+
updateMetrics.emitted = true;
|
|
6187
|
+
updateMetrics.tEnd = Date.now();
|
|
6188
|
+
try {
|
|
6189
|
+
const awaitMs = (updateMetrics.tAfterAwait || updateMetrics.t0) - updateMetrics.t0;
|
|
6190
|
+
const frameworkMs = (updateMetrics.tAfterFramework || updateMetrics.tAfterAwait || updateMetrics.t0) - (updateMetrics.tAfterAwait || updateMetrics.t0);
|
|
6191
|
+
const broadcastMs = updateMetrics.tEnd - (updateMetrics.tAfterFramework || updateMetrics.tAfterAwait || updateMetrics.t0);
|
|
6192
|
+
const totalMs = updateMetrics.tEnd - updateMetrics.t0;
|
|
6193
|
+
console.info(formatHmrUpdateSummary({
|
|
6194
|
+
file: updateMetrics.file,
|
|
6195
|
+
kind: updateMetrics.kind,
|
|
6196
|
+
awaitMs,
|
|
6197
|
+
frameworkMs,
|
|
6198
|
+
broadcastMs,
|
|
6199
|
+
totalMs,
|
|
6200
|
+
invalidated: updateMetrics.invalidated,
|
|
6201
|
+
recipients: updateMetrics.recipients,
|
|
6202
|
+
narrowed: updateMetrics.narrowed,
|
|
6203
|
+
}));
|
|
6204
|
+
}
|
|
6205
|
+
catch { }
|
|
6206
|
+
};
|
|
6207
|
+
// The first /ns/m request kicks off populateInitialGraph in the
|
|
6208
|
+
// background. If an HMR update races in before that walk
|
|
6209
|
+
// completes, we'd lose transitive-importer data. Await
|
|
6210
|
+
// completion here so the delta computation below always sees a
|
|
6211
|
+
// populated graph.
|
|
6212
|
+
if (graphInitialPopulationPromise) {
|
|
6213
|
+
try {
|
|
6214
|
+
await graphInitialPopulationPromise;
|
|
6215
|
+
}
|
|
6216
|
+
catch { }
|
|
6217
|
+
}
|
|
6218
|
+
updateMetrics.tAfterAwait = Date.now();
|
|
5041
6219
|
// Graph update for this file change (wrapped to avoid aborting rest of handler)
|
|
5042
6220
|
try {
|
|
5043
|
-
const
|
|
5044
|
-
if (
|
|
5045
|
-
const
|
|
5046
|
-
|
|
5047
|
-
.
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
6221
|
+
const skipAngularHtmlGraphUpdate = ACTIVE_STRATEGY.flavor === 'angular' && /\.(html|htm)$/i.test(file);
|
|
6222
|
+
if (!skipAngularHtmlGraphUpdate) {
|
|
6223
|
+
const graphTargets = collectGraphUpdateModulesForHotUpdate({
|
|
6224
|
+
file,
|
|
6225
|
+
flavor: ACTIVE_STRATEGY.flavor,
|
|
6226
|
+
modules: ctx.modules,
|
|
6227
|
+
getModuleById: (id) => server.moduleGraph.getModuleById(id),
|
|
6228
|
+
verbose,
|
|
6229
|
+
});
|
|
6230
|
+
for (const mod of graphTargets) {
|
|
6231
|
+
if (!mod?.id)
|
|
6232
|
+
continue;
|
|
6233
|
+
try {
|
|
6234
|
+
const deps = Array.from(mod.importedModules || [])
|
|
6235
|
+
.map((m) => (m.id || '').replace(/\?.*$/, ''))
|
|
6236
|
+
.filter(Boolean);
|
|
6237
|
+
const transformed = await server.transformRequest(mod.id);
|
|
6238
|
+
const code = transformed?.code || '';
|
|
6239
|
+
upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps, {
|
|
6240
|
+
emitDeltaOnInsert: true,
|
|
6241
|
+
// Defer the delta broadcast until AFTER the framework
|
|
6242
|
+
// hot-update handler has had a chance to invalidate the
|
|
6243
|
+
// shared transform-request cache + Vite's moduleGraph
|
|
6244
|
+
// for the changed file and its transitive importers.
|
|
6245
|
+
// Otherwise the client races: it receives the delta
|
|
6246
|
+
// (eviction + re-import via tagged URL) before the
|
|
6247
|
+
// server has purged its caches, and the re-import is
|
|
6248
|
+
// served from cache → V8 evaluates the previous save's
|
|
6249
|
+
// transformed code → patchRegistry runs against an
|
|
6250
|
+
// unchanged source → the visible page is "one save
|
|
6251
|
+
// behind". Angular has always taken this path; Solid
|
|
6252
|
+
// needs the same contract because Solid HMR depends
|
|
6253
|
+
// on the client re-fetching the just-changed module
|
|
6254
|
+
// to drive `solid-refresh.patchRegistry`.
|
|
6255
|
+
broadcastDelta: ACTIVE_STRATEGY.flavor !== 'angular' && ACTIVE_STRATEGY.flavor !== 'solid',
|
|
6256
|
+
});
|
|
6257
|
+
}
|
|
6258
|
+
catch (error) {
|
|
6259
|
+
if (verbose)
|
|
6260
|
+
console.warn('[hmr-ws][v2] failed graph update target', mod.id, error);
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
5051
6263
|
}
|
|
5052
6264
|
}
|
|
5053
6265
|
catch (e) {
|
|
@@ -5068,6 +6280,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5068
6280
|
console.log(`[hmr-ws] Hot update for: ${file}`);
|
|
5069
6281
|
// Handle CSS updates
|
|
5070
6282
|
if (file.endsWith('.css')) {
|
|
6283
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
5071
6284
|
try {
|
|
5072
6285
|
let rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
5073
6286
|
const origin = getServerOrigin(server);
|
|
@@ -5084,14 +6297,16 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5084
6297
|
],
|
|
5085
6298
|
};
|
|
5086
6299
|
wss.clients.forEach((client) => {
|
|
5087
|
-
if (client
|
|
6300
|
+
if (isSocketClientOpen(client)) {
|
|
5088
6301
|
client.send(JSON.stringify(msg));
|
|
6302
|
+
updateMetrics.recipients += 1;
|
|
5089
6303
|
}
|
|
5090
6304
|
});
|
|
5091
6305
|
}
|
|
5092
6306
|
catch (error) {
|
|
5093
6307
|
console.warn('[hmr-ws] CSS update failed:', error);
|
|
5094
6308
|
}
|
|
6309
|
+
emitHmrUpdateSummary();
|
|
5095
6310
|
return;
|
|
5096
6311
|
}
|
|
5097
6312
|
// Framework-specific hot update handling
|
|
@@ -5099,31 +6314,281 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5099
6314
|
// For Angular, react to component TS or external template HTML changes under /src
|
|
5100
6315
|
const isHtml = file.endsWith('.html');
|
|
5101
6316
|
const isTs = file.endsWith('.ts');
|
|
6317
|
+
const angularHotUpdateRoots = collectAngularHotUpdateRoots({
|
|
6318
|
+
file,
|
|
6319
|
+
modules: ctx.modules,
|
|
6320
|
+
getModuleById: (id) => server.moduleGraph.getModuleById(id),
|
|
6321
|
+
getModulesByFile: (targetFile) => server.moduleGraph.getModulesByFile?.(targetFile),
|
|
6322
|
+
});
|
|
6323
|
+
if (verbose) {
|
|
6324
|
+
console.info(`[ns-hmr-diag][server] hot-update file=${file} isHtml=${isHtml} isTs=${isTs} ctxModules=${Array.from(ctx.modules || []).length} hotUpdateRoots=${angularHotUpdateRoots.length} (${angularHotUpdateRoots
|
|
6325
|
+
.map((m) => m?.id ?? '(none)')
|
|
6326
|
+
.slice(0, 8)
|
|
6327
|
+
.join(', ')}${angularHotUpdateRoots.length > 8 ? ', …' : ''})`);
|
|
6328
|
+
}
|
|
5102
6329
|
if (!(isHtml || isTs))
|
|
5103
6330
|
return;
|
|
6331
|
+
updateMetrics.invalidated += angularHotUpdateRoots.length;
|
|
6332
|
+
if (angularHotUpdateRoots.length) {
|
|
6333
|
+
for (const mod of angularHotUpdateRoots) {
|
|
6334
|
+
try {
|
|
6335
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6336
|
+
}
|
|
6337
|
+
catch (invalidationError) {
|
|
6338
|
+
if (verbose) {
|
|
6339
|
+
console.warn('[hmr-ws][angular] hot-update root invalidation failed', mod?.id, invalidationError);
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
if (verbose) {
|
|
6344
|
+
console.log('[hmr-ws][angular] invalidated hot-update root modules:', angularHotUpdateRoots.length);
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
const angularTransitiveInvalidationRoots = (angularHotUpdateRoots.length ? angularHotUpdateRoots : ctx.modules);
|
|
6348
|
+
// Read the source for `.ts/.tsx/.js/.jsx` edits so
|
|
6349
|
+
// `shouldInvalidateAngularTransitiveImporters` can
|
|
6350
|
+
// distinguish leaf modules (constants/utils) from real
|
|
6351
|
+
// Angular files. If `ctx.read()` throws (file deleted, race
|
|
6352
|
+
// against the watcher), `angularChangedSource` stays
|
|
6353
|
+
// undefined and we fall back to the conservative "always
|
|
6354
|
+
// invalidate transitively" behavior.
|
|
6355
|
+
let angularChangedSource;
|
|
6356
|
+
if (isTs) {
|
|
6357
|
+
try {
|
|
6358
|
+
angularChangedSource = await ctx.read();
|
|
6359
|
+
}
|
|
6360
|
+
catch {
|
|
6361
|
+
angularChangedSource = undefined;
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
const angularNeedsTransitive = shouldInvalidateAngularTransitiveImporters({
|
|
6365
|
+
flavor: ACTIVE_STRATEGY.flavor,
|
|
6366
|
+
file,
|
|
6367
|
+
source: angularChangedSource,
|
|
6368
|
+
});
|
|
6369
|
+
// Surface the narrowing decision on every `.ts` Angular hot
|
|
6370
|
+
// update (HTML routes always invalidate transitively and
|
|
6371
|
+
// aren't subject to narrowing, so we leave them as
|
|
6372
|
+
// `undefined` — the field is omitted from the summary line).
|
|
6373
|
+
// The boolean is the inverse of `angularNeedsTransitive`
|
|
6374
|
+
// because "needs transitive" is the broad (un-narrowed)
|
|
6375
|
+
// behavior.
|
|
6376
|
+
if (isTs) {
|
|
6377
|
+
updateMetrics.narrowed = !angularNeedsTransitive;
|
|
6378
|
+
}
|
|
6379
|
+
// Stable URL + Explicit Invalidation:
|
|
6380
|
+
//
|
|
6381
|
+
// Compute the transitive importer closure ONCE here and reuse
|
|
6382
|
+
// it for (a) `server.moduleGraph.invalidateModule` (so Vite's
|
|
6383
|
+
// transform pipeline re-runs on next request), (b) the shared
|
|
6384
|
+
// transform-request cache, and (c) the runtime eviction set
|
|
6385
|
+
// we broadcast in `ns:angular-update`. Consolidating this
|
|
6386
|
+
// removes a redundant graph walk and guarantees the three
|
|
6387
|
+
// consumers see the exact same set of importers (otherwise a
|
|
6388
|
+
// late module-graph mutation between calls could leave an
|
|
6389
|
+
// asymmetric narrowed/broad mix).
|
|
6390
|
+
//
|
|
6391
|
+
// We separate Vite-transform narrowing from runtime eviction:
|
|
6392
|
+
// `angularNeedsTransitive` answers the question "does the
|
|
6393
|
+
// changed file's symbol shape change such that importers
|
|
6394
|
+
// must be re-transformed by Vite?". The runtime, however,
|
|
6395
|
+
// has a stricter requirement: ESM live bindings only refresh
|
|
6396
|
+
// if the importing module re-evaluates inside V8. A
|
|
6397
|
+
// constants file with no Angular decorator does NOT need a
|
|
6398
|
+
// Vite re-transform of its importers (their compiled JS is
|
|
6399
|
+
// identical), but its importers still hold stale bindings to
|
|
6400
|
+
// the OLD constants Module record. After eviction + re-import
|
|
6401
|
+
// of `main.ts`, V8 sees the cached importers, returns them
|
|
6402
|
+
// unchanged, and they continue to read the OLD values. The
|
|
6403
|
+
// user-visible symptom: HMR completes successfully, logs are
|
|
6404
|
+
// clean, but the simulator does not reflect the change.
|
|
6405
|
+
//
|
|
6406
|
+
// The fix: ALWAYS compute the transitive importer closure
|
|
6407
|
+
// for runtime eviction. Only skip Vite's
|
|
6408
|
+
// `moduleGraph.invalidate` + transform-cache purge when
|
|
6409
|
+
// `angularNeedsTransitive` is false — those are the genuine
|
|
6410
|
+
// narrowing wins (saves re-transform work on the server).
|
|
6411
|
+
// The eviction set always includes importers so V8 re-fetches
|
|
6412
|
+
// and re-binds them.
|
|
6413
|
+
if (verbose) {
|
|
6414
|
+
console.info(`[ns-hmr-diag][server] angularNeedsTransitive=${angularNeedsTransitive} (file=${path.basename(file)})`);
|
|
6415
|
+
}
|
|
6416
|
+
let transitiveImporters = [];
|
|
6417
|
+
try {
|
|
6418
|
+
transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
6419
|
+
modules: angularTransitiveInvalidationRoots,
|
|
6420
|
+
isExcluded: (id) => id.includes('/node_modules/'),
|
|
6421
|
+
maxDepth: 16,
|
|
6422
|
+
});
|
|
6423
|
+
if (verbose) {
|
|
6424
|
+
console.info(`[ns-hmr-diag][server] transitiveImporters count=${transitiveImporters.length} firstN=`, transitiveImporters.slice(0, 16).map((m) => m?.id ?? '(none)'));
|
|
6425
|
+
}
|
|
6426
|
+
if (angularNeedsTransitive) {
|
|
6427
|
+
updateMetrics.invalidated += transitiveImporters.length;
|
|
6428
|
+
for (const mod of transitiveImporters) {
|
|
6429
|
+
try {
|
|
6430
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6431
|
+
}
|
|
6432
|
+
catch (invalidationError) {
|
|
6433
|
+
if (verbose) {
|
|
6434
|
+
console.warn('[hmr-ws][angular] transitive importer invalidation failed', mod?.id, invalidationError);
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
if (verbose && transitiveImporters.length) {
|
|
6439
|
+
console.log('[hmr-ws][angular] invalidated transitive importers:', transitiveImporters.length);
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
else if (isTs && typeof angularChangedSource === 'string') {
|
|
6443
|
+
// Surfacing this log unconditionally lets the user
|
|
6444
|
+
// immediately confirm whether narrowing fired for a
|
|
6445
|
+
// given `.ts` edit (the summary line below still
|
|
6446
|
+
// emits `narrowed=yes`/`no`, but having both makes
|
|
6447
|
+
// the decision easier to spot in noisy logs and lets
|
|
6448
|
+
// the user diff scenarios without flipping
|
|
6449
|
+
// `NS_HMR_VERBOSE=true`).
|
|
6450
|
+
//
|
|
6451
|
+
// Narrowing means "skip Vite re-transform" (the
|
|
6452
|
+
// importers still get evicted from the V8 module
|
|
6453
|
+
// registry so live bindings refresh). The importer
|
|
6454
|
+
// count is appended so the distinction is visible.
|
|
6455
|
+
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)`);
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
catch (error) {
|
|
6459
|
+
if (verbose)
|
|
6460
|
+
console.warn('[hmr-ws][angular] transitive importer collection failed', error);
|
|
6461
|
+
}
|
|
6462
|
+
try {
|
|
6463
|
+
// Purge shared transform cache for the changed file +
|
|
6464
|
+
// hot-update roots unconditionally (their transform
|
|
6465
|
+
// output IS different now). Transitive importers are
|
|
6466
|
+
// only purged when narrowing decides their output may
|
|
6467
|
+
// have changed; otherwise their cached transforms are
|
|
6468
|
+
// still valid (compiled JS is identical even though the
|
|
6469
|
+
// runtime must re-evaluate them to refresh ESM bindings).
|
|
6470
|
+
const transformCacheInvalidationUrls = new Set(collectAngularTransformCacheInvalidationUrls({
|
|
6471
|
+
file,
|
|
6472
|
+
isTs,
|
|
6473
|
+
hotUpdateRoots: angularHotUpdateRoots,
|
|
6474
|
+
transitiveImporters: angularNeedsTransitive ? transitiveImporters : [],
|
|
6475
|
+
projectRoot: server.config.root || process.cwd(),
|
|
6476
|
+
}));
|
|
6477
|
+
if (transformCacheInvalidationUrls.size) {
|
|
6478
|
+
sharedTransformRequest.invalidateMany(transformCacheInvalidationUrls);
|
|
6479
|
+
if (verbose) {
|
|
6480
|
+
console.log('[hmr-ws][angular] purged shared transform cache entries:', transformCacheInvalidationUrls.size);
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
catch (error) {
|
|
6485
|
+
if (verbose)
|
|
6486
|
+
console.warn('[hmr-ws][angular] shared transform cache purge failed', error);
|
|
6487
|
+
}
|
|
6488
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
5104
6489
|
try {
|
|
5105
6490
|
const root = server.config.root || process.cwd();
|
|
5106
6491
|
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6492
|
+
rememberAngularReloadSuppression(root, file);
|
|
5107
6493
|
const origin = getServerOrigin(server);
|
|
6494
|
+
const bootstrapEntryRel = getBootstrapEntryRelPath();
|
|
6495
|
+
// Stable URL + Explicit Invalidation:
|
|
6496
|
+
//
|
|
6497
|
+
// `evictPaths` is the canonical list of `/ns/m/<rel>` URLs
|
|
6498
|
+
// the runtime must drop from `g_moduleRegistry` before
|
|
6499
|
+
// re-importing `importerEntry`. Older versions of the
|
|
6500
|
+
// server signaled invalidation by bumping a global
|
|
6501
|
+
// `graphVersion` counter and embedding it in every URL —
|
|
6502
|
+
// but V8 keys the module registry by full URL, so a v1 →
|
|
6503
|
+
// v2 bump effectively flushed the entire dependency
|
|
6504
|
+
// graph from the cache and forced the runtime to
|
|
6505
|
+
// re-fetch + re-eval every transitively-imported module
|
|
6506
|
+
// on each save (~3s HMR cycles, dominated by Vite's
|
|
6507
|
+
// single-threaded transform pipeline). The new model:
|
|
6508
|
+
//
|
|
6509
|
+
// 1. URLs are stable: `/ns/m/<rel>` everywhere, no `vN`.
|
|
6510
|
+
// 2. The server walks the inverse-dependency closure and
|
|
6511
|
+
// sends only the modules that actually need to be
|
|
6512
|
+
// re-evaluated (typically O(1) for component edits,
|
|
6513
|
+
// or the changed file + entry for narrowed edits).
|
|
6514
|
+
// 3. The client calls `__nsInvalidateModules(evictPaths)`
|
|
6515
|
+
// and re-imports `importerEntry`, which causes V8 to
|
|
6516
|
+
// refetch ONLY those modules. Everything else stays
|
|
6517
|
+
// hot in the registry.
|
|
6518
|
+
//
|
|
6519
|
+
// Invariants enforced by `collectAngularEvictionUrls`:
|
|
6520
|
+
// - Always includes the changed file (so the new source
|
|
6521
|
+
// is fetched).
|
|
6522
|
+
// - Always includes `importerEntry` (so re-import
|
|
6523
|
+
// re-evaluates).
|
|
6524
|
+
// - Excludes node_modules (vendor packages are stable).
|
|
6525
|
+
// - Excludes virtual / runtime-graph-excluded ids.
|
|
6526
|
+
// - Origin-prefixed: `http://host:port/ns/m/<rel>`.
|
|
6527
|
+
let evictPaths = [];
|
|
6528
|
+
try {
|
|
6529
|
+
evictPaths = collectAngularEvictionUrls({
|
|
6530
|
+
file,
|
|
6531
|
+
hotUpdateRoots: angularHotUpdateRoots,
|
|
6532
|
+
transitiveImporters,
|
|
6533
|
+
projectRoot: root,
|
|
6534
|
+
origin,
|
|
6535
|
+
bootstrapEntry: bootstrapEntryRel,
|
|
6536
|
+
});
|
|
6537
|
+
}
|
|
6538
|
+
catch (error) {
|
|
6539
|
+
if (verbose) {
|
|
6540
|
+
console.warn('[ns-hmr-diag][server] eviction set computation failed', error);
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
if (verbose) {
|
|
6544
|
+
try {
|
|
6545
|
+
const tsRel = rel.replace(/\.(html|htm)$/i, '.ts');
|
|
6546
|
+
const jsRel = rel.replace(/\.(html|htm)$/i, '.js');
|
|
6547
|
+
const containsRelatedTs = evictPaths.some((u) => u.endsWith(tsRel));
|
|
6548
|
+
const containsRelatedJs = evictPaths.some((u) => u.endsWith(jsRel));
|
|
6549
|
+
const sample = evictPaths.slice(0, 32);
|
|
6550
|
+
console.info(`[ns-hmr-diag][server] evict-set count=${evictPaths.length} importerEntry=${bootstrapEntryRel ?? '(none)'} containsRelatedTs=${containsRelatedTs} containsRelatedJs=${containsRelatedJs} firstN=`, sample);
|
|
6551
|
+
if (evictPaths.length > sample.length) {
|
|
6552
|
+
console.info(`[ns-hmr-diag][server] evict-set hidden=${evictPaths.length - sample.length} (showed first ${sample.length})`);
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
catch { }
|
|
6556
|
+
}
|
|
5108
6557
|
const msg = {
|
|
5109
6558
|
type: 'ns:angular-update',
|
|
5110
6559
|
origin,
|
|
5111
6560
|
path: rel,
|
|
6561
|
+
version: graphVersion,
|
|
5112
6562
|
timestamp: Date.now(),
|
|
6563
|
+
evictPaths,
|
|
6564
|
+
importerEntry: bootstrapEntryRel,
|
|
5113
6565
|
};
|
|
6566
|
+
if (verbose) {
|
|
6567
|
+
console.log('[hmr-ws][angular] broadcasting update', Array.from(wss.clients || []).map((client) => ({
|
|
6568
|
+
role: getHmrSocketRole(client),
|
|
6569
|
+
readyState: client.readyState,
|
|
6570
|
+
openState: client.OPEN,
|
|
6571
|
+
})));
|
|
6572
|
+
}
|
|
5114
6573
|
wss.clients.forEach((client) => {
|
|
5115
|
-
if (client
|
|
6574
|
+
if (isSocketClientOpen(client)) {
|
|
5116
6575
|
client.send(JSON.stringify(msg));
|
|
6576
|
+
updateMetrics.recipients += 1;
|
|
5117
6577
|
}
|
|
5118
6578
|
});
|
|
5119
6579
|
}
|
|
5120
6580
|
catch (error) {
|
|
5121
6581
|
console.warn('[hmr-ws][angular] update failed:', error);
|
|
5122
6582
|
}
|
|
6583
|
+
emitHmrUpdateSummary();
|
|
6584
|
+
if (shouldSuppressDefaultViteHotUpdate({ flavor: ACTIVE_STRATEGY.flavor, file })) {
|
|
6585
|
+
return [];
|
|
6586
|
+
}
|
|
5123
6587
|
return;
|
|
5124
6588
|
}
|
|
5125
6589
|
// TypeScript flavor: emit generic graph delta for app XML/TS/style changes
|
|
5126
6590
|
if (ACTIVE_STRATEGY.flavor === 'typescript') {
|
|
6591
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
5127
6592
|
try {
|
|
5128
6593
|
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
5129
6594
|
if (verbose)
|
|
@@ -5131,15 +6596,236 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5131
6596
|
// Treat the changed file itself as a graph module with no deps. We only
|
|
5132
6597
|
// care that its hash/identity changes so the client sees a delta and can
|
|
5133
6598
|
// 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], []);
|
|
6599
|
+
upsertGraphModule(rel, '', [], { emitDeltaOnInsert: true });
|
|
5138
6600
|
}
|
|
5139
6601
|
catch (e) {
|
|
5140
6602
|
if (verbose)
|
|
5141
6603
|
console.warn('[hmr-ws][ts] failed to emit delta for', file, e);
|
|
5142
6604
|
}
|
|
6605
|
+
emitHmrUpdateSummary();
|
|
6606
|
+
return;
|
|
6607
|
+
}
|
|
6608
|
+
// Solid flavor: emit graph delta for app TSX/TS/JSX file changes.
|
|
6609
|
+
// The common graph-update block above (moduleGraph lookup) may have
|
|
6610
|
+
// already emitted a delta if the file was in Vite's module graph.
|
|
6611
|
+
// This handler ensures a delta is emitted even if the module wasn't
|
|
6612
|
+
// found (e.g. new file, or moduleGraph mismatch), and provides
|
|
6613
|
+
// Solid-specific logging. The client-side processQueue handles
|
|
6614
|
+
// propagation from non-component .ts files to .tsx component boundaries.
|
|
6615
|
+
if (ACTIVE_STRATEGY.flavor === 'solid') {
|
|
6616
|
+
const isSolidFile = /\.(tsx?|jsx?)$/i.test(file);
|
|
6617
|
+
if (!isSolidFile)
|
|
6618
|
+
return;
|
|
6619
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
6620
|
+
try {
|
|
6621
|
+
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6622
|
+
if (verbose)
|
|
6623
|
+
console.log('[hmr-ws][solid] app file hot update', { file, rel });
|
|
6624
|
+
// If the common block already upserted (hash changed), this will
|
|
6625
|
+
// detect unchanged hash and no-op. If the common block missed it
|
|
6626
|
+
// (module not in Vite's graph), this forces the delta emission.
|
|
6627
|
+
const normalizedId = normalizeGraphId(rel);
|
|
6628
|
+
const existing = graph.get(normalizedId);
|
|
6629
|
+
if (!existing) {
|
|
6630
|
+
// Module not in graph yet — force upsert with timestamp-based
|
|
6631
|
+
// hash so the client sees a change.
|
|
6632
|
+
upsertGraphModule(rel, `/* solid-hmr ${Date.now()} */`, [], { emitDeltaOnInsert: true });
|
|
6633
|
+
}
|
|
6634
|
+
// Log what we're sending so devs can trace the flow on the server side.
|
|
6635
|
+
if (verbose) {
|
|
6636
|
+
const gm = graph.get(normalizedId);
|
|
6637
|
+
console.log('[hmr-ws][solid] delta module', { id: gm?.id, hash: gm?.hash });
|
|
6638
|
+
}
|
|
6639
|
+
// Purge the shared transform-request cache AND Vite's own
|
|
6640
|
+
// moduleGraph transformResult cache for the changed file
|
|
6641
|
+
// AND every transitive importer.
|
|
6642
|
+
//
|
|
6643
|
+
// Why this matters for Solid HMR specifically:
|
|
6644
|
+
// - The HMR client evicts V8's module cache for the
|
|
6645
|
+
// canonical /ns/m/<path> URL and re-imports the module.
|
|
6646
|
+
// - The dev server resolves /ns/m/* by calling
|
|
6647
|
+
// `sharedTransformRequest(...)`, which has a 60s TTL on
|
|
6648
|
+
// transform results to amortize cost across HMR
|
|
6649
|
+
// cycles. The shared cache wraps `server.transformRequest`,
|
|
6650
|
+
// which itself caches the compiled output on each
|
|
6651
|
+
// `ModuleNode.transformResult`. Both layers must be
|
|
6652
|
+
// invalidated, or the re-import resolves to whatever
|
|
6653
|
+
// the previous save populated.
|
|
6654
|
+
// - Without invalidation at *both* layers, the second
|
|
6655
|
+
// save of a file within the cache window returns the
|
|
6656
|
+
// FIRST save's transform — V8 evaluates stale code,
|
|
6657
|
+
// `solid-refresh.patchRegistry` runs against an
|
|
6658
|
+
// unchanged source body, and the visible page picks
|
|
6659
|
+
// up the previous save's edit instead of the current
|
|
6660
|
+
// one (the "one-save-behind" symptom users reported).
|
|
6661
|
+
//
|
|
6662
|
+
// Critically, transitive importers must also be invalidated
|
|
6663
|
+
// because TanStack file-based routing (and similar frameworks)
|
|
6664
|
+
// use route files that statically import their components.
|
|
6665
|
+
// When `home.tsx` changes, `routes/index.tsx`'s transform
|
|
6666
|
+
// output references the imported home module identity. Even
|
|
6667
|
+
// though the route file's source bytes did not change, its
|
|
6668
|
+
// *resolved* import target has — and its cached transform
|
|
6669
|
+
// might still encode the previous resolution. Forcing a
|
|
6670
|
+
// fresh transform of the importer guarantees the route
|
|
6671
|
+
// file's `import Home from ...` re-resolves against the
|
|
6672
|
+
// freshly evaluated home module on V8 side.
|
|
6673
|
+
//
|
|
6674
|
+
// The Angular path performs the equivalent purge via
|
|
6675
|
+
// `collectAngularTransformCacheInvalidationUrls` /
|
|
6676
|
+
// `sharedTransformRequest.invalidateMany`. We replicate
|
|
6677
|
+
// that contract for Solid here. The transitive walk is
|
|
6678
|
+
// bounded the same way (max depth 16, node_modules /
|
|
6679
|
+
// virtual ids excluded) so vendor packages stay hot.
|
|
6680
|
+
try {
|
|
6681
|
+
const projectRoot = server.config.root || process.cwd();
|
|
6682
|
+
const cacheInvalidationUrls = new Set();
|
|
6683
|
+
const addCacheKey = (rawId) => {
|
|
6684
|
+
const id = String(rawId || '');
|
|
6685
|
+
if (!id)
|
|
6686
|
+
return;
|
|
6687
|
+
const cacheKey = canonicalizeTransformRequestCacheKey(id, projectRoot);
|
|
6688
|
+
cacheInvalidationUrls.add(cacheKey);
|
|
6689
|
+
const noQuery = cacheKey.replace(/\?.*$/, '');
|
|
6690
|
+
const stripped = noQuery.replace(/\.(?:[mc]?[jt]sx?)$/i, '');
|
|
6691
|
+
if (stripped !== noQuery) {
|
|
6692
|
+
cacheInvalidationUrls.add(stripped);
|
|
6693
|
+
}
|
|
6694
|
+
};
|
|
6695
|
+
addCacheKey(file);
|
|
6696
|
+
const rootModules = server.moduleGraph.getModulesByFile?.(file);
|
|
6697
|
+
const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
6698
|
+
modules: rootModules ? Array.from(rootModules) : [],
|
|
6699
|
+
isExcluded: (id) => id.includes('/node_modules/') || isRuntimeGraphExcludedPath(id),
|
|
6700
|
+
maxDepth: 16,
|
|
6701
|
+
});
|
|
6702
|
+
// Invalidate Vite's moduleGraph for the changed file +
|
|
6703
|
+
// every transitive importer so `server.transformRequest`
|
|
6704
|
+
// re-runs the transform pipeline instead of returning
|
|
6705
|
+
// the cached `ModuleNode.transformResult`. We call
|
|
6706
|
+
// `onFileChange` (Vite's authoritative file-changed
|
|
6707
|
+
// signal — walks all module variants including `?v=`,
|
|
6708
|
+
// `?import`, `?t=`) AND per-module `invalidateModule`
|
|
6709
|
+
// for transitive importers (which onFileChange
|
|
6710
|
+
// doesn't reach).
|
|
6711
|
+
try {
|
|
6712
|
+
server.moduleGraph.onFileChange(file);
|
|
6713
|
+
}
|
|
6714
|
+
catch { }
|
|
6715
|
+
if (rootModules) {
|
|
6716
|
+
for (const mod of rootModules) {
|
|
6717
|
+
try {
|
|
6718
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6719
|
+
}
|
|
6720
|
+
catch { }
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
for (const mod of transitiveImporters) {
|
|
6724
|
+
addCacheKey(mod?.id);
|
|
6725
|
+
try {
|
|
6726
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6727
|
+
}
|
|
6728
|
+
catch { }
|
|
6729
|
+
}
|
|
6730
|
+
if (cacheInvalidationUrls.size && sharedTransformRequest) {
|
|
6731
|
+
sharedTransformRequest.invalidateMany(cacheInvalidationUrls);
|
|
6732
|
+
if (verbose) {
|
|
6733
|
+
console.log('[hmr-ws][solid] purged shared transform cache entries:', cacheInvalidationUrls.size, 'transitiveImporters=', transitiveImporters.length);
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
// Sledgehammer: nuke EVERY entry in sharedTransformRequest's
|
|
6737
|
+
// result cache. The targeted `invalidateMany` above only
|
|
6738
|
+
// clears keys we know about. The `/ns/m/` handler iterates
|
|
6739
|
+
// a long list of candidate extensions (`.ts`, `.js`, `.tsx`,
|
|
6740
|
+
// `.jsx`, `.mjs`, `.mts`, `.cts`, `.vue`, `index.*`) and
|
|
6741
|
+
// EACH candidate is a separate cache key. If a previous
|
|
6742
|
+
// serve populated cache for `/src/components/home.js` (via
|
|
6743
|
+
// extension fallback that resolves to `home.tsx`), our
|
|
6744
|
+
// targeted invalidate misses it and iOS HITs the stale
|
|
6745
|
+
// entry — serving the previous save's transformed code.
|
|
6746
|
+
try {
|
|
6747
|
+
sharedTransformRequest.clear();
|
|
6748
|
+
}
|
|
6749
|
+
catch { }
|
|
6750
|
+
}
|
|
6751
|
+
catch (e) {
|
|
6752
|
+
if (verbose)
|
|
6753
|
+
console.warn('[hmr-ws][solid] transform cache invalidation failed', e);
|
|
6754
|
+
}
|
|
6755
|
+
// Re-run the transform AFTER all caches are invalidated, then
|
|
6756
|
+
// re-upsert the graph so the broadcast hash matches the freshly-
|
|
6757
|
+
// transformed content. The common upsert block above ran
|
|
6758
|
+
// `server.transformRequest` BEFORE invalidation — at that
|
|
6759
|
+
// moment Vite's auto-invalidate hadn't fired yet (it runs after
|
|
6760
|
+
// `plugin.handleHotUpdate`), so the result it cached was the
|
|
6761
|
+
// previous save's. Without this re-transform, the broadcast
|
|
6762
|
+
// carries a stale hash and iOS evaluates the previous save's
|
|
6763
|
+
// bytes ("one save behind").
|
|
6764
|
+
//
|
|
6765
|
+
// We pre-populate the cache for every extension variant Vite's
|
|
6766
|
+
// /ns/m/ handler might try, so the first request from iOS hits
|
|
6767
|
+
// fresh data regardless of which candidate it resolves first.
|
|
6768
|
+
try {
|
|
6769
|
+
const ext = file.match(/\.(?:[mc]?[jt]sx?)$/i)?.[0] || '';
|
|
6770
|
+
const baseSpec = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6771
|
+
const baseNoExt = ext ? baseSpec.replace(/\.(?:[mc]?[jt]sx?)$/i, '') : baseSpec;
|
|
6772
|
+
const candidates = Array.from(new Set([
|
|
6773
|
+
baseSpec,
|
|
6774
|
+
baseNoExt,
|
|
6775
|
+
baseNoExt + '.ts',
|
|
6776
|
+
baseNoExt + '.tsx',
|
|
6777
|
+
baseNoExt + '.js',
|
|
6778
|
+
baseNoExt + '.jsx',
|
|
6779
|
+
baseNoExt + '.mjs',
|
|
6780
|
+
baseNoExt + '.mts',
|
|
6781
|
+
baseNoExt + '.cts',
|
|
6782
|
+
file,
|
|
6783
|
+
]));
|
|
6784
|
+
let freshCode = '';
|
|
6785
|
+
for (const cand of candidates) {
|
|
6786
|
+
try {
|
|
6787
|
+
const fresh = await sharedTransformRequest(cand, 30000);
|
|
6788
|
+
if (fresh?.code && !freshCode)
|
|
6789
|
+
freshCode = fresh.code;
|
|
6790
|
+
}
|
|
6791
|
+
catch { }
|
|
6792
|
+
}
|
|
6793
|
+
if (freshCode) {
|
|
6794
|
+
const existingGm = graph.get(normalizedId);
|
|
6795
|
+
const existingDeps = existingGm?.deps || [];
|
|
6796
|
+
upsertGraphModule(normalizedId, freshCode, existingDeps, {
|
|
6797
|
+
broadcastDelta: false,
|
|
6798
|
+
});
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
catch (e) {
|
|
6802
|
+
if (verbose)
|
|
6803
|
+
console.warn('[hmr-ws][solid] post-invalidation re-transform failed', e);
|
|
6804
|
+
}
|
|
6805
|
+
// Broadcast the (now-fresh) delta. Suppressing this in the
|
|
6806
|
+
// common upsert block (`broadcastDelta: ACTIVE_STRATEGY.flavor
|
|
6807
|
+
// !== 'solid'`) and emitting it here ensures the client's
|
|
6808
|
+
// eviction + re-import doesn't race the server's cache
|
|
6809
|
+
// invalidation.
|
|
6810
|
+
try {
|
|
6811
|
+
const gm = graph.get(normalizedId);
|
|
6812
|
+
if (gm) {
|
|
6813
|
+
emitDelta([gm], []);
|
|
6814
|
+
if (verbose) {
|
|
6815
|
+
console.log('[hmr-ws][solid] broadcast delta after cache invalidation', { id: gm.id, hash: gm.hash });
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
catch (e) {
|
|
6820
|
+
if (verbose)
|
|
6821
|
+
console.warn('[hmr-ws][solid] post-invalidation broadcast failed', e);
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
6824
|
+
catch (e) {
|
|
6825
|
+
if (verbose)
|
|
6826
|
+
console.warn('[hmr-ws][solid] failed to handle hot update for', file, e);
|
|
6827
|
+
}
|
|
6828
|
+
emitHmrUpdateSummary();
|
|
5143
6829
|
return;
|
|
5144
6830
|
}
|
|
5145
6831
|
// Handle .vue file updates
|
|
@@ -5148,7 +6834,8 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5148
6834
|
console.log('[hmr-ws] Not a .vue file, skipping');
|
|
5149
6835
|
return;
|
|
5150
6836
|
}
|
|
5151
|
-
|
|
6837
|
+
if (verbose)
|
|
6838
|
+
console.log('[hmr-ws] Processing .vue file update...');
|
|
5152
6839
|
try {
|
|
5153
6840
|
const root = server.config.root || process.cwd();
|
|
5154
6841
|
let rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
@@ -5249,6 +6936,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5249
6936
|
// Rewrite ONLY .vue imports (everything else is now inlined)
|
|
5250
6937
|
const projectRoot = server.config.root || process.cwd();
|
|
5251
6938
|
code = rewriteImports(code, rel, sfcFileMap, depFileMap, projectRoot, opts.verbose, undefined);
|
|
6939
|
+
upsertGraphModule(rel, code, [...deps, ...vueDeps]);
|
|
5252
6940
|
// Add HMR runtime prelude (CRITICAL for runtime)
|
|
5253
6941
|
const hmrPrelude = `
|
|
5254
6942
|
// Embedded HMR Runtime for NativeScript runtime
|
|
@@ -5291,7 +6979,7 @@ if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
|
5291
6979
|
if (typeof spec === 'string' && /^(?:https?:)\/\//.test(spec)) {
|
|
5292
6980
|
const err = new Error('[ns-hmr][require-guard] require of URL: ' + spec + ' via ' + label);
|
|
5293
6981
|
const stack = err.stack || '';
|
|
5294
|
-
|
|
6982
|
+
console.error(err.message + '\n' + stack);
|
|
5295
6983
|
try { g.__NS_REQUIRE_GUARD_LAST__ = { spec, stack, label, ts: Date.now() }; } catch {}
|
|
5296
6984
|
}
|
|
5297
6985
|
} catch {}
|
|
@@ -5324,7 +7012,7 @@ if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
|
5324
7012
|
version: graphVersion,
|
|
5325
7013
|
};
|
|
5326
7014
|
wss.clients.forEach((client) => {
|
|
5327
|
-
if (client
|
|
7015
|
+
if (isSocketClientOpen(client)) {
|
|
5328
7016
|
client.send(JSON.stringify(registryUpdateMsg));
|
|
5329
7017
|
}
|
|
5330
7018
|
});
|
|
@@ -5424,6 +7112,10 @@ if (typeof __VUE_HMR_RUNTIME__ === 'undefined') {
|
|
|
5424
7112
|
console.warn('[hmr-ws] HMR update failed:', error);
|
|
5425
7113
|
console.error(error);
|
|
5426
7114
|
}
|
|
7115
|
+
// Vue path emits update summary at the end of the function so
|
|
7116
|
+
// every framework branch gets exactly one log line. Idempotent
|
|
7117
|
+
// — if any branch already emitted, this is a no-op.
|
|
7118
|
+
emitHmrUpdateSummary();
|
|
5427
7119
|
// CRITICAL: Return empty array to prevent Vite's default HMR
|
|
5428
7120
|
return [];
|
|
5429
7121
|
},
|
|
@@ -5495,4 +7187,6 @@ function getServerOrigin(server) {
|
|
|
5495
7187
|
// Test-only export: allow unit tests to run the sanitizer on snippets without booting a server
|
|
5496
7188
|
// Safe in production builds; this is a named export that tests can import explicitly.
|
|
5497
7189
|
export const __test_processCodeForDevice = processCodeForDevice;
|
|
7190
|
+
export const __test_resolveVendorRouting = resolveVendorRouting;
|
|
7191
|
+
export const __test_getBlockedDeviceNodeModulesReason = getBlockedDeviceNodeModulesReason;
|
|
5498
7192
|
//# sourceMappingURL=websocket.js.map
|