@nativescript/vite 8.0.0-alpha.1 → 8.0.0-alpha.2
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 +286 -119
- package/configuration/angular.js.map +1 -1
- package/configuration/base.js +40 -23
- package/configuration/base.js.map +1 -1
- package/configuration/javascript.js +3 -3
- package/configuration/javascript.js.map +1 -1
- package/configuration/solid.js +7 -0
- package/configuration/solid.js.map +1 -1
- package/configuration/typescript.js +3 -3
- package/configuration/typescript.js.map +1 -1
- package/helpers/angular/angular-linker.js +39 -34
- package/helpers/angular/angular-linker.js.map +1 -1
- 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/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/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/main-entry.d.ts +4 -2
- package/helpers/main-entry.js +86 -10
- 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 +12 -1
- package/helpers/nativeclass-transformer-plugin.js +175 -36
- package/helpers/nativeclass-transformer-plugin.js.map +1 -1
- package/hmr/client/css-handler.js +60 -20
- package/hmr/client/css-handler.js.map +1 -1
- package/hmr/client/index.js +453 -5
- package/hmr/client/index.js.map +1 -1
- package/hmr/entry-runtime.d.ts +10 -0
- package/hmr/entry-runtime.js +263 -21
- package/hmr/entry-runtime.js.map +1 -1
- package/hmr/frameworks/angular/client/index.js +22 -18
- package/hmr/frameworks/angular/client/index.js.map +1 -1
- package/hmr/frameworks/angular/server/linker.js +36 -2
- package/hmr/frameworks/angular/server/linker.js.map +1 -1
- package/hmr/helpers/ast-normalizer.js +22 -10
- package/hmr/helpers/ast-normalizer.js.map +1 -1
- package/hmr/server/constants.d.ts +1 -0
- package/hmr/server/constants.js +2 -0
- package/hmr/server/constants.js.map +1 -1
- package/hmr/server/core-sanitize.d.ts +42 -0
- package/hmr/server/core-sanitize.js +189 -0
- package/hmr/server/core-sanitize.js.map +1 -1
- package/hmr/server/import-map.d.ts +65 -0
- package/hmr/server/import-map.js +213 -0
- package/hmr/server/import-map.js.map +1 -0
- package/hmr/server/websocket.d.ts +79 -3
- package/hmr/server/websocket.js +1888 -233
- package/hmr/server/websocket.js.map +1 -1
- package/hmr/shared/runtime/dev-overlay.d.ts +38 -0
- package/hmr/shared/runtime/dev-overlay.js +664 -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 +66 -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.js +175 -53
- package/hmr/shared/runtime/root-placeholder.js.map +1 -1
- package/hmr/shared/runtime/vendor-bootstrap.js +51 -6
- package/hmr/shared/runtime/vendor-bootstrap.js.map +1 -1
- package/hmr/shared/vendor/manifest.d.ts +5 -0
- package/hmr/shared/vendor/manifest.js +339 -13
- 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/package.json +6 -2
- 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,5 +1,5 @@
|
|
|
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
3
|
// AST tooling for robust transformations
|
|
4
4
|
import { parse as babelParse } from '@babel/parser';
|
|
5
5
|
import { genCode } from '../helpers/babel.js';
|
|
@@ -30,6 +30,23 @@ import { typescriptServerStrategy } from '../frameworks/typescript/server/strate
|
|
|
30
30
|
import { buildInlineTemplateBlock, createProcessSfcCode, extractTemplateRender, processTemplateVariantMinimal } from '../frameworks/vue/server/sfc-transforms.js';
|
|
31
31
|
import { astExtractImportsAndStripTypes } from '../helpers/ast-extract.js';
|
|
32
32
|
import { getProjectAppPath, getProjectAppRelativePath, getProjectAppVirtualPath } from '../../helpers/utils.js';
|
|
33
|
+
import { buildRuntimeConfig, generateImportMap } from './import-map.js';
|
|
34
|
+
import { getCliFlags } from '../../helpers/cli-flags.js';
|
|
35
|
+
// Build a serialized process.env object from CLI --env.* flags.
|
|
36
|
+
// This is injected into every HTTP-served module so app code referencing
|
|
37
|
+
// process.env.TEST_ENV (etc.) works on device in HMR dev mode.
|
|
38
|
+
const __processEnvEntries = { NODE_ENV: 'development' };
|
|
39
|
+
try {
|
|
40
|
+
const flags = getCliFlags();
|
|
41
|
+
for (const [k, v] of Object.entries(flags)) {
|
|
42
|
+
// Skip internal NativeScript build flags
|
|
43
|
+
if (['ios', 'android', 'visionos', 'platform', 'hmr', 'verbose'].includes(k))
|
|
44
|
+
continue;
|
|
45
|
+
__processEnvEntries[k] = String(v);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch { }
|
|
49
|
+
const __processEnvJson = JSON.stringify(__processEnvEntries);
|
|
33
50
|
const { parse, compileTemplate, compileScript } = vueSfcCompiler;
|
|
34
51
|
const APP_ROOT_DIR = getProjectAppPath();
|
|
35
52
|
const APP_VIRTUAL_PREFIX = getProjectAppVirtualPath();
|
|
@@ -84,6 +101,33 @@ function isNativeScriptCoreModule(spec) {
|
|
|
84
101
|
function isNativeScriptPluginModule(spec) {
|
|
85
102
|
return /^@nativescript\//i.test(spec || '') && !isNativeScriptCoreModule(spec || '');
|
|
86
103
|
}
|
|
104
|
+
function isLikelyNativeScriptRuntimePluginSpecifier(spec) {
|
|
105
|
+
if (!spec)
|
|
106
|
+
return false;
|
|
107
|
+
const s = spec.replace(PAT.QUERY_PATTERN, '');
|
|
108
|
+
if (/^(?:\.|\/|https?:\/\/)/i.test(s))
|
|
109
|
+
return false;
|
|
110
|
+
if (s.startsWith('@@/'))
|
|
111
|
+
return false;
|
|
112
|
+
if (s.startsWith('~/'))
|
|
113
|
+
return false;
|
|
114
|
+
if (s.startsWith('@/'))
|
|
115
|
+
return false;
|
|
116
|
+
if (/\.vue(?:\?|$)/i.test(s))
|
|
117
|
+
return false;
|
|
118
|
+
const root = extractRootPackageName(s) || s;
|
|
119
|
+
if (!root)
|
|
120
|
+
return false;
|
|
121
|
+
if (isNativeScriptCoreModule(root))
|
|
122
|
+
return false;
|
|
123
|
+
if (/^(?:vue|nativescript-vue)(?:\b|\/)/i.test(root))
|
|
124
|
+
return false;
|
|
125
|
+
if (/^@nativescript\//i.test(root))
|
|
126
|
+
return true;
|
|
127
|
+
if (/^(?:@nativescript-community|@nstudio|@mleleux)\//i.test(root))
|
|
128
|
+
return true;
|
|
129
|
+
return /(?:^|\/)nativescript(?:$|[-_])/i.test(root);
|
|
130
|
+
}
|
|
87
131
|
// Looser detector for NativeScript plugin-style specifiers that should be resolved
|
|
88
132
|
// via device require() rather than HTTP during HMR. This includes popular community
|
|
89
133
|
// scopes in addition to @nativescript/* (excluding core).
|
|
@@ -112,7 +156,518 @@ function isLikelyNativeScriptPluginSpecifier(spec) {
|
|
|
112
156
|
// Treat any other bare package id as device-resolved (require) during HMR
|
|
113
157
|
return true;
|
|
114
158
|
}
|
|
115
|
-
export function
|
|
159
|
+
export function tryReadRawExplicitJavaScriptModule(spec, projectRoot) {
|
|
160
|
+
if (!spec || !spec.startsWith('/'))
|
|
161
|
+
return null;
|
|
162
|
+
if (spec.startsWith('/@id/') || spec.startsWith('/@fs/'))
|
|
163
|
+
return null;
|
|
164
|
+
if (!/\.js$/i.test(spec) || /\.(?:mjs|cjs)$/i.test(spec))
|
|
165
|
+
return null;
|
|
166
|
+
// NativeScript runtime plugins rely on Vite's transform pipeline to apply
|
|
167
|
+
// platform-aware entry resolution and preserve root-entry routing semantics.
|
|
168
|
+
// Reading their on-disk JS directly recreates raw relative imports such as
|
|
169
|
+
// `./gesturehandler`, which can reintroduce HTTP ESM cycles during HMR.
|
|
170
|
+
try {
|
|
171
|
+
const nodeModulesSpecifier = normalizeNodeModulesSpecifier(spec);
|
|
172
|
+
if (nodeModulesSpecifier) {
|
|
173
|
+
const pkgName = extractRootPackageName(nodeModulesSpecifier);
|
|
174
|
+
if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName)) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch { }
|
|
180
|
+
const root = path.resolve(projectRoot);
|
|
181
|
+
const absPath = path.resolve(root, `.${spec}`);
|
|
182
|
+
const rel = path.relative(root, absPath);
|
|
183
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel))
|
|
184
|
+
return null;
|
|
185
|
+
if (!existsSync(absPath))
|
|
186
|
+
return null;
|
|
187
|
+
try {
|
|
188
|
+
const code = readFileSync(absPath, 'utf-8');
|
|
189
|
+
if (/from\s*['"](?:node:)?module['"]/.test(code) || /\bcreateRequire\b/.test(code)) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
code,
|
|
194
|
+
resolvedId: spec,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function canonicalizeTransformRequestCacheKey(url, projectRoot) {
|
|
202
|
+
if (!url)
|
|
203
|
+
return url;
|
|
204
|
+
const [rawPath, rawQuery = ''] = url.split('?', 2);
|
|
205
|
+
let normalizedPath = rawPath;
|
|
206
|
+
const root = projectRoot ? projectRoot.replace(/\\/g, '/') : '';
|
|
207
|
+
if (normalizedPath.startsWith('/@fs/')) {
|
|
208
|
+
const fsPath = normalizedPath.slice('/@fs'.length).replace(/\\/g, '/');
|
|
209
|
+
if (root && fsPath.startsWith(root)) {
|
|
210
|
+
const rel = fsPath.slice(root.length);
|
|
211
|
+
normalizedPath = rel.startsWith('/') ? rel : `/${rel}`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else if (root && normalizedPath.replace(/\\/g, '/').startsWith(root)) {
|
|
215
|
+
const rel = normalizedPath.replace(/\\/g, '/').slice(root.length);
|
|
216
|
+
normalizedPath = rel.startsWith('/') ? rel : `/${rel}`;
|
|
217
|
+
}
|
|
218
|
+
if (!rawQuery) {
|
|
219
|
+
return normalizedPath;
|
|
220
|
+
}
|
|
221
|
+
const params = new URLSearchParams(rawQuery);
|
|
222
|
+
params.delete('t');
|
|
223
|
+
params.delete('v');
|
|
224
|
+
const kept = Array.from(params.entries()).sort(([leftKey, leftValue], [rightKey, rightValue]) => {
|
|
225
|
+
if (leftKey === rightKey) {
|
|
226
|
+
return leftValue.localeCompare(rightValue);
|
|
227
|
+
}
|
|
228
|
+
return leftKey.localeCompare(rightKey);
|
|
229
|
+
});
|
|
230
|
+
if (!kept.length) {
|
|
231
|
+
return normalizedPath;
|
|
232
|
+
}
|
|
233
|
+
const normalizedQuery = new URLSearchParams();
|
|
234
|
+
for (const [key, value] of kept) {
|
|
235
|
+
normalizedQuery.append(key, value);
|
|
236
|
+
}
|
|
237
|
+
return `${normalizedPath}?${normalizedQuery.toString()}`;
|
|
238
|
+
}
|
|
239
|
+
const MODULE_IMPORT_ANALYSIS_PLUGINS = ['typescript', 'jsx', 'importMeta', 'topLevelAwait', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', 'decorators-legacy'];
|
|
240
|
+
function collectTopLevelImportRecords(code) {
|
|
241
|
+
if (!code || typeof code !== 'string' || !/\bimport\b/.test(code)) {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const ast = babelParse(code, {
|
|
246
|
+
sourceType: 'module',
|
|
247
|
+
plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
|
|
248
|
+
});
|
|
249
|
+
const body = ast?.program?.body;
|
|
250
|
+
if (!Array.isArray(body)) {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
return body
|
|
254
|
+
.filter((node) => t.isImportDeclaration(node) && typeof node.start === 'number' && typeof node.end === 'number' && typeof node.source?.value === 'string')
|
|
255
|
+
.map((node) => ({
|
|
256
|
+
start: node.start,
|
|
257
|
+
end: node.end,
|
|
258
|
+
text: code.slice(node.start, node.end),
|
|
259
|
+
source: node.source.value,
|
|
260
|
+
hasOnlyNamedSpecifiers: Array.isArray(node.specifiers) && node.specifiers.length > 0 && node.specifiers.every((spec) => t.isImportSpecifier(spec)),
|
|
261
|
+
namedBindings: Array.isArray(node.specifiers)
|
|
262
|
+
? node.specifiers
|
|
263
|
+
.filter((spec) => t.isImportSpecifier(spec) && typeof spec.start === 'number' && typeof spec.end === 'number')
|
|
264
|
+
.map((spec) => ({
|
|
265
|
+
importedName: t.isIdentifier(spec.imported) ? spec.imported.name : String(spec.imported?.value || ''),
|
|
266
|
+
text: code.slice(spec.start, spec.end),
|
|
267
|
+
}))
|
|
268
|
+
: [],
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function hoistTopLevelStaticImports(code) {
|
|
276
|
+
const imports = collectTopLevelImportRecords(code);
|
|
277
|
+
if (!imports.length) {
|
|
278
|
+
return code;
|
|
279
|
+
}
|
|
280
|
+
let stripped = code;
|
|
281
|
+
for (const imp of [...imports].sort((left, right) => right.start - left.start)) {
|
|
282
|
+
stripped = stripped.slice(0, imp.start) + stripped.slice(imp.end);
|
|
283
|
+
}
|
|
284
|
+
const hoisted = [];
|
|
285
|
+
const seen = new Set();
|
|
286
|
+
for (const imp of imports) {
|
|
287
|
+
const text = imp.text.trim();
|
|
288
|
+
if (!text || seen.has(text)) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
seen.add(text);
|
|
292
|
+
hoisted.push(text);
|
|
293
|
+
}
|
|
294
|
+
if (!hoisted.length) {
|
|
295
|
+
return stripped;
|
|
296
|
+
}
|
|
297
|
+
return `${hoisted.join('\n')}\n${stripped.replace(/^\s*\n+/, '')}`;
|
|
298
|
+
}
|
|
299
|
+
export function buildBootProgressSnippet(bootModuleLabel) {
|
|
300
|
+
const normalizedLabel = JSON.stringify(String(bootModuleLabel || '').replace(/\\/g, '/'));
|
|
301
|
+
return [
|
|
302
|
+
`const __nsBootGlobal=globalThis;`,
|
|
303
|
+
`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){}`,
|
|
304
|
+
`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));}}`,
|
|
305
|
+
'',
|
|
306
|
+
].join('\n');
|
|
307
|
+
}
|
|
308
|
+
function rewriteVitePrebundleImportsForDevice(code, preserveVendorImports) {
|
|
309
|
+
const imports = collectTopLevelImportRecords(code);
|
|
310
|
+
if (!imports.length) {
|
|
311
|
+
return code;
|
|
312
|
+
}
|
|
313
|
+
const edits = [];
|
|
314
|
+
for (const imp of imports) {
|
|
315
|
+
const source = imp.source;
|
|
316
|
+
const depMatch = source.match(/(?:^|\/)node_modules\/\.vite\/deps\/(.+)$/);
|
|
317
|
+
const depPath = depMatch?.[1] || (source.startsWith('.vite/deps/') ? source.slice('.vite/deps/'.length) : null);
|
|
318
|
+
if (!depPath) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
let replacement = '';
|
|
322
|
+
if (preserveVendorImports) {
|
|
323
|
+
const canonical = resolveVendorFromCandidate(`.vite/deps/${depPath}`);
|
|
324
|
+
const bareSpecifier = canonical || viteDepsPathToBareSpecifier(depPath);
|
|
325
|
+
if (bareSpecifier) {
|
|
326
|
+
replacement = imp.text.replace(source, bareSpecifier);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
edits.push({
|
|
330
|
+
start: imp.start,
|
|
331
|
+
end: imp.end,
|
|
332
|
+
text: replacement,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (!edits.length) {
|
|
336
|
+
return code;
|
|
337
|
+
}
|
|
338
|
+
let next = code;
|
|
339
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
340
|
+
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
341
|
+
}
|
|
342
|
+
return next;
|
|
343
|
+
}
|
|
344
|
+
function buildNodeModuleProvenancePrelude(sourceId) {
|
|
345
|
+
if (!sourceId) {
|
|
346
|
+
return '';
|
|
347
|
+
}
|
|
348
|
+
const cleaned = sourceId.replace(PAT.QUERY_PATTERN, '');
|
|
349
|
+
let normalized = normalizeNodeModulesSpecifier(cleaned);
|
|
350
|
+
if (!normalized) {
|
|
351
|
+
const viteDepsMatch = cleaned.match(/(?:^|\/)node_modules\/\.vite\/deps\/([^?#]+)/);
|
|
352
|
+
if (viteDepsMatch?.[1]) {
|
|
353
|
+
normalized = `.vite/deps/${viteDepsMatch[1]}`;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (!normalized) {
|
|
357
|
+
return '';
|
|
358
|
+
}
|
|
359
|
+
let packageSpecifier = normalized;
|
|
360
|
+
let via = 'node_modules';
|
|
361
|
+
if (normalized.startsWith('.vite/deps/')) {
|
|
362
|
+
via = 'vite-deps';
|
|
363
|
+
packageSpecifier = viteDepsPathToBareSpecifier(normalized.slice('.vite/deps/'.length)) || normalized;
|
|
364
|
+
}
|
|
365
|
+
const rootPackage = extractRootPackageName(packageSpecifier);
|
|
366
|
+
if (!rootPackage) {
|
|
367
|
+
return '';
|
|
368
|
+
}
|
|
369
|
+
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`;
|
|
370
|
+
}
|
|
371
|
+
export function collectGraphUpdateModulesForHotUpdate(options) {
|
|
372
|
+
const targets = new Map();
|
|
373
|
+
const addTarget = (mod) => {
|
|
374
|
+
const id = mod?.id?.replace(/\?.*$/, '');
|
|
375
|
+
if (!id)
|
|
376
|
+
return;
|
|
377
|
+
if (!targets.has(id)) {
|
|
378
|
+
targets.set(id, mod);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
if (options.flavor === 'angular' && /\.(html|htm)$/i.test(options.file)) {
|
|
382
|
+
for (const mod of options.modules || []) {
|
|
383
|
+
for (const importer of mod?.importers || []) {
|
|
384
|
+
const importerId = importer?.id || '';
|
|
385
|
+
if (/\.[cm]?[jt]sx?(?:$|\?)/i.test(importerId)) {
|
|
386
|
+
addTarget(importer);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (!targets.size) {
|
|
391
|
+
addTarget(options.getModuleById(options.file.replace(/\.(html|htm)$/i, '.ts')));
|
|
392
|
+
addTarget(options.getModuleById(options.file.replace(/\.(html|htm)$/i, '.js')));
|
|
393
|
+
}
|
|
394
|
+
return Array.from(targets.values());
|
|
395
|
+
}
|
|
396
|
+
if (!options.file.endsWith('.vue')) {
|
|
397
|
+
addTarget(options.getModuleById(options.file) || options.getModuleById(options.file + '?vue'));
|
|
398
|
+
}
|
|
399
|
+
return Array.from(targets.values());
|
|
400
|
+
}
|
|
401
|
+
export function collectAngularHotUpdateRoots(options) {
|
|
402
|
+
const roots = [];
|
|
403
|
+
const seenIds = new Set();
|
|
404
|
+
const seenObjects = new Set();
|
|
405
|
+
const addRoot = (mod) => {
|
|
406
|
+
if (!mod) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (mod.id) {
|
|
410
|
+
if (seenIds.has(mod.id)) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
seenIds.add(mod.id);
|
|
414
|
+
roots.push(mod);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (seenObjects.has(mod)) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
seenObjects.add(mod);
|
|
421
|
+
roots.push(mod);
|
|
422
|
+
};
|
|
423
|
+
if (/\.(html|htm)$/i.test(options.file)) {
|
|
424
|
+
for (const mod of collectGraphUpdateModulesForHotUpdate({
|
|
425
|
+
file: options.file,
|
|
426
|
+
flavor: 'angular',
|
|
427
|
+
modules: options.modules,
|
|
428
|
+
getModuleById: options.getModuleById,
|
|
429
|
+
})) {
|
|
430
|
+
addRoot(mod);
|
|
431
|
+
}
|
|
432
|
+
return roots;
|
|
433
|
+
}
|
|
434
|
+
if (!/\.(m|c)?ts$/i.test(options.file)) {
|
|
435
|
+
return roots;
|
|
436
|
+
}
|
|
437
|
+
for (const mod of options.modules || []) {
|
|
438
|
+
addRoot(mod);
|
|
439
|
+
}
|
|
440
|
+
for (const mod of options.getModulesByFile?.(options.file) || []) {
|
|
441
|
+
addRoot(mod);
|
|
442
|
+
}
|
|
443
|
+
if (!roots.length) {
|
|
444
|
+
addRoot(options.getModuleById(options.file));
|
|
445
|
+
}
|
|
446
|
+
return roots;
|
|
447
|
+
}
|
|
448
|
+
export function collectAngularTransitiveImportersForInvalidation(options) {
|
|
449
|
+
const visited = new Set();
|
|
450
|
+
const collected = new Map();
|
|
451
|
+
const isExcluded = options.isExcluded ?? ((id) => id.includes('/node_modules/'));
|
|
452
|
+
const maxDepth = Math.max(1, Math.floor(options.maxDepth ?? 16));
|
|
453
|
+
const normalizeId = (value) => (value ?? '').replace(/\?.*$/, '');
|
|
454
|
+
const walk = (mod, depth) => {
|
|
455
|
+
if (!mod || visited.has(mod)) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
visited.add(mod);
|
|
459
|
+
if (depth >= maxDepth) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const importers = mod.importers;
|
|
463
|
+
if (!importers) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
for (const importer of importers) {
|
|
467
|
+
if (!importer)
|
|
468
|
+
continue;
|
|
469
|
+
const importerId = normalizeId(importer.id);
|
|
470
|
+
if (!importerId) {
|
|
471
|
+
walk(importer, depth + 1);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (isExcluded(importerId)) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (!collected.has(importerId)) {
|
|
478
|
+
collected.set(importerId, importer);
|
|
479
|
+
}
|
|
480
|
+
walk(importer, depth + 1);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
for (const mod of options.modules || []) {
|
|
484
|
+
walk(mod, 0);
|
|
485
|
+
}
|
|
486
|
+
return Array.from(collected.values());
|
|
487
|
+
}
|
|
488
|
+
export function shouldInvalidateAngularTransitiveImporters(options) {
|
|
489
|
+
if (options.flavor !== 'angular') {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
return /\.(?:html|htm|(m|c)?[jt]sx?)$/i.test(options.file);
|
|
493
|
+
}
|
|
494
|
+
export function shouldSuppressDefaultViteHotUpdate(options) {
|
|
495
|
+
if (options.flavor !== 'angular') {
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
return /\.(html|htm|ts)$/i.test(options.file);
|
|
499
|
+
}
|
|
500
|
+
export function normalizeHotReloadMatchPath(raw, root) {
|
|
501
|
+
let normalized = String(raw || '')
|
|
502
|
+
.split('?')[0]
|
|
503
|
+
.replace(/\\/g, '/')
|
|
504
|
+
.replace(/^file:\/\//, '');
|
|
505
|
+
if (root) {
|
|
506
|
+
const rootNormalized = root.replace(/\\/g, '/');
|
|
507
|
+
if (normalized.startsWith(rootNormalized)) {
|
|
508
|
+
normalized = normalized.slice(rootNormalized.length);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (!normalized.startsWith('/')) {
|
|
512
|
+
normalized = `/${normalized}`;
|
|
513
|
+
}
|
|
514
|
+
return normalized;
|
|
515
|
+
}
|
|
516
|
+
export function shouldSuppressViteFullReloadPayload(options) {
|
|
517
|
+
const { payload, pendingEntries, root } = options;
|
|
518
|
+
const now = options.now ?? Date.now();
|
|
519
|
+
if (!payload || payload.type !== 'full-reload') {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
const payloadPath = typeof payload.path === 'string' && payload.path !== '*' ? normalizeHotReloadMatchPath(payload.path, root) : null;
|
|
523
|
+
const payloadTriggeredBy = typeof payload.triggeredBy === 'string' ? normalizeHotReloadMatchPath(payload.triggeredBy, root) : null;
|
|
524
|
+
for (const entry of pendingEntries) {
|
|
525
|
+
if (!entry || entry.expiresAt <= now) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (payloadTriggeredBy === entry.absPath || payloadTriggeredBy === entry.relPath || payloadPath === entry.relPath || payloadPath === entry.absPath) {
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
export function createSharedTransformRequestRunner(transformRequest, onTimeout, options = {}) {
|
|
535
|
+
const inFlight = new Map();
|
|
536
|
+
const recentResults = new Map();
|
|
537
|
+
const cacheGenerations = new Map();
|
|
538
|
+
const queue = [];
|
|
539
|
+
const maxConcurrent = Math.max(1, Math.floor(options.maxConcurrent ?? 1));
|
|
540
|
+
const resultCacheTtlMs = Math.max(0, Math.floor(options.resultCacheTtlMs ?? 0));
|
|
541
|
+
const getResultCacheKey = options.getResultCacheKey ?? ((url) => url);
|
|
542
|
+
let activeCount = 0;
|
|
543
|
+
const getCacheGeneration = (cacheKey) => cacheGenerations.get(cacheKey) ?? 0;
|
|
544
|
+
const invalidateCacheKey = (cacheKey) => {
|
|
545
|
+
cacheGenerations.set(cacheKey, getCacheGeneration(cacheKey) + 1);
|
|
546
|
+
recentResults.delete(cacheKey);
|
|
547
|
+
};
|
|
548
|
+
const pruneRecentResults = () => {
|
|
549
|
+
if (!recentResults.size) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
const now = Date.now();
|
|
553
|
+
for (const [key, entry] of recentResults) {
|
|
554
|
+
if (entry.expiresAt <= now) {
|
|
555
|
+
recentResults.delete(key);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
const rememberRecentResult = (url, result, generation) => {
|
|
560
|
+
if (!result || resultCacheTtlMs <= 0) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const cacheKey = getResultCacheKey(url);
|
|
564
|
+
if (getCacheGeneration(cacheKey) !== generation) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
recentResults.delete(cacheKey);
|
|
568
|
+
recentResults.set(cacheKey, {
|
|
569
|
+
expiresAt: Date.now() + resultCacheTtlMs,
|
|
570
|
+
result,
|
|
571
|
+
});
|
|
572
|
+
if (recentResults.size > 512) {
|
|
573
|
+
const oldestKey = recentResults.keys().next().value;
|
|
574
|
+
if (oldestKey) {
|
|
575
|
+
recentResults.delete(oldestKey);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
const runNext = () => {
|
|
580
|
+
while (activeCount < maxConcurrent) {
|
|
581
|
+
const next = queue.shift();
|
|
582
|
+
if (!next) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
activeCount += 1;
|
|
586
|
+
next();
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
const schedule = (task) => {
|
|
590
|
+
let resolveStarted = null;
|
|
591
|
+
const started = new Promise((resolve) => {
|
|
592
|
+
resolveStarted = resolve;
|
|
593
|
+
});
|
|
594
|
+
const execution = new Promise((resolve, reject) => {
|
|
595
|
+
queue.push(() => {
|
|
596
|
+
let started;
|
|
597
|
+
resolveStarted?.();
|
|
598
|
+
try {
|
|
599
|
+
started = Promise.resolve(task());
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
started = Promise.reject(error);
|
|
603
|
+
}
|
|
604
|
+
started.then(resolve, reject).finally(() => {
|
|
605
|
+
activeCount = Math.max(0, activeCount - 1);
|
|
606
|
+
runNext();
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
runNext();
|
|
610
|
+
});
|
|
611
|
+
return { execution, started };
|
|
612
|
+
};
|
|
613
|
+
const withTimeout = (entry, url, timeoutMs) => {
|
|
614
|
+
if (!(timeoutMs > 0)) {
|
|
615
|
+
return entry.execution;
|
|
616
|
+
}
|
|
617
|
+
return entry.started.then(() => new Promise((resolve, reject) => {
|
|
618
|
+
const timer = setTimeout(() => {
|
|
619
|
+
try {
|
|
620
|
+
onTimeout?.(url, timeoutMs);
|
|
621
|
+
}
|
|
622
|
+
catch { }
|
|
623
|
+
}, timeoutMs);
|
|
624
|
+
entry.execution.then(resolve, reject).finally(() => {
|
|
625
|
+
clearTimeout(timer);
|
|
626
|
+
});
|
|
627
|
+
}));
|
|
628
|
+
};
|
|
629
|
+
const runner = ((url, timeoutMs = 120000) => {
|
|
630
|
+
pruneRecentResults();
|
|
631
|
+
const cacheKey = getResultCacheKey(url);
|
|
632
|
+
const generation = getCacheGeneration(cacheKey);
|
|
633
|
+
const recent = recentResults.get(cacheKey);
|
|
634
|
+
if (recent && recent.expiresAt > Date.now()) {
|
|
635
|
+
return Promise.resolve(recent.result);
|
|
636
|
+
}
|
|
637
|
+
const existingExecution = inFlight.get(url);
|
|
638
|
+
if (existingExecution && existingExecution.generation === generation && existingExecution.cacheKey === cacheKey) {
|
|
639
|
+
return withTimeout(existingExecution, url, timeoutMs);
|
|
640
|
+
}
|
|
641
|
+
const scheduled = schedule(async () => {
|
|
642
|
+
const result = await Promise.resolve(transformRequest(url));
|
|
643
|
+
rememberRecentResult(url, result, generation);
|
|
644
|
+
return result;
|
|
645
|
+
});
|
|
646
|
+
let execution;
|
|
647
|
+
execution = scheduled.execution.finally(() => {
|
|
648
|
+
if (inFlight.get(url)?.execution === execution) {
|
|
649
|
+
inFlight.delete(url);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
const entry = { execution, started: scheduled.started, cacheKey, generation };
|
|
653
|
+
inFlight.set(url, entry);
|
|
654
|
+
return withTimeout(entry, url, timeoutMs);
|
|
655
|
+
});
|
|
656
|
+
runner.invalidate = (url) => {
|
|
657
|
+
invalidateCacheKey(getResultCacheKey(url));
|
|
658
|
+
};
|
|
659
|
+
runner.invalidateMany = (urls) => {
|
|
660
|
+
for (const url of urls || []) {
|
|
661
|
+
runner.invalidate(url);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
runner.clear = () => {
|
|
665
|
+
recentResults.clear();
|
|
666
|
+
cacheGenerations.clear();
|
|
667
|
+
};
|
|
668
|
+
return runner;
|
|
669
|
+
}
|
|
670
|
+
export function ensureNativeScriptModuleBindings(code, options) {
|
|
116
671
|
// Proceed even if a vendor manifest isn't available; we'll still vendor-bind
|
|
117
672
|
// likely NativeScript plugin-style specifiers (e.g., 'pinia', '@scope/pkg')
|
|
118
673
|
// via require() so device can resolve them from the app bundle.
|
|
@@ -165,6 +720,11 @@ export function ensureNativeScriptModuleBindings(code) {
|
|
|
165
720
|
}
|
|
166
721
|
const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
|
|
167
722
|
let canonical = resolveVendorFromCandidate(specifier);
|
|
723
|
+
const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier);
|
|
724
|
+
if (options?.preserveNonPluginVendorImports && !runtimePluginSpecifier) {
|
|
725
|
+
preservedImports.push(original);
|
|
726
|
+
return pfx || '';
|
|
727
|
+
}
|
|
168
728
|
// If not found in vendor manifest, treat well-known NativeScript plugin-style packages
|
|
169
729
|
// as require() based modules so the device can resolve them from the app bundle or vendor.
|
|
170
730
|
if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
|
|
@@ -216,6 +776,11 @@ export function ensureNativeScriptModuleBindings(code) {
|
|
|
216
776
|
const original = full.replace(/^\n/, '');
|
|
217
777
|
const specifier = rawSpec.replace(PAT.QUERY_PATTERN, '');
|
|
218
778
|
let canonical = resolveVendorFromCandidate(specifier);
|
|
779
|
+
const runtimePluginSpecifier = isLikelyNativeScriptRuntimePluginSpecifier(canonical || specifier);
|
|
780
|
+
if (options?.preserveNonPluginVendorImports && !runtimePluginSpecifier) {
|
|
781
|
+
preservedImports.push(original);
|
|
782
|
+
return _pfx || '';
|
|
783
|
+
}
|
|
219
784
|
if (!canonical && isLikelyNativeScriptPluginSpecifier(specifier)) {
|
|
220
785
|
canonical = specifier;
|
|
221
786
|
}
|
|
@@ -318,6 +883,34 @@ function guardBareDynamicImports(code) {
|
|
|
318
883
|
return code;
|
|
319
884
|
}
|
|
320
885
|
}
|
|
886
|
+
function ensureDynamicHmrImportHelper(code) {
|
|
887
|
+
try {
|
|
888
|
+
if (!code.includes('__nsDynamicHmrImport('))
|
|
889
|
+
return code;
|
|
890
|
+
if (code.includes('const __nsDynamicHmrImport ='))
|
|
891
|
+
return code;
|
|
892
|
+
const helper = 'const __nsDynamicHmrImport = (spec) => {\n' +
|
|
893
|
+
" const __nsm = '/ns' + '/m';\n" +
|
|
894
|
+
" try { if (!spec || spec === '@') { return import(new URL(__nsm + '/__invalid_at__.mjs', import.meta.url).href); } } catch {}\n" +
|
|
895
|
+
' try {\n' +
|
|
896
|
+
" if (typeof spec === 'string' && spec.startsWith(__nsm + '/')) {\n" +
|
|
897
|
+
" if (spec.includes('/__ns_hmr__/')) { return import(new URL(spec, import.meta.url).href); }\n" +
|
|
898
|
+
' const g = globalThis;\n' +
|
|
899
|
+
" const nonce = typeof g.__NS_HMR_IMPORT_NONCE__ === 'number' ? g.__NS_HMR_IMPORT_NONCE__ : 0;\n" +
|
|
900
|
+
" const tag = nonce ? `n${nonce}` : 'live';\n" +
|
|
901
|
+
" const nextPath = __nsm + '/__ns_hmr__/' + encodeURIComponent(tag) + spec.slice(__nsm.length);\n" +
|
|
902
|
+
" const origin = typeof g.__NS_HTTP_ORIGIN__ === 'string' && /^https?:\\/\\//.test(g.__NS_HTTP_ORIGIN__) ? g.__NS_HTTP_ORIGIN__ : '';\n" +
|
|
903
|
+
' return import(origin ? origin + nextPath : new URL(nextPath, import.meta.url).href);\n' +
|
|
904
|
+
' }\n' +
|
|
905
|
+
' } catch {}\n' +
|
|
906
|
+
' return import(spec);\n' +
|
|
907
|
+
'};\n';
|
|
908
|
+
return helper + code;
|
|
909
|
+
}
|
|
910
|
+
catch {
|
|
911
|
+
return code;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
321
914
|
function normalizeNativeScriptCoreSpecifier(spec) {
|
|
322
915
|
let normalized = spec.replace(/@nativescript[_-]core/gi, '@nativescript/core').replace(/@nativescript\/core\/index\.js$/i, '@nativescript/core/index.js');
|
|
323
916
|
if (normalized.startsWith('/node_modules/')) {
|
|
@@ -331,7 +924,7 @@ function normalizeNativeScriptCoreSpecifier(spec) {
|
|
|
331
924
|
}
|
|
332
925
|
return normalized;
|
|
333
926
|
}
|
|
334
|
-
function normalizeNodeModulesSpecifier(spec) {
|
|
927
|
+
export function normalizeNodeModulesSpecifier(spec) {
|
|
335
928
|
if (!spec) {
|
|
336
929
|
return null;
|
|
337
930
|
}
|
|
@@ -354,7 +947,7 @@ function normalizeNodeModulesSpecifier(spec) {
|
|
|
354
947
|
}
|
|
355
948
|
return subPath.startsWith('/') ? subPath.slice(1) : subPath;
|
|
356
949
|
}
|
|
357
|
-
function resolveVendorFromCandidate(specifier) {
|
|
950
|
+
export function resolveVendorFromCandidate(specifier) {
|
|
358
951
|
if (!specifier) {
|
|
359
952
|
return null;
|
|
360
953
|
}
|
|
@@ -375,9 +968,27 @@ function resolveVendorFromCandidate(specifier) {
|
|
|
375
968
|
return flatMatch;
|
|
376
969
|
}
|
|
377
970
|
for (const [flatKey, canonical] of flattenedMap.entries()) {
|
|
378
|
-
if (flattenedId === flatKey
|
|
971
|
+
if (flattenedId === flatKey) {
|
|
379
972
|
return canonical;
|
|
380
973
|
}
|
|
974
|
+
if (flattenedId.startsWith(`${flatKey}_`)) {
|
|
975
|
+
// The suffix after the flat key represents a subpath.
|
|
976
|
+
// Convert it back: e.g., "_solid" → "solid", "_store_dist_x" → "store/dist/x"
|
|
977
|
+
// Only resolve to the root vendor module if it's a file/dist subpath.
|
|
978
|
+
// Entry-point subpaths (e.g., _solid, _store) have different exports
|
|
979
|
+
// and must NOT be collapsed to the root.
|
|
980
|
+
const flatSuffix = flattenedId.slice(flatKey.length + 1);
|
|
981
|
+
const subpath = flatSuffix.replace(/_/g, '/');
|
|
982
|
+
if (isFileDistSubpath(subpath)) {
|
|
983
|
+
return canonical;
|
|
984
|
+
}
|
|
985
|
+
// Check if there's an alias for this subpath entry
|
|
986
|
+
const aliasKey = `${canonical}/${subpath.split('/')[0]}`;
|
|
987
|
+
if (manifest.aliases?.[aliasKey] && manifest.modules[manifest.aliases[aliasKey]]) {
|
|
988
|
+
return manifest.aliases[aliasKey];
|
|
989
|
+
}
|
|
990
|
+
// Entry-point subpath — don't collapse to root
|
|
991
|
+
}
|
|
381
992
|
}
|
|
382
993
|
const guessedId = flattenedId.replace(/__/g, '.').replace(/_/g, '/');
|
|
383
994
|
if (guessedId && guessedId !== flattenedId) {
|
|
@@ -415,16 +1026,279 @@ function resolveVendorFromCandidate(specifier) {
|
|
|
415
1026
|
}
|
|
416
1027
|
return null;
|
|
417
1028
|
}
|
|
1029
|
+
function resolveCandidateFilePath(candidate, projectRoot) {
|
|
1030
|
+
const cleaned = candidate.replace(PAT.QUERY_PATTERN, '');
|
|
1031
|
+
if (!cleaned)
|
|
1032
|
+
return null;
|
|
1033
|
+
const root = path.resolve(projectRoot);
|
|
1034
|
+
let absPath = null;
|
|
1035
|
+
if (cleaned.startsWith('/@fs/')) {
|
|
1036
|
+
absPath = cleaned.slice('/@fs'.length);
|
|
1037
|
+
}
|
|
1038
|
+
else if (cleaned.includes('/node_modules/')) {
|
|
1039
|
+
absPath = path.resolve(root, `.${cleaned}`);
|
|
1040
|
+
}
|
|
1041
|
+
else if (/^(?:[A-Za-z]:)?\//.test(cleaned)) {
|
|
1042
|
+
absPath = path.resolve(cleaned);
|
|
1043
|
+
}
|
|
1044
|
+
if (!absPath) {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
const rel = path.relative(root, absPath);
|
|
1048
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
1051
|
+
return existsSync(absPath) ? absPath : null;
|
|
1052
|
+
}
|
|
1053
|
+
export function filterExistingNodeModulesTransformCandidates(spec, candidates, projectRoot) {
|
|
1054
|
+
const cleanedSpec = spec.replace(PAT.QUERY_PATTERN, '');
|
|
1055
|
+
if (!cleanedSpec.includes('/node_modules/')) {
|
|
1056
|
+
return candidates;
|
|
1057
|
+
}
|
|
1058
|
+
return candidates.filter((candidate) => !!resolveCandidateFilePath(candidate, projectRoot));
|
|
1059
|
+
}
|
|
418
1060
|
function findVendorPrefix(specifier, manifest) {
|
|
419
|
-
const { modules } = manifest;
|
|
1061
|
+
const { modules, aliases } = manifest;
|
|
420
1062
|
const keys = Object.keys(modules || {});
|
|
421
1063
|
for (const key of keys) {
|
|
422
|
-
if (specifier === key
|
|
1064
|
+
if (specifier === key) {
|
|
423
1065
|
return key;
|
|
424
1066
|
}
|
|
1067
|
+
if (specifier.startsWith(`${key}/`)) {
|
|
1068
|
+
const subpath = specifier.slice(key.length + 1);
|
|
1069
|
+
// Only match file/dist subpaths (e.g., solid-js/dist/dev.js → solid-js).
|
|
1070
|
+
// Entry-point subpaths (e.g., solid-js/store) are separate packages
|
|
1071
|
+
// and must NOT be collapsed to the root vendor module.
|
|
1072
|
+
if (isFileDistSubpath(subpath)) {
|
|
1073
|
+
return key;
|
|
1074
|
+
}
|
|
1075
|
+
// Check if there's an explicit alias for this subpath
|
|
1076
|
+
const aliasKey = `${key}/${subpath.split('/')[0]}`;
|
|
1077
|
+
if (aliases?.[aliasKey] && modules[aliases[aliasKey]]) {
|
|
1078
|
+
return aliases[aliasKey];
|
|
1079
|
+
}
|
|
1080
|
+
// Entry-point subpath with no alias — don't vendor-resolve
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Convert a Vite .vite/deps/ filename back to a bare specifier with subpath,
|
|
1088
|
+
* using the vendor manifest to determine where the package name ends and the
|
|
1089
|
+
* subpath begins.
|
|
1090
|
+
*
|
|
1091
|
+
* e.g., "@nativescript_tanstack-router_solid.js" → "@nativescript/tanstack-router/solid"
|
|
1092
|
+
* "solid-js.js" → "solid-js"
|
|
1093
|
+
*
|
|
1094
|
+
* Returns null if no known package prefix matches.
|
|
1095
|
+
*/
|
|
1096
|
+
function viteDepsPathToBareSpecifier(depPath) {
|
|
1097
|
+
const manifest = getVendorManifest();
|
|
1098
|
+
if (!manifest)
|
|
1099
|
+
return null;
|
|
1100
|
+
const flatId = extractVitePrebundleId(`.vite/deps/${depPath}`);
|
|
1101
|
+
if (!flatId)
|
|
1102
|
+
return null;
|
|
1103
|
+
const flatMap = getFlattenedManifestMap(manifest);
|
|
1104
|
+
// Try exact match first
|
|
1105
|
+
if (flatMap.has(flatId)) {
|
|
1106
|
+
return flatMap.get(flatId);
|
|
1107
|
+
}
|
|
1108
|
+
// Try prefix match: find the longest matching vendor flat key
|
|
1109
|
+
let bestKey = '';
|
|
1110
|
+
let bestCanonical = '';
|
|
1111
|
+
for (const [flatKey, canonical] of flatMap.entries()) {
|
|
1112
|
+
if (flatId.startsWith(`${flatKey}_`) && flatKey.length > bestKey.length) {
|
|
1113
|
+
bestKey = flatKey;
|
|
1114
|
+
bestCanonical = canonical;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (bestKey && bestCanonical) {
|
|
1118
|
+
// Convert the suffix back to a subpath
|
|
1119
|
+
const flatSuffix = flatId.slice(bestKey.length + 1);
|
|
1120
|
+
const subpath = flatSuffix.replace(/_/g, '/');
|
|
1121
|
+
return `${bestCanonical}/${subpath}`;
|
|
425
1122
|
}
|
|
426
1123
|
return null;
|
|
427
1124
|
}
|
|
1125
|
+
function isFileDistSubpath(subpath) {
|
|
1126
|
+
const firstSegment = subpath.split('/')[0];
|
|
1127
|
+
// Starts with a known build/dist directory segment → file path
|
|
1128
|
+
const FILE_DIST_DIRS = new Set(['dist', 'src', 'lib', 'build', 'esm', 'cjs', 'es', 'umd', 'module', 'bundle', 'output', '_esm', '_cjs']);
|
|
1129
|
+
if (FILE_DIST_DIRS.has(firstSegment)) {
|
|
1130
|
+
return true;
|
|
1131
|
+
}
|
|
1132
|
+
// Single segment with file extension → file path (e.g., "index.js")
|
|
1133
|
+
if (!subpath.includes('/') && /\.[a-zA-Z0-9]+$/.test(subpath)) {
|
|
1134
|
+
return true;
|
|
1135
|
+
}
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
// ── Package exports reverse map ──────────────────────────────────────────────
|
|
1139
|
+
// Resolves Vite's resolved file paths back to original bare specifiers using
|
|
1140
|
+
// the package's own package.json exports field. This eliminates the fragile
|
|
1141
|
+
// heuristic that tried to guess main entry vs. subpath from file paths.
|
|
1142
|
+
const _exportsReverseMapCache = new Map();
|
|
1143
|
+
/**
|
|
1144
|
+
* Resolve the concrete file path from a package.json exports condition value.
|
|
1145
|
+
* Handles nested condition objects: { "esm2022": { "default": "./file.mjs" } }
|
|
1146
|
+
*/
|
|
1147
|
+
function resolveExportConditionValue(conditions) {
|
|
1148
|
+
if (typeof conditions === 'string')
|
|
1149
|
+
return conditions;
|
|
1150
|
+
if (typeof conditions !== 'object' || conditions === null)
|
|
1151
|
+
return null;
|
|
1152
|
+
const obj = conditions;
|
|
1153
|
+
for (const key of ['esm2022', 'esm', 'esm2015', 'import', 'module', 'default']) {
|
|
1154
|
+
if (key in obj) {
|
|
1155
|
+
const result = resolveExportConditionValue(obj[key]);
|
|
1156
|
+
if (result)
|
|
1157
|
+
return result;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
for (const val of Object.values(obj)) {
|
|
1161
|
+
if (typeof val === 'string')
|
|
1162
|
+
return val;
|
|
1163
|
+
}
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Build a reverse map from file paths → bare specifiers for a package.
|
|
1168
|
+
*
|
|
1169
|
+
* Example for @angular/common:
|
|
1170
|
+
* "fesm2022/common.mjs" → "@angular/common" (exports["."])
|
|
1171
|
+
* "fesm2022/http.mjs" → "@angular/common/http" (exports["./http"])
|
|
1172
|
+
*
|
|
1173
|
+
* For packages without exports field, uses main/module fields.
|
|
1174
|
+
*/
|
|
1175
|
+
function getExportsReverseMap(pkgName, projectRoot) {
|
|
1176
|
+
const cached = _exportsReverseMapCache.get(pkgName);
|
|
1177
|
+
if (cached)
|
|
1178
|
+
return cached;
|
|
1179
|
+
const map = new Map();
|
|
1180
|
+
try {
|
|
1181
|
+
const pkgJsonPath = path.join(projectRoot, 'node_modules', pkgName, 'package.json');
|
|
1182
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
1183
|
+
if (pkgJson.exports && typeof pkgJson.exports === 'object') {
|
|
1184
|
+
for (const [entryPoint, conditions] of Object.entries(pkgJson.exports)) {
|
|
1185
|
+
// Skip wildcard patterns (./locales/*, etc.) and non-JS exports
|
|
1186
|
+
if (entryPoint.includes('*') || entryPoint.endsWith('.json'))
|
|
1187
|
+
continue;
|
|
1188
|
+
const resolvedPath = resolveExportConditionValue(conditions);
|
|
1189
|
+
if (resolvedPath && typeof resolvedPath === 'string') {
|
|
1190
|
+
const normalized = resolvedPath.replace(/^\.\//, '');
|
|
1191
|
+
const bareSpec = entryPoint === '.' ? pkgName : `${pkgName}/${entryPoint.replace(/^\.\//, '')}`;
|
|
1192
|
+
map.set(normalized, bareSpec);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
// Fallback: main/module field for packages without exports
|
|
1197
|
+
if (map.size === 0) {
|
|
1198
|
+
for (const field of ['module', 'main']) {
|
|
1199
|
+
const value = pkgJson[field];
|
|
1200
|
+
if (value && typeof value === 'string') {
|
|
1201
|
+
const normalized = value.replace(/^\.\//, '');
|
|
1202
|
+
map.set(normalized, pkgName);
|
|
1203
|
+
// Also store without extension for NativeScript platform resolution
|
|
1204
|
+
const withoutExt = normalized.replace(/\.[^.]+$/, '');
|
|
1205
|
+
if (withoutExt !== normalized) {
|
|
1206
|
+
map.set(withoutExt, pkgName);
|
|
1207
|
+
}
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
catch {
|
|
1214
|
+
// Package.json not found or unreadable
|
|
1215
|
+
}
|
|
1216
|
+
_exportsReverseMapCache.set(pkgName, map);
|
|
1217
|
+
return map;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Extract the root package name from a node_modules specifier.
|
|
1221
|
+
* "@angular/common/fesm2022/http.mjs" → "@angular/common"
|
|
1222
|
+
* "tslib/tslib.es6.mjs" → "tslib"
|
|
1223
|
+
*/
|
|
1224
|
+
function extractRootPackageName(spec) {
|
|
1225
|
+
if (spec.startsWith('@')) {
|
|
1226
|
+
const parts = spec.split('/');
|
|
1227
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : spec;
|
|
1228
|
+
}
|
|
1229
|
+
return spec.split('/')[0];
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Determine whether a vendor package import should go through the vendor bridge
|
|
1233
|
+
* (bare specifier) or HTTP (full URL), using the package's exports map.
|
|
1234
|
+
*
|
|
1235
|
+
* Returns:
|
|
1236
|
+
* { route: 'vendor', bareSpec: string } — use vendor bridge with this bare specifier
|
|
1237
|
+
* { route: 'http' } — serve via HTTP
|
|
1238
|
+
* null — not a vendor package
|
|
1239
|
+
*/
|
|
1240
|
+
function resolveVendorRouting(nodeModulesSpec, projectRoot) {
|
|
1241
|
+
const pkgName = extractRootPackageName(nodeModulesSpec);
|
|
1242
|
+
const subpath = nodeModulesSpec.slice(pkgName.length).replace(/^\//, '');
|
|
1243
|
+
const pkgBaseName = pkgName.split('/').pop() || '';
|
|
1244
|
+
const isRootLevelMainEntry = (() => {
|
|
1245
|
+
if (!subpath || subpath.includes('/')) {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
const withoutExt = subpath.replace(/\.[^.]+$/, '');
|
|
1249
|
+
const withoutPlatform = withoutExt.replace(/\.(ios|android|visionos)$/i, '');
|
|
1250
|
+
return withoutPlatform === 'index' || withoutPlatform === pkgBaseName;
|
|
1251
|
+
})();
|
|
1252
|
+
// Runtime NativeScript plugins must preserve require()-style loading even when
|
|
1253
|
+
// they are not part of the vendor manifest. Many community packages rely on
|
|
1254
|
+
// singleton side effects or tolerate CommonJS circular initialization that
|
|
1255
|
+
// would fail under plain HTTP ESM evaluation.
|
|
1256
|
+
if (isLikelyNativeScriptRuntimePluginSpecifier(pkgName) && (!subpath || isRootLevelMainEntry)) {
|
|
1257
|
+
return { route: 'vendor', bareSpec: pkgName };
|
|
1258
|
+
}
|
|
1259
|
+
// Check if this package is in the vendor manifest
|
|
1260
|
+
const manifest = getVendorManifest();
|
|
1261
|
+
if (!manifest?.modules?.[pkgName]) {
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
// Platform-specific NativeScript plugin main entries should still use the
|
|
1265
|
+
// vendor bridge so device require() preserves singleton side effects.
|
|
1266
|
+
// Their named/default bindings are synthesized later by
|
|
1267
|
+
// ensureNativeScriptModuleBindings, so they do not rely on ns-vendor:// ESM
|
|
1268
|
+
// exports directly.
|
|
1269
|
+
if (/\.(ios|android|visionos)\.(js|ts|mjs|mts)$/i.test(nodeModulesSpec) && isLikelyNativeScriptRuntimePluginSpecifier(pkgName) && isRootLevelMainEntry) {
|
|
1270
|
+
return { route: 'vendor', bareSpec: pkgName };
|
|
1271
|
+
}
|
|
1272
|
+
// Other platform-specific files still go via HTTP.
|
|
1273
|
+
if (/\.(ios|android|visionos)\.(js|ts|mjs|mts)$/.test(nodeModulesSpec)) {
|
|
1274
|
+
return { route: 'http' };
|
|
1275
|
+
}
|
|
1276
|
+
// No subpath → bare package specifier → vendor bridge
|
|
1277
|
+
if (!subpath) {
|
|
1278
|
+
return { route: 'vendor', bareSpec: pkgName };
|
|
1279
|
+
}
|
|
1280
|
+
// Use exports reverse map to detect subpath entries — these MUST go to HTTP
|
|
1281
|
+
// to avoid collapsing e.g. @angular/common/http → @angular/common
|
|
1282
|
+
const reverseMap = getExportsReverseMap(pkgName, projectRoot);
|
|
1283
|
+
const originalSpec = reverseMap.get(subpath);
|
|
1284
|
+
if (originalSpec && originalSpec !== pkgName) {
|
|
1285
|
+
// Subpath entry (e.g., fesm2022/http.mjs → @angular/common/http) → HTTP
|
|
1286
|
+
return { route: 'http' };
|
|
1287
|
+
}
|
|
1288
|
+
// For vendor routing, only use the vendor bridge for root-level main entries
|
|
1289
|
+
// (single-segment paths like "index.js", "tslib.es6.mjs"). Multi-segment
|
|
1290
|
+
// build output paths (fesm2022/core.mjs, dist/index.js) go to HTTP even if
|
|
1291
|
+
// they ARE the main entry — the ns-vendor:// protocol in HMR mode does not
|
|
1292
|
+
// reliably serve all named ES exports for complex packages.
|
|
1293
|
+
if (!subpath.includes('/')) {
|
|
1294
|
+
const lastSegment = subpath.replace(/\.[^.]+$/, '');
|
|
1295
|
+
if (lastSegment === 'index' || lastSegment === pkgBaseName || lastSegment.startsWith(pkgBaseName + '.')) {
|
|
1296
|
+
return { route: 'vendor', bareSpec: pkgName };
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
// Default: HTTP — safe for all module types and preserves all named exports
|
|
1300
|
+
return { route: 'http' };
|
|
1301
|
+
}
|
|
428
1302
|
function stripCoreGlobalsImports(code) {
|
|
429
1303
|
const pattern = /^\s*(?:import\s+(?:[^'"\n]*from\s+)?|export\s+\*\s+from\s+)["'][^"']*(?:@nativescript(?:[/_-])core(?:[\/_-])globals|@nativescript_core_globals)[^"']*["'];?\s*$/gm;
|
|
430
1304
|
return code.replace(pattern, '');
|
|
@@ -471,6 +1345,109 @@ function ensureGuardPlainDynamicImports(code, origin) {
|
|
|
471
1345
|
return code;
|
|
472
1346
|
}
|
|
473
1347
|
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Expand `export * from "url"` into explicit named re-exports.
|
|
1350
|
+
*
|
|
1351
|
+
* NativeScript's HTTP ESM loader on iOS/Android may not correctly propagate
|
|
1352
|
+
* `export * from` re-exports across HTTP module boundaries — the importing
|
|
1353
|
+
* module's namespace object gets only direct exports, missing re-exported
|
|
1354
|
+
* names. This function resolves each `export * from` by fetching the target
|
|
1355
|
+
* module, scanning for its named exports, and replacing the star export with
|
|
1356
|
+
* explicit `export { name1, name2, ... } from "url"`.
|
|
1357
|
+
*
|
|
1358
|
+
* Only expands star exports pointing to node_modules HTTP URLs to avoid
|
|
1359
|
+
* unnecessary work for app source files (which are typically not re-exported).
|
|
1360
|
+
*/
|
|
1361
|
+
async function expandStarExports(code, server, projectRoot, verbose) {
|
|
1362
|
+
const STAR_RE = /^[ \t]*(export\s+\*\s+from\s+["'])([^"']+)(["'];?)[ \t]*$/gm;
|
|
1363
|
+
let match;
|
|
1364
|
+
const replacements = [];
|
|
1365
|
+
while ((match = STAR_RE.exec(code)) !== null) {
|
|
1366
|
+
const url = match[2];
|
|
1367
|
+
// Only expand node_modules star exports served over HTTP
|
|
1368
|
+
if (!url.includes('/node_modules/'))
|
|
1369
|
+
continue;
|
|
1370
|
+
replacements.push({ full: match[0], url, prefix: match[1], suffix: match[3] });
|
|
1371
|
+
}
|
|
1372
|
+
if (!replacements.length)
|
|
1373
|
+
return code;
|
|
1374
|
+
for (const rep of replacements) {
|
|
1375
|
+
try {
|
|
1376
|
+
// Strip HTTP origin to get a Vite-resolvable path
|
|
1377
|
+
let vitePath = rep.url.replace(/^https?:\/\/[^/]+/, '');
|
|
1378
|
+
// Strip /ns/m/ prefix to get the node_modules path
|
|
1379
|
+
vitePath = vitePath.replace(/^\/ns\/m\//, '/');
|
|
1380
|
+
// Strip boot-path prefix used during initial HTTP boot progress tracking.
|
|
1381
|
+
vitePath = vitePath.replace(/^\/__ns_boot__\/[^/]+/, '');
|
|
1382
|
+
// Strip HMR cache-busting path segments
|
|
1383
|
+
vitePath = vitePath.replace(/\/__ns_hmr__\/[^/]+/, '');
|
|
1384
|
+
const r = await server.transformRequest(vitePath);
|
|
1385
|
+
if (!r?.code)
|
|
1386
|
+
continue;
|
|
1387
|
+
const names = extractExportedNames(r.code);
|
|
1388
|
+
if (!names.length)
|
|
1389
|
+
continue;
|
|
1390
|
+
// Replace `export * from "url"` with explicit named re-exports
|
|
1391
|
+
const explicit = `export { ${names.join(', ')} } from ${JSON.stringify(rep.url)};`;
|
|
1392
|
+
code = code.replace(rep.full, explicit);
|
|
1393
|
+
if (verbose) {
|
|
1394
|
+
console.log(`[ns/m] expanded export* → ${names.length} names from ${vitePath}`);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
catch { }
|
|
1398
|
+
}
|
|
1399
|
+
return code;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Extract named export identifiers from a module's source code.
|
|
1403
|
+
* Handles: export function/class/const/let/var NAME, export { NAME },
|
|
1404
|
+
* export { NAME as ALIAS }, and export * from (recursive marker).
|
|
1405
|
+
* Does NOT follow `export * from` chains — only direct exports.
|
|
1406
|
+
*/
|
|
1407
|
+
function extractExportedNames(code) {
|
|
1408
|
+
const names = new Set();
|
|
1409
|
+
// export function NAME / export class NAME / export async function NAME
|
|
1410
|
+
const declRe = /\bexport\s+(?:async\s+)?(?:function|class)\s+([A-Za-z_$][\w$]*)/g;
|
|
1411
|
+
let m;
|
|
1412
|
+
while ((m = declRe.exec(code)) !== null) {
|
|
1413
|
+
names.add(m[1]);
|
|
1414
|
+
}
|
|
1415
|
+
// export const/let/var NAME (handles destructuring and multiple declarators)
|
|
1416
|
+
const varRe = /\bexport\s+(?:const|let|var)\s+([^=;{]+)/g;
|
|
1417
|
+
while ((m = varRe.exec(code)) !== null) {
|
|
1418
|
+
// Simple case: `export const foo = ...`
|
|
1419
|
+
const decl = m[1].trim();
|
|
1420
|
+
// Could be `{ a, b }` (destructuring) or `foo, bar` (multiple) or just `foo`
|
|
1421
|
+
if (decl.startsWith('{')) {
|
|
1422
|
+
const inner = decl.replace(/^\{|\}$/g, '');
|
|
1423
|
+
for (const part of inner.split(',')) {
|
|
1424
|
+
const name = part.split(':')[0].trim(); // handle { orig: alias }
|
|
1425
|
+
if (/^[A-Za-z_$][\w$]*$/.test(name))
|
|
1426
|
+
names.add(name);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
const name = decl.split(/[\s,=]/)[0].trim();
|
|
1431
|
+
if (/^[A-Za-z_$][\w$]*$/.test(name))
|
|
1432
|
+
names.add(name);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
// export { NAME, NAME as ALIAS, ... } (without `from`)
|
|
1436
|
+
// and export { NAME, ... } from "..." (re-exports)
|
|
1437
|
+
const braceRe = /\bexport\s*\{([^}]+)\}/g;
|
|
1438
|
+
while ((m = braceRe.exec(code)) !== null) {
|
|
1439
|
+
for (const part of m[1].split(',')) {
|
|
1440
|
+
const trimmed = part.trim();
|
|
1441
|
+
// `name as alias` → use alias; `name` → use name
|
|
1442
|
+
const asMatch = trimmed.match(/\S+\s+as\s+(\S+)/);
|
|
1443
|
+
const name = asMatch ? asMatch[1] : trimmed.split(/\s/)[0];
|
|
1444
|
+
if (name && /^[A-Za-z_$][\w$]*$/.test(name) && name !== 'default') {
|
|
1445
|
+
names.add(name);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
return Array.from(names);
|
|
1450
|
+
}
|
|
474
1451
|
// Heal accidental "import ... = expr" assignments produced by upstream transforms.
|
|
475
1452
|
// These are invalid JS; convert to equivalent const assignments.
|
|
476
1453
|
function repairImportEqualsAssignments(code) {
|
|
@@ -521,6 +1498,11 @@ function ensureVersionedCoreImports(code, origin, ver) {
|
|
|
521
1498
|
catch { }
|
|
522
1499
|
return code;
|
|
523
1500
|
}
|
|
1501
|
+
export function buildVersionedCoreSubpathAliasModule(sub, ver) {
|
|
1502
|
+
const normalizedSub = (sub || '').replace(/^\/+/, '');
|
|
1503
|
+
const canonicalUrl = `/ns/core/${ver}?p=${normalizedSub}`;
|
|
1504
|
+
return `import * as __ns_core_alias from ${JSON.stringify(canonicalUrl)};\n` + `export default (__ns_core_alias.default || __ns_core_alias);\n` + `export * from ${JSON.stringify(canonicalUrl)};\n`;
|
|
1505
|
+
}
|
|
524
1506
|
// Hardened removal of Vite's virtual dynamic-import-helper. Some variants (side-effect only
|
|
525
1507
|
// or minified forms) slipped past earlier regexes causing runtime attempts to resolve
|
|
526
1508
|
// /@id/__x00__vite/dynamic-import-helper.js which does not exist in the device mirror.
|
|
@@ -809,68 +1791,15 @@ function cleanCode(code) {
|
|
|
809
1791
|
result = ACTIVE_STRATEGY.preClean(result);
|
|
810
1792
|
result = ACTIVE_STRATEGY.rewriteFrameworkImports(result);
|
|
811
1793
|
// Vendor manifest-driven import rewrites
|
|
1794
|
+
// NOTE: Static and side-effect vendor imports are intentionally NOT rewritten here.
|
|
1795
|
+
// They are left as import statements so that ensureNativeScriptModuleBindings()
|
|
1796
|
+
// (called later in processCodeForDevice) can transform them using the robust
|
|
1797
|
+
// __nsVendorRequire + __nsPick pattern that works on device.
|
|
1798
|
+
// Only dynamic imports are handled here since ensureNativeScriptModuleBindings
|
|
1799
|
+
// does not process dynamic import() calls.
|
|
812
1800
|
try {
|
|
813
1801
|
const manifest = getVendorManifest();
|
|
814
1802
|
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
1803
|
// Dynamic import rewrites: import('pkg') -> Promise.resolve(__nsVendor('id'))
|
|
875
1804
|
const dynImportRE = /(import\(\s*["'])([^"']+)(["']\s*\))/g;
|
|
876
1805
|
result = result.replace(dynImportRE, (full, pre, spec, post) => {
|
|
@@ -1023,15 +1952,169 @@ function normalizeAbsoluteFilesystemImport(spec, importerPath, projectRoot) {
|
|
|
1023
1952
|
if (!absolute || absolute === spec) {
|
|
1024
1953
|
return null;
|
|
1025
1954
|
}
|
|
1026
|
-
return absolute;
|
|
1955
|
+
return absolute;
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* After the Angular linker runs on code that Vite has already resolved (bare
|
|
1959
|
+
* specifiers → full URLs), the linker injects NEW import statements with bare
|
|
1960
|
+
* specifiers (e.g. `import {Component} from '@angular/core'`). These cause:
|
|
1961
|
+
* 1. Duplicate-identifier SyntaxErrors (the name was already imported via URL)
|
|
1962
|
+
* 2. Unresolvable bare specifiers at runtime on device
|
|
1963
|
+
*
|
|
1964
|
+
* This function:
|
|
1965
|
+
* • builds a map packageName → resolvedURL from existing resolved imports
|
|
1966
|
+
* • collects all binding names already imported per package
|
|
1967
|
+
* • for each bare-specifier import, removes duplicate bindings
|
|
1968
|
+
* • rewrites any genuinely-new bindings to use the resolved URL
|
|
1969
|
+
*/
|
|
1970
|
+
function deduplicateLinkerImports(code) {
|
|
1971
|
+
if (!code)
|
|
1972
|
+
return code;
|
|
1973
|
+
try {
|
|
1974
|
+
const imports = collectTopLevelImportRecords(code);
|
|
1975
|
+
if (!imports.length) {
|
|
1976
|
+
return code;
|
|
1977
|
+
}
|
|
1978
|
+
// ── Step 1: collect resolved imports already in the file ──────────
|
|
1979
|
+
const pkgUrlMap = new Map();
|
|
1980
|
+
const pkgBindings = new Map();
|
|
1981
|
+
for (const imp of imports) {
|
|
1982
|
+
const url = imp.source;
|
|
1983
|
+
if (!/^https?:\/\//.test(url) && !url.startsWith('/')) {
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
1986
|
+
const nmIdx = url.lastIndexOf('/node_modules/');
|
|
1987
|
+
if (nmIdx === -1)
|
|
1988
|
+
continue;
|
|
1989
|
+
const afterNm = url.substring(nmIdx + '/node_modules/'.length);
|
|
1990
|
+
const parts = afterNm.split('/');
|
|
1991
|
+
const pkg = parts[0].startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
1992
|
+
if (!pkgUrlMap.has(pkg))
|
|
1993
|
+
pkgUrlMap.set(pkg, url);
|
|
1994
|
+
if (imp.namedBindings.length) {
|
|
1995
|
+
if (!pkgBindings.has(pkg))
|
|
1996
|
+
pkgBindings.set(pkg, new Set());
|
|
1997
|
+
for (const binding of imp.namedBindings) {
|
|
1998
|
+
if (binding.importedName)
|
|
1999
|
+
pkgBindings.get(pkg).add(binding.importedName);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
if (pkgUrlMap.size === 0)
|
|
2004
|
+
return code;
|
|
2005
|
+
// ── Step 2: rewrite bare-specifier imports ───────────────────────
|
|
2006
|
+
const edits = [];
|
|
2007
|
+
for (const imp of imports) {
|
|
2008
|
+
if (!imp.hasOnlyNamedSpecifiers) {
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
const specifier = imp.source;
|
|
2012
|
+
if (specifier.startsWith('/') || specifier.startsWith('.') || specifier.startsWith('http')) {
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
const parts = specifier.split('/');
|
|
2016
|
+
const pkg = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
2017
|
+
const url = pkgUrlMap.get(pkg);
|
|
2018
|
+
if (!url) {
|
|
2019
|
+
continue;
|
|
2020
|
+
}
|
|
2021
|
+
const existing = pkgBindings.get(pkg) || new Set();
|
|
2022
|
+
const newBindings = imp.namedBindings.filter((binding) => !existing.has(binding.importedName));
|
|
2023
|
+
if (newBindings.length === 0) {
|
|
2024
|
+
edits.push({ start: imp.start, end: imp.end, text: '' });
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
if (newBindings.length === imp.namedBindings.length) {
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
for (const binding of newBindings) {
|
|
2031
|
+
existing.add(binding.importedName);
|
|
2032
|
+
}
|
|
2033
|
+
edits.push({
|
|
2034
|
+
start: imp.start,
|
|
2035
|
+
end: imp.end,
|
|
2036
|
+
text: `import { ${newBindings.map((binding) => binding.text).join(', ')} } from ${JSON.stringify(url)};`,
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
if (!edits.length) {
|
|
2040
|
+
return code;
|
|
2041
|
+
}
|
|
2042
|
+
let next = code;
|
|
2043
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
2044
|
+
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
2045
|
+
}
|
|
2046
|
+
return next;
|
|
2047
|
+
}
|
|
2048
|
+
catch {
|
|
2049
|
+
return code;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
export function wrapCommonJsModuleForDevice(code) {
|
|
2053
|
+
if (!code)
|
|
2054
|
+
return code;
|
|
2055
|
+
try {
|
|
2056
|
+
const hasExportDefault = /\bexport\s+default\b/.test(code) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(code);
|
|
2057
|
+
const hasNamedExports = /\bexport\s+(?:const|let|var|function|class|async)\b/.test(code) || /\bexport\s*\{/.test(code);
|
|
2058
|
+
const hasCjsExports = /\bmodule\s*\.\s*exports\b/.test(code) || /\bexports\s*\.\s*\w/.test(code);
|
|
2059
|
+
if (hasExportDefault || hasNamedExports || !hasCjsExports) {
|
|
2060
|
+
return code;
|
|
2061
|
+
}
|
|
2062
|
+
const namedExports = new Set();
|
|
2063
|
+
const exportsRe = /\bexports\s*\.\s*([A-Za-z_$][\w$]*)\s*=/g;
|
|
2064
|
+
let em;
|
|
2065
|
+
while ((em = exportsRe.exec(code)) !== null) {
|
|
2066
|
+
const name = em[1];
|
|
2067
|
+
if (name !== '__esModule' && name !== 'default') {
|
|
2068
|
+
namedExports.add(name);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
const defPropRe = /Object\s*\.\s*defineProperty\s*\(\s*exports\s*,\s*['"]([^'"]+)['"]/g;
|
|
2072
|
+
while ((em = defPropRe.exec(code)) !== null) {
|
|
2073
|
+
const name = em[1];
|
|
2074
|
+
if (name !== '__esModule' && name !== 'default') {
|
|
2075
|
+
namedExports.add(name);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
let suffix = `\nvar __cjs_mod = module.exports;\nexport default __cjs_mod;\n`;
|
|
2079
|
+
if (namedExports.size) {
|
|
2080
|
+
const entries = Array.from(namedExports);
|
|
2081
|
+
const temps = entries.map((name, i) => `var __cjs_e${i} = __cjs_mod[${JSON.stringify(name)}];`);
|
|
2082
|
+
const reExports = entries.map((name, i) => `__cjs_e${i} as ${name}`);
|
|
2083
|
+
suffix += `${temps.join(' ')}\nexport { ${reExports.join(', ')} };\n`;
|
|
2084
|
+
}
|
|
2085
|
+
const prelude = `var module = { exports: {} }; var exports = module.exports;\n` +
|
|
2086
|
+
`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` +
|
|
2087
|
+
`var __ns_cjs_require_kind = (typeof globalThis.__nsBaseRequire === 'function' ? 'base-require' : (typeof globalThis.__nsRequire === 'function' ? 'vendor-require' : 'global-require'));\n` +
|
|
2088
|
+
`var require = function(spec) {\n` +
|
|
2089
|
+
` if (!__ns_cjs_require_base) { throw new Error('require is not defined'); }\n` +
|
|
2090
|
+
` try { var __nsRecord = globalThis.__NS_RECORD_MODULE_PROVENANCE__; if (typeof __nsRecord === 'function') { __nsRecord(String(spec), { kind: __ns_cjs_require_kind, specifier: String(spec), via: 'cjs-wrapper', parent: (typeof import.meta !== 'undefined' && import.meta && import.meta.url) ? import.meta.url : undefined }); } } catch (e) {}\n` +
|
|
2091
|
+
` var mod = __ns_cjs_require_base(spec);\n` +
|
|
2092
|
+
` try {\n` +
|
|
2093
|
+
` if (mod && (typeof mod === 'object' || typeof mod === 'function') && mod.default !== undefined) {\n` +
|
|
2094
|
+
` var keys = [];\n` +
|
|
2095
|
+
` try { keys = Object.keys(mod); } catch (e) {}\n` +
|
|
2096
|
+
` var defaultOnly = keys.length === 1 && keys[0] === 'default';\n` +
|
|
2097
|
+
` var esModuleOnly = keys.length === 2 && keys.indexOf('default') !== -1 && keys.indexOf('__esModule') !== -1;\n` +
|
|
2098
|
+
` if (mod.__esModule || defaultOnly || esModuleOnly) { return mod.default; }\n` +
|
|
2099
|
+
` }\n` +
|
|
2100
|
+
` } catch (e) {}\n` +
|
|
2101
|
+
` return mod;\n` +
|
|
2102
|
+
`};\n`;
|
|
2103
|
+
return `${prelude}${code}${suffix}`;
|
|
2104
|
+
}
|
|
2105
|
+
catch {
|
|
2106
|
+
return code;
|
|
2107
|
+
}
|
|
1027
2108
|
}
|
|
1028
2109
|
/**
|
|
1029
2110
|
* Process code for device: inject globals, remove framework imports
|
|
1030
2111
|
*/
|
|
1031
|
-
function processCodeForDevice(code, isVitePreBundled) {
|
|
2112
|
+
function processCodeForDevice(code, isVitePreBundled, preserveVendorImports = false, isNodeModule = false, sourceId) {
|
|
1032
2113
|
let result = code;
|
|
1033
2114
|
// Ensure Angular partial declarations are linked before any sanitizers run so runtime never hits the JIT path.
|
|
1034
2115
|
result = linkAngularPartialsIfNeeded(result);
|
|
2116
|
+
// Post-linker: deduplicate/resolve imports the Angular linker injected with bare specifiers
|
|
2117
|
+
result = deduplicateLinkerImports(result);
|
|
1035
2118
|
// First: aggressively strip any lingering virtual dynamic-import-helper before anything else.
|
|
1036
2119
|
// Doing this up-front prevents downstream dependency collection from seeing the virtual id.
|
|
1037
2120
|
result = stripViteDynamicImportVirtual(result);
|
|
@@ -1042,13 +2125,17 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1042
2125
|
// Inject ALL NativeScript/build globals at the top (matching global-defines.ts)
|
|
1043
2126
|
// This ensures any code using __DEV__, __ANDROID__, __IOS__, etc. works correctly
|
|
1044
2127
|
const allGlobals = [
|
|
2128
|
+
// Minimal process shim — populated with CLI --env.* flags at module load time.
|
|
2129
|
+
// In production builds, Vite/Rollup replaces process.env.* statically.
|
|
2130
|
+
// In HMR dev mode the code runs as-is on device, so we need the shim.
|
|
2131
|
+
`if (typeof process === "undefined") { globalThis.process = { env: ${__processEnvJson} }; } else if (!process.env) { process.env = ${__processEnvJson}; }`,
|
|
1045
2132
|
'const __ANDROID__ = globalThis.__ANDROID__ !== undefined ? globalThis.__ANDROID__ : false;',
|
|
1046
2133
|
'const __IOS__ = globalThis.__IOS__ !== undefined ? globalThis.__IOS__ : false;',
|
|
1047
2134
|
'const __VISIONOS__ = globalThis.__VISIONOS__ !== undefined ? globalThis.__VISIONOS__ : false;',
|
|
1048
2135
|
'const __APPLE__ = globalThis.__APPLE__ !== undefined ? globalThis.__APPLE__ : (__IOS__ || __VISIONOS__);',
|
|
1049
2136
|
'const __DEV__ = globalThis.__DEV__ !== undefined ? globalThis.__DEV__ : false;',
|
|
1050
2137
|
'const __COMMONJS__ = globalThis.__COMMONJS__ !== undefined ? globalThis.__COMMONJS__ : false;',
|
|
1051
|
-
'const __NS_WEBPACK__ = globalThis.__NS_WEBPACK__ !== undefined ? globalThis.__NS_WEBPACK__ :
|
|
2138
|
+
'const __NS_WEBPACK__ = globalThis.__NS_WEBPACK__ !== undefined ? globalThis.__NS_WEBPACK__ : false;',
|
|
1052
2139
|
'const __NS_ENV_VERBOSE__ = globalThis.__NS_ENV_VERBOSE__ !== undefined ? !!globalThis.__NS_ENV_VERBOSE__ : false;',
|
|
1053
2140
|
"const __CSS_PARSER__ = globalThis.__CSS_PARSER__ !== undefined ? globalThis.__CSS_PARSER__ : 'css-tree';",
|
|
1054
2141
|
'const __UI_USE_XML_PARSER__ = globalThis.__UI_USE_XML_PARSER__ !== undefined ? globalThis.__UI_USE_XML_PARSER__ : true;',
|
|
@@ -1056,19 +2143,29 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1056
2143
|
'const __TEST__ = globalThis.__TEST__ !== undefined ? globalThis.__TEST__ : false;',
|
|
1057
2144
|
];
|
|
1058
2145
|
result = allGlobals.join('\n') + '\n' + result;
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
result =
|
|
2146
|
+
const nodeModuleProvenancePrelude = buildNodeModuleProvenancePrelude(sourceId);
|
|
2147
|
+
if (nodeModuleProvenancePrelude) {
|
|
2148
|
+
result = nodeModuleProvenancePrelude + result;
|
|
1062
2149
|
}
|
|
1063
|
-
|
|
1064
|
-
//
|
|
1065
|
-
|
|
1066
|
-
|
|
2150
|
+
// AST normalization: inject /ns/rt helper aliases for underscore-prefixed identifiers.
|
|
2151
|
+
// ONLY for app source files — library code in node_modules should be served as-is.
|
|
2152
|
+
// Running the normalizer on libraries like tslib injects harmful destructures
|
|
2153
|
+
// (e.g., `const { SuppressedError } = __ns_rt_ns_1`) that shadow globals.
|
|
2154
|
+
if (!isNodeModule) {
|
|
2155
|
+
try {
|
|
2156
|
+
result = astNormalizeModuleImportsAndHelpers(result);
|
|
2157
|
+
}
|
|
2158
|
+
catch { }
|
|
2159
|
+
// Verify there are no duplicate top-level const/let bindings after AST normalization
|
|
2160
|
+
try {
|
|
2161
|
+
result = astVerifyAndAnnotateDuplicates(result);
|
|
2162
|
+
}
|
|
2163
|
+
catch { }
|
|
1067
2164
|
}
|
|
1068
|
-
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
if (!/^\s*(?:\/\/|\/\*) \[ast-normalized\]/m.test(result)) {
|
|
2165
|
+
// If AST marker present OR this is a node_modules file, skip regex-based helper
|
|
2166
|
+
// alias injection. Library code should NOT get /ns/rt destructures injected —
|
|
2167
|
+
// underscore-prefixed identifiers in libraries are internal variables, not NS helpers.
|
|
2168
|
+
if (!isNodeModule && !/^\s*(?:\/\/|\/\*) \[ast-normalized\]/m.test(result)) {
|
|
1072
2169
|
try {
|
|
1073
2170
|
const underscored = new Set();
|
|
1074
2171
|
const re = /(^|[^.\w$])_([A-Za-z]\w*)\b/g;
|
|
@@ -1149,7 +2246,11 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1149
2246
|
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
2247
|
}
|
|
1151
2248
|
catch { }
|
|
1152
|
-
|
|
2249
|
+
// When preserveVendorImports is true (HMR /ns/m/ endpoint), skip the
|
|
2250
|
+
// __nsVendorRequire + __nsPick rewrite. Vendor imports stay as bare
|
|
2251
|
+
// specifiers so the device-side import map resolves them via V8's native
|
|
2252
|
+
// module system, which correctly handles export * re-exports.
|
|
2253
|
+
result = preserveVendorImports ? ensureNativeScriptModuleBindings(result, { preserveNonPluginVendorImports: true }) : ensureNativeScriptModuleBindings(result);
|
|
1153
2254
|
// Repair any accidental "import ... = expr" assignments that may have slipped in.
|
|
1154
2255
|
try {
|
|
1155
2256
|
result = repairImportEqualsAssignments(result);
|
|
@@ -1158,10 +2259,7 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1158
2259
|
// Strip Vite prebundle deps imports (both named and side-effect) and any malformed const string artifacts
|
|
1159
2260
|
// Example problematic line observed: const "/node_modules/.vite/deps/@nativescript_firebase-messaging.js?v=...";
|
|
1160
2261
|
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, '');
|
|
2262
|
+
result = rewriteVitePrebundleImportsForDevice(result, preserveVendorImports);
|
|
1165
2263
|
// Malformed const string lines accidentally produced by upstream transforms
|
|
1166
2264
|
result = result.replace(/^[\t ]*const\s+["']\/?node_modules\/\.vite\/deps\/[^"']+["'];?\s*$/gm, '// [hmr-sanitize] stripped malformed const prebundle ref\n');
|
|
1167
2265
|
// Naked string-only lines pointing at prebundle deps
|
|
@@ -1248,7 +2346,7 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1248
2346
|
}
|
|
1249
2347
|
// Ensure vendor bindings also apply after potential wrapper injections above
|
|
1250
2348
|
// (idempotent: second pass will be a no-op if imports already consumed).
|
|
1251
|
-
result = ensureNativeScriptModuleBindings(result);
|
|
2349
|
+
result = preserveVendorImports ? ensureNativeScriptModuleBindings(result, { preserveNonPluginVendorImports: true }) : ensureNativeScriptModuleBindings(result);
|
|
1252
2350
|
try {
|
|
1253
2351
|
result = repairImportEqualsAssignments(result);
|
|
1254
2352
|
}
|
|
@@ -1315,6 +2413,9 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1315
2413
|
.join(', ');
|
|
1316
2414
|
const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1317
2415
|
result = result.replace(reNamed, (_full, pfx, specList, src) => {
|
|
2416
|
+
// Deep subpath URLs serve actual ESM with real named exports — skip.
|
|
2417
|
+
if (isDeepCoreSubpath(src))
|
|
2418
|
+
return _full;
|
|
1318
2419
|
__core_ns_seq++;
|
|
1319
2420
|
const tmp = `__ns_core_ns${__core_ns_seq}`;
|
|
1320
2421
|
const decl = `const { ${toDestructureCore(specList)} } = ${tmp};`;
|
|
@@ -1322,6 +2423,8 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1322
2423
|
});
|
|
1323
2424
|
const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1324
2425
|
result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
|
|
2426
|
+
if (isDeepCoreSubpath(src))
|
|
2427
|
+
return _full;
|
|
1325
2428
|
const decl = `const { ${toDestructureCore(specList)} } = ${defName};`;
|
|
1326
2429
|
return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1327
2430
|
});
|
|
@@ -1335,8 +2438,11 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1335
2438
|
// Keep a single semicolon before the import to avoid generating ';;'
|
|
1336
2439
|
result = result.replace(/;\s*import\s+/g, ';\nimport ');
|
|
1337
2440
|
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
|
-
|
|
2441
|
+
// Fallback: ensure any static import that isn't at start of line gets a newline before it.
|
|
2442
|
+
// Only match after statement-ending characters (;, }, ), ], quotes) — NOT after `*` or
|
|
2443
|
+
// spaces inside JSDoc comment blocks, which would accidentally extract example imports
|
|
2444
|
+
// from documentation comments and hoist them as real code.
|
|
2445
|
+
result = result.replace(/([;}\)\]'"`])\s*(import\s+[^;\n]*\s+from\s*["'][^"']+["'])/g, '$1\n$2');
|
|
1340
2446
|
}
|
|
1341
2447
|
catch { }
|
|
1342
2448
|
// Collapse duplicate destructuring from the same temp namespace var (e.g., multiple const { x } = __ns_rt_ns1)
|
|
@@ -1370,22 +2476,13 @@ function processCodeForDevice(code, isVitePreBundled) {
|
|
|
1370
2476
|
// always come before any statements that might reference their bindings. This ordering avoids
|
|
1371
2477
|
// device runtimes that are stricter about imports-first semantics during module instantiation.
|
|
1372
2478
|
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
|
-
}
|
|
2479
|
+
result = hoistTopLevelStaticImports(result);
|
|
1383
2480
|
}
|
|
1384
2481
|
catch { }
|
|
1385
2482
|
// Final safety: normalize any lingering named imports from /ns/rt into default+destructure
|
|
1386
|
-
// Skip
|
|
2483
|
+
// Skip for node_modules (no /ns/rt helpers needed) and when AST marker present
|
|
1387
2484
|
try {
|
|
1388
|
-
if (!/^\s*\/\* \[ast-normalized\] \*\//m.test(result)) {
|
|
2485
|
+
if (!isNodeModule && !/^\s*\/\* \[ast-normalized\] \*\//m.test(result)) {
|
|
1389
2486
|
result = ensureDestructureRtImports(result);
|
|
1390
2487
|
}
|
|
1391
2488
|
}
|
|
@@ -1531,6 +2628,13 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
|
|
|
1531
2628
|
}
|
|
1532
2629
|
}
|
|
1533
2630
|
if (localCore.test(ln)) {
|
|
2631
|
+
// Comments can never cause split-realm risk at runtime — skip them.
|
|
2632
|
+
// Library authors commonly reference @nativescript/core in comments
|
|
2633
|
+
// (e.g. TSDoc /// <reference> directives, module resolution notes).
|
|
2634
|
+
const trimmed = ln.trimStart();
|
|
2635
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
1534
2638
|
offenders.push(`${i + 1}: ${ln.substring(0, 200)} [local-core-path]`);
|
|
1535
2639
|
}
|
|
1536
2640
|
if (offenders.length >= 10)
|
|
@@ -1555,6 +2659,7 @@ function assertNoOptimizedArtifacts(code, contextLabel) {
|
|
|
1555
2659
|
function ensureDestructureCoreImports(code) {
|
|
1556
2660
|
try {
|
|
1557
2661
|
let result = code;
|
|
2662
|
+
let coreImportCounter = 0;
|
|
1558
2663
|
const toDestructure = (specList) => specList
|
|
1559
2664
|
.split(',')
|
|
1560
2665
|
.map((s) => s.trim())
|
|
@@ -1567,13 +2672,19 @@ function ensureDestructureCoreImports(code) {
|
|
|
1567
2672
|
// import { A, B } from '/ns/core[/ver][?p=...]'
|
|
1568
2673
|
const reNamed = /(^|\n)\s*import\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1569
2674
|
result = result.replace(reNamed, (_full, pfx, specList, src) => {
|
|
1570
|
-
|
|
2675
|
+
// Deep subpath URLs serve actual ESM with real named exports — skip.
|
|
2676
|
+
if (isDeepCoreSubpath(src))
|
|
2677
|
+
return _full;
|
|
2678
|
+
const tmp = `__ns_core_ns_re${coreImportCounter > 0 ? `_${coreImportCounter}` : ''}`;
|
|
2679
|
+
coreImportCounter++;
|
|
1571
2680
|
const decl = `const { ${toDestructure(specList)} } = ${tmp};`;
|
|
1572
2681
|
return `${pfx}import ${tmp} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1573
2682
|
});
|
|
1574
2683
|
// import Default, { A, B } from '/ns/core[...]'
|
|
1575
2684
|
const reMixed = /(^|\n)\s*import\s+([A-Za-z_$][\w$]*)\s*,\s*\{([^}]+)\}\s*from\s*["']((?:https?:\/\/[^"']+)?\/ns\/core(?:\/[\d]+)?(?:\?p=[^"']+)?)['"];?\s*/gm;
|
|
1576
2685
|
result = result.replace(reMixed, (_full, pfx, defName, specList, src) => {
|
|
2686
|
+
if (isDeepCoreSubpath(src))
|
|
2687
|
+
return _full;
|
|
1577
2688
|
const decl = `const { ${toDestructure(specList)} } = ${defName};`;
|
|
1578
2689
|
return `${pfx}import ${defName} from ${JSON.stringify(src)};\n${decl}\n`;
|
|
1579
2690
|
});
|
|
@@ -1685,14 +2796,16 @@ function dedupeRtNamedImportsAgainstDestructures(code) {
|
|
|
1685
2796
|
/**
|
|
1686
2797
|
* THE SINGLE REWRITE FUNCTION - used everywhere for consistency
|
|
1687
2798
|
*/
|
|
1688
|
-
function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin) {
|
|
2799
|
+
export function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot, verbose = false, outputDirOverrideRel, httpOrigin, resolveVendorAsHttp = false) {
|
|
1689
2800
|
let result = code;
|
|
1690
2801
|
const httpOriginSafe = httpOrigin;
|
|
2802
|
+
const isDynamicImportPrefix = (prefix) => /import\(\s*["']?$/.test(prefix.trimStart());
|
|
1691
2803
|
const importerDir = path.posix.dirname(importerPath);
|
|
1692
2804
|
// Determine importer output relative path (project-relative .mjs) to compute relative imports consistently
|
|
1693
2805
|
const importerOutRel = outputDirOverrideRel || getProjectRelativeImportPath(importerPath, projectRoot) || stripToProjectRelative(importerPath, projectRoot).replace(/\.(ts|js|tsx|jsx|mjs|mts|cts)$/i, '.mjs');
|
|
1694
2806
|
const importerOutDir = importerOutRel ? path.posix.dirname(importerOutRel) : '';
|
|
1695
2807
|
const ensureRel = (p) => (p.startsWith('.') ? p : `./${p}`);
|
|
2808
|
+
const isNsSfcSpecifier = (spec) => /^(?:https?:\/\/[^/]+)?\/ns\/sfc(?:\/\d+)?(?:\/|$)/.test(spec.replace(PAT.QUERY_PATTERN, ''));
|
|
1696
2809
|
// Normalize all @nativescript/core imports to the unified HTTP ESM core bridge to guarantee a single realm on device
|
|
1697
2810
|
try {
|
|
1698
2811
|
let coreAliasIdx = 0;
|
|
@@ -1835,6 +2948,16 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1835
2948
|
return `${prefix}${stub}${suffix}`;
|
|
1836
2949
|
}
|
|
1837
2950
|
spec = normalizeNativeScriptCoreSpecifier(spec);
|
|
2951
|
+
// Route Vite virtual modules (/@solid-refresh, etc.) through /ns/m/ so their
|
|
2952
|
+
// internal imports (e.g. solid-js) get vendor-rewritten by our pipeline.
|
|
2953
|
+
// Skip known Vite internals (/@vite/, /@id/, /@fs/) which are handled elsewhere.
|
|
2954
|
+
if (spec.startsWith('/@') && !/^\/@(?:vite|id|fs)\//.test(spec)) {
|
|
2955
|
+
const out = `/ns/m${spec}`;
|
|
2956
|
+
if (httpOriginSafe) {
|
|
2957
|
+
return `${prefix}${httpOriginSafe}${out}${suffix}`;
|
|
2958
|
+
}
|
|
2959
|
+
return `${prefix}${out}${suffix}`;
|
|
2960
|
+
}
|
|
1838
2961
|
// Route internal NS endpoints to absolute HTTP origin for device
|
|
1839
2962
|
if (spec.startsWith('/ns/')) {
|
|
1840
2963
|
if (httpOriginSafe) {
|
|
@@ -1847,19 +2970,30 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1847
2970
|
}
|
|
1848
2971
|
const nodeModulesSpecifier = normalizeNodeModulesSpecifier(spec);
|
|
1849
2972
|
const candidateNativeScriptSpec = nodeModulesSpecifier ?? spec;
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
return `${prefix}${spec.replace(PAT.QUERY_PATTERN, '')}${suffix}`;
|
|
1856
|
-
}
|
|
1857
|
-
if (isNativeScriptPluginModule(candidateNativeScriptSpec)) {
|
|
1858
|
-
const bareSpecifier = candidateNativeScriptSpec.replace(PAT.QUERY_PATTERN, '');
|
|
1859
|
-
return `${prefix}${bareSpecifier}${suffix}`;
|
|
1860
|
-
}
|
|
2973
|
+
// ── Node modules routing ──────────────────────────────────────
|
|
2974
|
+
// Uses the package's own package.json exports field to determine
|
|
2975
|
+
// whether an import is the main entry (→ vendor bridge) or a
|
|
2976
|
+
// subpath entry (→ HTTP). This replaces the old heuristic-based
|
|
2977
|
+
// approach that tried to guess from file paths.
|
|
1861
2978
|
if (nodeModulesSpecifier) {
|
|
1862
|
-
|
|
2979
|
+
const vendorRouting = resolveVendorRouting(nodeModulesSpecifier, projectRoot);
|
|
2980
|
+
if (vendorRouting) {
|
|
2981
|
+
if (vendorRouting.route === 'vendor') {
|
|
2982
|
+
return `${prefix}${vendorRouting.bareSpec}${suffix}`;
|
|
2983
|
+
}
|
|
2984
|
+
// Vendor package but subpath/platform-specific → HTTP
|
|
2985
|
+
const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
2986
|
+
if (httpOriginSafe) {
|
|
2987
|
+
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
2988
|
+
}
|
|
2989
|
+
return `${prefix}${httpSpec}${suffix}`;
|
|
2990
|
+
}
|
|
2991
|
+
// Not a vendor package → serve via HTTP from Vite dev server
|
|
2992
|
+
const httpSpec = `/ns/m/node_modules/${nodeModulesSpecifier}`;
|
|
2993
|
+
if (httpOriginSafe) {
|
|
2994
|
+
return `${prefix}${httpOriginSafe}${httpSpec}${suffix}`;
|
|
2995
|
+
}
|
|
2996
|
+
return `${prefix}${httpSpec}${suffix}`;
|
|
1863
2997
|
}
|
|
1864
2998
|
// Handle .vue imports
|
|
1865
2999
|
if (PAT.VUE_FILE_PATTERN.test(spec)) {
|
|
@@ -1916,6 +3050,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1916
3050
|
const baseId = absMaybe ? toAppModuleBaseId(absMaybe, projectRoot) : null; // e.g. /src/foo.mjs
|
|
1917
3051
|
if (baseId) {
|
|
1918
3052
|
const httpSpec = `/ns/m${baseId}`;
|
|
3053
|
+
if (isDynamicImportPrefix(prefix)) {
|
|
3054
|
+
if (verbose)
|
|
3055
|
+
console.log(`[rewrite][http] dynamic relative app import → ${httpSpec} (from ${spec})`);
|
|
3056
|
+
return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
|
|
3057
|
+
}
|
|
1919
3058
|
if (verbose)
|
|
1920
3059
|
console.log(`[rewrite][http] relative app import → ${httpSpec} (from ${spec})`);
|
|
1921
3060
|
return `${prefix}${httpSpec}${suffix}`;
|
|
@@ -1928,6 +3067,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1928
3067
|
const baseId = toAppModuleBaseId(spec, projectRoot);
|
|
1929
3068
|
if (baseId) {
|
|
1930
3069
|
const httpSpec = `/ns/m${baseId}`;
|
|
3070
|
+
if (isDynamicImportPrefix(prefix)) {
|
|
3071
|
+
if (verbose)
|
|
3072
|
+
console.log(`[rewrite][http] dynamic app import → ${httpSpec} (from ${spec})`);
|
|
3073
|
+
return `__nsDynamicHmrImport(${JSON.stringify(httpSpec)})`;
|
|
3074
|
+
}
|
|
1931
3075
|
if (verbose)
|
|
1932
3076
|
console.log(`[rewrite][http] absolute app import → ${httpSpec} (from ${spec})`);
|
|
1933
3077
|
return `${prefix}${httpSpec}${suffix}`;
|
|
@@ -1959,6 +3103,10 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1959
3103
|
result = result.replace(PAT.IMPORT_PATTERN_2, replaceVueImport);
|
|
1960
3104
|
result = result.replace(PAT.EXPORT_PATTERN, replaceVueImport);
|
|
1961
3105
|
result = result.replace(PAT.IMPORT_PATTERN_3, replaceVueImport);
|
|
3106
|
+
// Side-effect imports (import "spec") — must run AFTER named-import patterns
|
|
3107
|
+
// since IMPORT_PATTERN_1 already handles `import ... from "spec"`.
|
|
3108
|
+
result = result.replace(PAT.IMPORT_PATTERN_SIDE_EFFECT, replaceVueImport);
|
|
3109
|
+
result = ensureDynamicHmrImportHelper(result);
|
|
1962
3110
|
// Extra guard: map any lingering dynamic import('@') to a safe stub module path
|
|
1963
3111
|
// to prevent device runtime normalization errors.
|
|
1964
3112
|
// Example matched: import('@') or import("@") with optional whitespace before closing paren
|
|
@@ -1999,6 +3147,11 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
1999
3147
|
// In HTTP mode, skip legacy local-path rewrite to avoid mixing module origins
|
|
2000
3148
|
result = result.replace(PAT.VUE_FILE_IMPORT, (_m, p1, spec, p3) => {
|
|
2001
3149
|
if (httpOrigin) {
|
|
3150
|
+
if (isNsSfcSpecifier(spec)) {
|
|
3151
|
+
if (verbose)
|
|
3152
|
+
console.log(`[rewrite] .vue already routed (VUE_FILE_IMPORT http): ${spec}`);
|
|
3153
|
+
return `${p1}${spec}${p3}`;
|
|
3154
|
+
}
|
|
2002
3155
|
// Route via /ns/sfc with full query preserved
|
|
2003
3156
|
try {
|
|
2004
3157
|
let base = spec;
|
|
@@ -2064,6 +3217,8 @@ function rewriteImports(code, importerPath, sfcFileMap, depFileMap, projectRoot,
|
|
|
2064
3217
|
function createHmrWebSocketPlugin(opts) {
|
|
2065
3218
|
const verbose = !!opts.verbose;
|
|
2066
3219
|
let wss = null;
|
|
3220
|
+
let sharedTransformRequest;
|
|
3221
|
+
const pendingAngularReloadSuppressions = new Map();
|
|
2067
3222
|
const sfcFileMap = new Map();
|
|
2068
3223
|
const depFileMap = new Map();
|
|
2069
3224
|
// Generic module manifest (spec -> emitted relative .mjs path)
|
|
@@ -2078,6 +3233,22 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2078
3233
|
// Transactional HMR batches: map graphVersion -> ordered list of changed ids for that version
|
|
2079
3234
|
const txnBatches = new Map();
|
|
2080
3235
|
const graph = new Map();
|
|
3236
|
+
function rememberAngularReloadSuppression(root, file, ttlMs = 3000) {
|
|
3237
|
+
const absPath = normalizeHotReloadMatchPath(file);
|
|
3238
|
+
const relPath = normalizeHotReloadMatchPath(file, root);
|
|
3239
|
+
pendingAngularReloadSuppressions.set(absPath, {
|
|
3240
|
+
absPath,
|
|
3241
|
+
relPath,
|
|
3242
|
+
expiresAt: Date.now() + ttlMs,
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
function pruneAngularReloadSuppressions(now = Date.now()) {
|
|
3246
|
+
for (const [key, entry] of pendingAngularReloadSuppressions) {
|
|
3247
|
+
if (!entry || entry.expiresAt <= now) {
|
|
3248
|
+
pendingAngularReloadSuppressions.delete(key);
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
2081
3252
|
// Compute a dependency-closed, topologically sorted list of modules for a given set of changed ids.
|
|
2082
3253
|
// Only include application modules we can serve (e.g., under /src and known .vue/.ts/.js entries in the graph).
|
|
2083
3254
|
function computeTxnOrderForChanged(changedIds) {
|
|
@@ -2323,6 +3494,41 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2323
3494
|
const httpServer = server.httpServer;
|
|
2324
3495
|
if (!httpServer)
|
|
2325
3496
|
return;
|
|
3497
|
+
const wsAny = server.ws;
|
|
3498
|
+
if (!wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__) {
|
|
3499
|
+
const originalSend = server.ws.send.bind(server.ws);
|
|
3500
|
+
wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__ = true;
|
|
3501
|
+
server.ws.send = ((payload, ...rest) => {
|
|
3502
|
+
pruneAngularReloadSuppressions();
|
|
3503
|
+
if (shouldSuppressViteFullReloadPayload({
|
|
3504
|
+
payload,
|
|
3505
|
+
pendingEntries: pendingAngularReloadSuppressions.values(),
|
|
3506
|
+
root: pluginRoot,
|
|
3507
|
+
})) {
|
|
3508
|
+
if (verbose) {
|
|
3509
|
+
console.log('[hmr-ws][angular] suppressed vite full-reload payload', payload);
|
|
3510
|
+
}
|
|
3511
|
+
return;
|
|
3512
|
+
}
|
|
3513
|
+
return originalSend(payload, ...rest);
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
// Default to serialized transform execution for deterministic HTTP HMR startup.
|
|
3517
|
+
// Higher fan-out can be re-enabled explicitly via NS_VITE_HMR_TRANSFORM_CONCURRENCY.
|
|
3518
|
+
const configuredTransformConcurrency = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CONCURRENCY || '1', 10);
|
|
3519
|
+
const transformConcurrency = Number.isFinite(configuredTransformConcurrency) && configuredTransformConcurrency > 0 ? configuredTransformConcurrency : 1;
|
|
3520
|
+
const configuredTransformCacheMs = Number.parseInt(process.env.NS_VITE_HMR_TRANSFORM_CACHE_MS || '15000', 10);
|
|
3521
|
+
const transformCacheMs = Number.isFinite(configuredTransformCacheMs) && configuredTransformCacheMs >= 0 ? configuredTransformCacheMs : 15000;
|
|
3522
|
+
sharedTransformRequest = createSharedTransformRequestRunner((url) => server.transformRequest(url), (url, timeoutMs) => {
|
|
3523
|
+
try {
|
|
3524
|
+
console.warn('[ns:m] slow transformRequest for', url, '(>' + timeoutMs + 'ms)');
|
|
3525
|
+
}
|
|
3526
|
+
catch { }
|
|
3527
|
+
}, {
|
|
3528
|
+
maxConcurrent: transformConcurrency,
|
|
3529
|
+
resultCacheTtlMs: transformCacheMs,
|
|
3530
|
+
getResultCacheKey: (url) => canonicalizeTransformRequestCacheKey(url, pluginRoot),
|
|
3531
|
+
});
|
|
2326
3532
|
// Attempt early vendor manifest bootstrap once per server.
|
|
2327
3533
|
if (!vendorBootstrapDone) {
|
|
2328
3534
|
vendorBootstrapDone = true;
|
|
@@ -2381,6 +3587,40 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2381
3587
|
}
|
|
2382
3588
|
catch { }
|
|
2383
3589
|
});
|
|
3590
|
+
// Import map endpoint: GET /ns/import-map.json
|
|
3591
|
+
// Returns the import map + runtime config for __nsConfigureRuntime()
|
|
3592
|
+
server.middlewares.use(async (req, res, next) => {
|
|
3593
|
+
try {
|
|
3594
|
+
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3595
|
+
if (urlObj.pathname !== '/ns/import-map.json')
|
|
3596
|
+
return next();
|
|
3597
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
3598
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
3599
|
+
if (req.method === 'OPTIONS') {
|
|
3600
|
+
res.statusCode = 204;
|
|
3601
|
+
res.end();
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
// Determine origin from request headers or server config
|
|
3605
|
+
const host = req.headers.host || 'localhost:5173';
|
|
3606
|
+
const protocol = 'http';
|
|
3607
|
+
const origin = `${protocol}://${host}`;
|
|
3608
|
+
const runtimeConfig = buildRuntimeConfig({
|
|
3609
|
+
origin,
|
|
3610
|
+
flavor: ACTIVE_STRATEGY?.flavor || 'typescript',
|
|
3611
|
+
});
|
|
3612
|
+
res.setHeader('Content-Type', 'application/json');
|
|
3613
|
+
res.end(JSON.stringify({
|
|
3614
|
+
importMap: JSON.parse(runtimeConfig.importMap),
|
|
3615
|
+
volatilePatterns: runtimeConfig.volatilePatterns,
|
|
3616
|
+
}, null, 2));
|
|
3617
|
+
}
|
|
3618
|
+
catch (err) {
|
|
3619
|
+
console.error('[import-map] error generating import map:', err?.message || err);
|
|
3620
|
+
res.statusCode = 500;
|
|
3621
|
+
res.end(JSON.stringify({ error: 'Failed to generate import map' }));
|
|
3622
|
+
}
|
|
3623
|
+
});
|
|
2384
3624
|
// Dev-only HTTP ESM loader endpoint for device clients
|
|
2385
3625
|
// 1) Legacy JSON module endpoint (kept temporarily): GET /ns-module?path=/abs -> { path, code, additionalFiles }
|
|
2386
3626
|
server.middlewares.use(async (req, res, next) => {
|
|
@@ -2415,13 +3655,15 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2415
3655
|
// Transform via Vite with variant resolution (same as ws ns:fetch-module)
|
|
2416
3656
|
const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
|
|
2417
3657
|
const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
|
|
3658
|
+
const transformRoot = server.config?.root || process.cwd();
|
|
2418
3659
|
const candidates = [];
|
|
2419
3660
|
if (hasExt)
|
|
2420
3661
|
candidates.push(spec);
|
|
2421
3662
|
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');
|
|
3663
|
+
const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, transformRoot);
|
|
2422
3664
|
let transformed = null;
|
|
2423
3665
|
let resolvedCandidate = null;
|
|
2424
|
-
for (const cand of
|
|
3666
|
+
for (const cand of transformCandidates) {
|
|
2425
3667
|
try {
|
|
2426
3668
|
const r = await server.transformRequest(cand);
|
|
2427
3669
|
if (r?.code) {
|
|
@@ -2443,7 +3685,10 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2443
3685
|
code = REQUIRE_GUARD_SNIPPET + code;
|
|
2444
3686
|
// Apply same sanitation/rewrite pipeline used for WS path
|
|
2445
3687
|
code = cleanCode(code);
|
|
2446
|
-
|
|
3688
|
+
// preserveVendorImports=true: vendor imports stay as bare specifiers
|
|
3689
|
+
// for the device-side import map (ns-vendor://) instead of being
|
|
3690
|
+
// transformed to __nsVendorRequire calls with fragile __nsPick lookups.
|
|
3691
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
|
|
2447
3692
|
code = rewriteImports(code, spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
2448
3693
|
code = ensureVariableDynamicImportHelper(code);
|
|
2449
3694
|
// Enforce upstream guarantee: no optimized deps or virtual ids remain
|
|
@@ -2499,7 +3744,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2499
3744
|
if (seen.has(depBase))
|
|
2500
3745
|
continue;
|
|
2501
3746
|
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'];
|
|
3747
|
+
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);
|
|
2503
3748
|
let depTrans = null;
|
|
2504
3749
|
let depResolved = null;
|
|
2505
3750
|
for (const c of depCandidates) {
|
|
@@ -2516,7 +3761,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2516
3761
|
if (depTrans?.code && depResolved) {
|
|
2517
3762
|
let depCode = depTrans.code;
|
|
2518
3763
|
depCode = cleanCode(depCode);
|
|
2519
|
-
depCode = processCodeForDevice(depCode, false);
|
|
3764
|
+
depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
|
|
2520
3765
|
depCode = rewriteImports(depCode, depResolved, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server));
|
|
2521
3766
|
depCode = ensureVariableDynamicImportHelper(depCode);
|
|
2522
3767
|
try {
|
|
@@ -2568,6 +3813,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2568
3813
|
let spec = urlObj.searchParams.get('path') || '';
|
|
2569
3814
|
// Optional graph version pin for deterministic boot
|
|
2570
3815
|
const forcedVer = urlObj.searchParams.get('v');
|
|
3816
|
+
let bootTaggedRequest = false;
|
|
2571
3817
|
if (!spec) {
|
|
2572
3818
|
const base = '/ns/m';
|
|
2573
3819
|
let rest = urlObj.pathname.slice(base.length);
|
|
@@ -2585,22 +3831,36 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2585
3831
|
res.end('export {}\n');
|
|
2586
3832
|
return;
|
|
2587
3833
|
}
|
|
3834
|
+
const serverRoot = (server.config?.root || process.cwd());
|
|
2588
3835
|
spec = spec.replace(/[?#].*$/, '');
|
|
2589
|
-
// Accept path-based HMR
|
|
3836
|
+
// Accept path-based boot/HMR prefixes:
|
|
3837
|
+
// /ns/m/__ns_boot__/b1/<real-spec>
|
|
3838
|
+
// /ns/m/__ns_hmr__/<tag>/<real-spec>
|
|
3839
|
+
// /ns/m/__ns_boot__/b1/__ns_hmr__/<tag>/<real-spec>
|
|
2590
3840
|
// The iOS HTTP ESM loader canonicalizes cache keys by stripping query params,
|
|
2591
3841
|
// so we must carry the cache-buster in the path.
|
|
2592
3842
|
try {
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
3843
|
+
let changed = true;
|
|
3844
|
+
while (changed) {
|
|
3845
|
+
changed = false;
|
|
3846
|
+
const bootMatch = spec.match(/^\/?__ns_boot__\/[^\/]+(\/.*)?$/);
|
|
3847
|
+
if (bootMatch) {
|
|
3848
|
+
bootTaggedRequest = true;
|
|
3849
|
+
spec = bootMatch[1] || '/';
|
|
3850
|
+
changed = true;
|
|
3851
|
+
}
|
|
3852
|
+
const hmrMatch = spec.match(/^\/?__ns_hmr__\/[^\/]+(\/.*)?$/);
|
|
3853
|
+
if (hmrMatch) {
|
|
3854
|
+
spec = hmrMatch[1] || '/';
|
|
3855
|
+
changed = true;
|
|
3856
|
+
}
|
|
2596
3857
|
}
|
|
2597
3858
|
}
|
|
2598
3859
|
catch { }
|
|
2599
3860
|
// Normalize absolute filesystem paths back to project-relative ids (e.g. /src/app.ts)
|
|
2600
3861
|
try {
|
|
2601
|
-
const projectRoot = (server.config?.root || process.cwd());
|
|
2602
3862
|
const toPosix = (p) => p.replace(/\\/g, '/');
|
|
2603
|
-
const rootPosix = toPosix(
|
|
3863
|
+
const rootPosix = toPosix(serverRoot);
|
|
2604
3864
|
const specPosix = toPosix(spec);
|
|
2605
3865
|
// If spec is an absolute path under the project root, convert to '/'+relative
|
|
2606
3866
|
const isAbsFs = /^\//.test(specPosix) || /^[A-Za-z]:\//.test(spec); // posix or win drive
|
|
@@ -2615,6 +3875,36 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2615
3875
|
}
|
|
2616
3876
|
}
|
|
2617
3877
|
catch { }
|
|
3878
|
+
// Serve Vite virtual modules (/@id/ prefix). These are internal
|
|
3879
|
+
// virtual modules (e.g., \0nsvite:nsconfig-json for ~/package.json)
|
|
3880
|
+
// that don't exist on disk. Decode the ID and load via plugin container.
|
|
3881
|
+
if (spec.startsWith('/@id/')) {
|
|
3882
|
+
try {
|
|
3883
|
+
// First try Vite's transform pipeline directly
|
|
3884
|
+
const vr = await sharedTransformRequest(spec);
|
|
3885
|
+
if (vr?.code) {
|
|
3886
|
+
res.statusCode = 200;
|
|
3887
|
+
res.end(vr.code);
|
|
3888
|
+
return;
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
catch { }
|
|
3892
|
+
try {
|
|
3893
|
+
// Fallback: decode the virtual module ID (__x00__ → \0) and
|
|
3894
|
+
// load through the plugin container directly
|
|
3895
|
+
const rawId = spec.slice('/@id/'.length).replace(/__x00__/g, '\0');
|
|
3896
|
+
const loadResult = await server.pluginContainer.load(rawId);
|
|
3897
|
+
if (loadResult) {
|
|
3898
|
+
const code = typeof loadResult === 'string' ? loadResult : loadResult.code;
|
|
3899
|
+
if (code) {
|
|
3900
|
+
res.statusCode = 200;
|
|
3901
|
+
res.end(code);
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
catch { }
|
|
3907
|
+
}
|
|
2618
3908
|
if (spec.startsWith('@/'))
|
|
2619
3909
|
spec = APP_VIRTUAL_WITH_SLASH + spec.slice(2);
|
|
2620
3910
|
if (spec.startsWith('./'))
|
|
@@ -2624,18 +3914,33 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2624
3914
|
const hasExt = /\.(ts|tsx|js|jsx|mjs|mts|cts|vue)$/i.test(spec);
|
|
2625
3915
|
const baseNoExt = hasExt ? spec.replace(/\.(ts|tsx|js|jsx|mjs|mts|cts)$/i, '') : spec;
|
|
2626
3916
|
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'];
|
|
3917
|
+
const transformCandidates = filterExistingNodeModulesTransformCandidates(spec, candidates, serverRoot);
|
|
2627
3918
|
let transformed = null;
|
|
2628
3919
|
let resolvedCandidate = null;
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
3920
|
+
const rawExplicitModule = tryReadRawExplicitJavaScriptModule(spec, serverRoot);
|
|
3921
|
+
if (rawExplicitModule) {
|
|
3922
|
+
transformed = { code: rawExplicitModule.code };
|
|
3923
|
+
resolvedCandidate = rawExplicitModule.resolvedId;
|
|
3924
|
+
}
|
|
3925
|
+
// Queue and dedupe transformRequest calls so heavy app graphs do not
|
|
3926
|
+
// overwhelm Vite with concurrent work. Slow-transform warnings start only
|
|
3927
|
+
// when the transform actually begins executing, and requests stay pending
|
|
3928
|
+
// until Vite returns a real result.
|
|
3929
|
+
const transformWithTimeout = (url, timeoutMs = 120000) => {
|
|
3930
|
+
return sharedTransformRequest(url, timeoutMs);
|
|
3931
|
+
};
|
|
3932
|
+
if (!transformed?.code) {
|
|
3933
|
+
for (const cand of transformCandidates) {
|
|
3934
|
+
try {
|
|
3935
|
+
const r = await transformWithTimeout(cand);
|
|
3936
|
+
if (r?.code) {
|
|
3937
|
+
transformed = r;
|
|
3938
|
+
resolvedCandidate = cand;
|
|
3939
|
+
break;
|
|
3940
|
+
}
|
|
2636
3941
|
}
|
|
3942
|
+
catch { }
|
|
2637
3943
|
}
|
|
2638
|
-
catch { }
|
|
2639
3944
|
}
|
|
2640
3945
|
// Fallback 1: ask Vite to resolve the id, then transform the resolved id (handles aliases and virtual ids)
|
|
2641
3946
|
if (!transformed?.code) {
|
|
@@ -2643,7 +3948,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2643
3948
|
const rid = await server.pluginContainer?.resolveId?.(spec, undefined);
|
|
2644
3949
|
const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
|
|
2645
3950
|
if (ridStr) {
|
|
2646
|
-
const r = await
|
|
3951
|
+
const r = await transformWithTimeout(ridStr);
|
|
2647
3952
|
if (r?.code) {
|
|
2648
3953
|
transformed = r;
|
|
2649
3954
|
resolvedCandidate = ridStr;
|
|
@@ -2652,27 +3957,49 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2652
3957
|
}
|
|
2653
3958
|
catch { }
|
|
2654
3959
|
}
|
|
3960
|
+
// Fallback 1b: if spec is a /node_modules/ path, extract bare specifier
|
|
3961
|
+
// and try resolveId with that. This handles package.json "exports" field
|
|
3962
|
+
// resolution (e.g., solid-js/jsx-runtime → solid-js/dist/solid.js).
|
|
3963
|
+
if (!transformed?.code && spec.includes('/node_modules/')) {
|
|
3964
|
+
try {
|
|
3965
|
+
const nmIdx = spec.lastIndexOf('/node_modules/');
|
|
3966
|
+
const bare = spec.slice(nmIdx + '/node_modules/'.length);
|
|
3967
|
+
if (bare && !bare.startsWith('.')) {
|
|
3968
|
+
const rid = await server.pluginContainer?.resolveId?.(bare, undefined);
|
|
3969
|
+
const ridStr = typeof rid === 'string' ? rid : rid?.id || null;
|
|
3970
|
+
if (ridStr) {
|
|
3971
|
+
const r = await sharedTransformRequest(ridStr);
|
|
3972
|
+
if (r?.code) {
|
|
3973
|
+
transformed = r;
|
|
3974
|
+
resolvedCandidate = ridStr;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
catch { }
|
|
3980
|
+
}
|
|
2655
3981
|
// Fallback 2: try /@fs absolute path under project root (Vite file system alias)
|
|
2656
3982
|
if (!transformed?.code) {
|
|
2657
3983
|
try {
|
|
2658
|
-
const projectRoot = (server.config?.root || process.cwd());
|
|
2659
3984
|
const toPosix = (p) => p.replace(/\\/g, '/');
|
|
2660
|
-
const rootPosix = toPosix(
|
|
3985
|
+
const rootPosix = toPosix(serverRoot).replace(/\/$/, '');
|
|
2661
3986
|
const absPosix = `${rootPosix}${spec.startsWith('/') ? '' : '/'}${spec}`;
|
|
2662
3987
|
const fsId = `/@fs${absPosix}`;
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
3988
|
+
if (resolveCandidateFilePath(fsId, serverRoot)) {
|
|
3989
|
+
const r = await transformWithTimeout(fsId);
|
|
3990
|
+
if (r?.code) {
|
|
3991
|
+
transformed = r;
|
|
3992
|
+
resolvedCandidate = fsId;
|
|
3993
|
+
}
|
|
2667
3994
|
}
|
|
2668
3995
|
}
|
|
2669
3996
|
catch { }
|
|
2670
3997
|
}
|
|
2671
3998
|
// Fallback 3: try adding ?import to hint Vite's transform pipeline
|
|
2672
3999
|
if (!transformed?.code) {
|
|
2673
|
-
for (const cand of
|
|
4000
|
+
for (const cand of transformCandidates) {
|
|
2674
4001
|
try {
|
|
2675
|
-
const r = await
|
|
4002
|
+
const r = await transformWithTimeout(`${cand}${cand.includes('?') ? '&' : '?'}import`);
|
|
2676
4003
|
if (r?.code) {
|
|
2677
4004
|
transformed = r;
|
|
2678
4005
|
resolvedCandidate = `${cand}?import`;
|
|
@@ -2682,39 +4009,67 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2682
4009
|
catch { }
|
|
2683
4010
|
}
|
|
2684
4011
|
}
|
|
2685
|
-
//
|
|
2686
|
-
//
|
|
2687
|
-
//
|
|
4012
|
+
// Solid HMR: patch @@solid-refresh's $$refreshESM to do inline patching
|
|
4013
|
+
// during module re-evaluation instead of deferring to hot.accept() callback.
|
|
4014
|
+
// In NativeScript's HTTP ESM environment, accept callbacks are registered
|
|
4015
|
+
// but not invoked by the HMR client. By adding a direct patchRegistry()
|
|
4016
|
+
// call when hot.data already has a stored registry, component updates
|
|
4017
|
+
// apply immediately when the module re-evaluates.
|
|
2688
4018
|
try {
|
|
2689
|
-
if (transformed?.code) {
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
4019
|
+
if (transformed?.code && ACTIVE_STRATEGY?.flavor === 'solid' && (resolvedCandidate || spec || '').includes('@solid-refresh')) {
|
|
4020
|
+
const PATCH_SENTINEL = '/* __ns_solid_refresh_patched__ */';
|
|
4021
|
+
const alreadyPatched = transformed.code.includes(PATCH_SENTINEL);
|
|
4022
|
+
console.log('[hmr-ws][solid] @solid-refresh patch check:', { spec: resolvedCandidate || spec, alreadyPatched, codeLen: transformed.code.length });
|
|
4023
|
+
if (!alreadyPatched) {
|
|
4024
|
+
let patchedCode = transformed.code;
|
|
4025
|
+
// Patch 1: Bypass shouldWarnAndDecline() — the vendor-bundled solid-js
|
|
4026
|
+
// may not have the 'development' condition active, making DEV empty/undefined.
|
|
4027
|
+
// In NativeScript HMR mode we are always in dev, so force it to return false.
|
|
4028
|
+
const declineCheck = 'function shouldWarnAndDecline() {';
|
|
4029
|
+
if (patchedCode.includes(declineCheck)) {
|
|
4030
|
+
patchedCode = patchedCode.replace(declineCheck, `${PATCH_SENTINEL}\nfunction shouldWarnAndDecline() { return false; /* NS HMR: always allow refresh */ }\nfunction __original_shouldWarnAndDecline() {`);
|
|
4031
|
+
console.log('[hmr-ws][solid] bypassed shouldWarnAndDecline() for NativeScript HMR');
|
|
2702
4032
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
4033
|
+
// Patch 2: Force createMemo path in createProxy.
|
|
4034
|
+
// Without the 'development' condition, $DEVCOMP is not set on components,
|
|
4035
|
+
// so createProxy falls through to `return s(props)` — a direct call with
|
|
4036
|
+
// no reactive subscription. When patchComponent fires update() (the signal
|
|
4037
|
+
// setter), nobody is listening. By forcing the createMemo path, HMRComp
|
|
4038
|
+
// subscribes to the signal and re-renders when the component changes.
|
|
4039
|
+
const proxyCondition = 'if (!s || $DEVCOMP in s) {';
|
|
4040
|
+
if (patchedCode.includes(proxyCondition)) {
|
|
4041
|
+
patchedCode = patchedCode.replace(proxyCondition, 'if (true) { /* NS HMR: always use createMemo for reactive HMR updates */');
|
|
4042
|
+
console.log('[hmr-ws][solid] forced createMemo path in createProxy for NativeScript HMR');
|
|
2705
4043
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
4044
|
+
// Patch 3: Inline patchRegistry call so updates apply immediately
|
|
4045
|
+
// on module re-evaluation (accept callbacks are not invoked by the HMR client).
|
|
4046
|
+
const marker = 'hot.data[SOLID_REFRESH] = hot.data[SOLID_REFRESH] || registry;';
|
|
4047
|
+
if (patchedCode.includes(marker)) {
|
|
4048
|
+
const patchCode = [
|
|
4049
|
+
`console.log('[solid-refresh][$$refreshESM] hot.data keys=', hot.data ? Object.keys(hot.data) : 'no-data', 'has=', !!(hot.data && hot.data[SOLID_REFRESH]));`,
|
|
4050
|
+
`if (hot.data[SOLID_REFRESH]) {`,
|
|
4051
|
+
` console.log('[solid-refresh][$$refreshESM] patching: oldComponents=', hot.data[SOLID_REFRESH].components ? hot.data[SOLID_REFRESH].components.size : 0, 'newComponents=', registry.components ? registry.components.size : 0);`,
|
|
4052
|
+
` var _shouldInvalidate = patchRegistry(hot.data[SOLID_REFRESH], registry);`,
|
|
4053
|
+
` console.log('[solid-refresh][$$refreshESM] patchRegistry result: shouldInvalidate=', _shouldInvalidate);`,
|
|
4054
|
+
`} else {`,
|
|
4055
|
+
` console.log('[solid-refresh][$$refreshESM] first load — creating registry, components=', registry.components ? registry.components.size : 0);`,
|
|
4056
|
+
`}`,
|
|
4057
|
+
].join('\n ');
|
|
4058
|
+
patchedCode = patchedCode.replace(marker, `${patchCode}\n ${marker}`);
|
|
4059
|
+
console.log('[hmr-ws][solid] added inline patchRegistry for NativeScript HMR');
|
|
4060
|
+
}
|
|
4061
|
+
// Work on a copy to avoid mutating Vite's cached TransformResult
|
|
4062
|
+
transformed = { ...transformed, code: patchedCode };
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
catch { }
|
|
4067
|
+
// NOTE: Path-based cache busting for /ns/m/* imports is applied in the
|
|
4068
|
+
// finalize step below (after rewriteImports adds the /ns/m/ prefix).
|
|
4069
|
+
// The block here only handles TypeScript-specific graph population.
|
|
4070
|
+
try {
|
|
4071
|
+
if (transformed?.code) {
|
|
4072
|
+
const code = transformed.code;
|
|
2718
4073
|
// TypeScript-specific graph population: when TS flavor is active
|
|
2719
4074
|
// and this is an application module under the virtual app root,
|
|
2720
4075
|
// upsert it into the HMR graph so ns:hmr-full-graph is non-empty.
|
|
@@ -2843,7 +4198,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2843
4198
|
if (!transformed?.code) {
|
|
2844
4199
|
// Emit a module that throws with context for easier on-device debugging
|
|
2845
4200
|
try {
|
|
2846
|
-
const tried = Array.from(new Set(candidates)).slice(0, 12);
|
|
4201
|
+
const tried = Array.from(new Set(transformCandidates.length > 0 ? transformCandidates : candidates)).slice(0, 12);
|
|
2847
4202
|
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
4203
|
res.statusCode = 404;
|
|
2849
4204
|
res.end(out);
|
|
@@ -2860,8 +4215,36 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2860
4215
|
// Prepend guard to capture any URL-based require attempts
|
|
2861
4216
|
code = REQUIRE_GUARD_SNIPPET + code;
|
|
2862
4217
|
code = cleanCode(code);
|
|
2863
|
-
|
|
2864
|
-
code =
|
|
4218
|
+
const isNodeMod = /(?:^|\/)node_modules\//.test(resolvedCandidate || spec || '');
|
|
4219
|
+
code = processCodeForDevice(code, false, true, isNodeMod, resolvedCandidate || spec);
|
|
4220
|
+
// Solid HMR: The NativeScript iOS/Android runtime provides import.meta.hot
|
|
4221
|
+
// natively (via InitializeImportMetaHot in HMRSupport.mm) with C++-backed
|
|
4222
|
+
// persistent hot.data that survives across module re-evaluations.
|
|
4223
|
+
// cleanCode() strips Vite's __vite__createHotContext assignment, which is
|
|
4224
|
+
// correct — the runtime's native hot context is better.
|
|
4225
|
+
// We inject a diagnostic log to trace hot.data state during development.
|
|
4226
|
+
try {
|
|
4227
|
+
if (ACTIVE_STRATEGY?.flavor === 'solid' && /\.(tsx|jsx)$/i.test(resolvedCandidate || spec)) {
|
|
4228
|
+
const moduleId = (resolvedCandidate || spec).replace(/[?#].*$/, '');
|
|
4229
|
+
// Diagnostic: log import.meta.hot state on device to trace solid-refresh flow
|
|
4230
|
+
code = `try{if(typeof import.meta!=='undefined'&&import.meta.hot){var _hd=import.meta.hot.data;var _sr=_hd&&_hd['solid-refresh'];console.log('[solid-hmr][native-hot]',${JSON.stringify(moduleId)},'hasHot=true','hasData=',!!_hd,'hasSolidRefresh=',!!_sr,'dataKeys=',_hd?Object.keys(_hd):[]);}else{console.log('[solid-hmr][native-hot]',${JSON.stringify(moduleId)},'hasHot=',!!(typeof import.meta!=='undefined'&&import.meta.hot));}}catch(e){console.log('[solid-hmr][native-hot] error',e);}\n` + code;
|
|
4231
|
+
console.log('[hmr-ws][solid] diagnostic injected for', moduleId, '(using runtime native import.meta.hot)');
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
catch { }
|
|
4235
|
+
code = rewriteImports(code, resolvedCandidate || spec, sfcFileMap, depFileMap, server.config?.root || process.cwd(), !!verbose, undefined, getServerOrigin(server), true);
|
|
4236
|
+
// Expand `export * from "url"` into explicit named re-exports.
|
|
4237
|
+
// NativeScript's HTTP ESM loader may not propagate star-re-exports across
|
|
4238
|
+
// HTTP module boundaries (the namespace object gets direct exports but
|
|
4239
|
+
// misses re-exported names). By expanding to `export { a, b } from "url"`,
|
|
4240
|
+
// the engine sees explicit named exports and resolves them correctly.
|
|
4241
|
+
try {
|
|
4242
|
+
code = await expandStarExports(code, server, server.config?.root || process.cwd(), verbose);
|
|
4243
|
+
}
|
|
4244
|
+
catch (e) {
|
|
4245
|
+
if (verbose)
|
|
4246
|
+
console.warn('[ns/m] export* expansion failed:', e?.message);
|
|
4247
|
+
}
|
|
2865
4248
|
// Dedupe any /ns/rt named imports that duplicate destructured bindings off default /ns/rt
|
|
2866
4249
|
try {
|
|
2867
4250
|
code = dedupeRtNamedImportsAgainstDestructures(code);
|
|
@@ -2888,6 +4271,28 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2888
4271
|
}
|
|
2889
4272
|
}
|
|
2890
4273
|
catch { }
|
|
4274
|
+
// Final pass: deduplicate/resolve any bare-specifier imports that slipped
|
|
4275
|
+
// through the pipeline (e.g., extracted from JSDoc comments by import-splitting
|
|
4276
|
+
// regexes, or injected by the Angular linker on already-resolved code).
|
|
4277
|
+
try {
|
|
4278
|
+
code = deduplicateLinkerImports(code);
|
|
4279
|
+
}
|
|
4280
|
+
catch { }
|
|
4281
|
+
// CJS/UMD wrapping: if a module uses module.exports but has no ESM export default,
|
|
4282
|
+
// wrap it with CJS shims so the device HTTP ESM loader can consume it.
|
|
4283
|
+
// This handles npm packages that use CommonJS but aren't pre-bundled by Vite.
|
|
4284
|
+
//
|
|
4285
|
+
// Key constraints this must handle:
|
|
4286
|
+
// - CJS modules often declare local vars with the same names as their exports
|
|
4287
|
+
// (e.g. `function createLTTB() {...}; exports.createLTTB = createLTTB;`)
|
|
4288
|
+
// so `export var { createLTTB }` would cause a duplicate declaration.
|
|
4289
|
+
// - UMD modules reference `this` at top level (undefined in ESM) but
|
|
4290
|
+
// typically fall back to `self` or `globalThis`.
|
|
4291
|
+
// - `module`, `exports` must be shims since they don't exist in ESM.
|
|
4292
|
+
try {
|
|
4293
|
+
code = wrapCommonJsModuleForDevice(code);
|
|
4294
|
+
}
|
|
4295
|
+
catch { }
|
|
2891
4296
|
try {
|
|
2892
4297
|
assertNoOptimizedArtifacts(code, `NS M ${resolvedCandidate || spec}`);
|
|
2893
4298
|
}
|
|
@@ -2914,23 +4319,37 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2914
4319
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), verNum);
|
|
2915
4320
|
}
|
|
2916
4321
|
catch { }
|
|
2917
|
-
// Finalize:
|
|
4322
|
+
// Finalize: stamp all internal /ns/m imports with PATH-based cache busting.
|
|
4323
|
+
// IMPORTANT: use path prefix (not ?v= query) because the iOS HTTP ESM loader
|
|
4324
|
+
// strips query params when computing module cache keys, so ?v= doesn't bust the V8 cache.
|
|
2918
4325
|
try {
|
|
2919
4326
|
const ver = String(forcedVer || graphVersion || 0);
|
|
2920
4327
|
const origin = getServerOrigin(server);
|
|
4328
|
+
const hmrPrefix = `/ns/m/__ns_hmr__/v${ver}`;
|
|
4329
|
+
const bootHmrPrefix = `/ns/m/__ns_boot__/b1/__ns_hmr__/v${ver}`;
|
|
4330
|
+
const rewritePath = (p) => {
|
|
4331
|
+
if (!p || !p.startsWith('/ns/m/'))
|
|
4332
|
+
return p;
|
|
4333
|
+
if (p.startsWith('/ns/m/__ns_boot__/'))
|
|
4334
|
+
return p;
|
|
4335
|
+
if (p.startsWith('/ns/m/__ns_hmr__/')) {
|
|
4336
|
+
return bootTaggedRequest ? `/ns/m/__ns_boot__/b1${p.slice('/ns/m'.length)}` : p;
|
|
4337
|
+
}
|
|
4338
|
+
return (bootTaggedRequest ? bootHmrPrefix : hmrPrefix) + p.slice('/ns/m'.length);
|
|
4339
|
+
};
|
|
2921
4340
|
// 1) Static imports: import ... from "/ns/m/..."
|
|
2922
|
-
code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$
|
|
4341
|
+
code = code.replace(/(from\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2923
4342
|
// 2) Side-effect imports: import "/ns/m/..."
|
|
2924
|
-
code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, `$
|
|
4343
|
+
code = code.replace(/(import\s*(?!\()\s*["'])(\/ns\/m\/[^"'?]+)(["'])/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2925
4344
|
// 3) Dynamic imports: import("/ns/m/...")
|
|
2926
|
-
code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, `$
|
|
4345
|
+
code = code.replace(/(import\(\s*["'])(\/ns\/m\/[^"'?]+)(["']\s*\))/g, (_m, a, p, b) => `${a}${rewritePath(p)}${b}`);
|
|
2927
4346
|
// 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, `$
|
|
4347
|
+
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
4348
|
// 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 → "${origin}/ns/m/..."
|
|
4349
|
+
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}`);
|
|
4350
|
+
// 6) Force absolute HTTP for new URL('/ns/m/...', import.meta.url).href → "${origin}/ns/m/__ns_hmr__/..."
|
|
2932
4351
|
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}
|
|
4352
|
+
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
4353
|
}
|
|
2935
4354
|
catch { }
|
|
2936
4355
|
// 7) Also fix SFC new URL('/ns/sfc/...', import.meta.url).href → "${origin}/ns/sfc/<ver>/..."
|
|
@@ -2946,13 +4365,25 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
2946
4365
|
code = ensureDestructureCoreImports(code);
|
|
2947
4366
|
}
|
|
2948
4367
|
catch { }
|
|
4368
|
+
// Boot-time module graph progress: while the app is still replacing the
|
|
4369
|
+
// placeholder, emit lightweight progress updates as /ns/m modules begin
|
|
4370
|
+
// evaluating. This keeps the overlay moving during large initial graphs.
|
|
4371
|
+
try {
|
|
4372
|
+
if (bootTaggedRequest) {
|
|
4373
|
+
const bootModuleLabel = String(spec || '').replace(/\\/g, '/');
|
|
4374
|
+
const bootProgressSnippet = buildBootProgressSnippet(bootModuleLabel);
|
|
4375
|
+
code = bootProgressSnippet + code;
|
|
4376
|
+
code = hoistTopLevelStaticImports(code);
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
catch { }
|
|
2949
4380
|
// Dev-only: link-check static imports to surface missing bindings early
|
|
2950
4381
|
try {
|
|
2951
4382
|
const devCheck = process.env.NODE_ENV !== 'production';
|
|
2952
4383
|
if (devCheck) {
|
|
2953
4384
|
const ast = babelParse(code, {
|
|
2954
4385
|
sourceType: 'module',
|
|
2955
|
-
plugins:
|
|
4386
|
+
plugins: MODULE_IMPORT_ANALYSIS_PLUGINS,
|
|
2956
4387
|
});
|
|
2957
4388
|
const imports = [];
|
|
2958
4389
|
babelTraverse(ast, {
|
|
@@ -3036,6 +4467,16 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3036
4467
|
continue;
|
|
3037
4468
|
const hasDefault = /\bexport\s+default\b/.test(targetCode) || /export\s*\{\s*default\s*(?:as\s*default)?\s*\}/.test(targetCode);
|
|
3038
4469
|
if (!hasDefault) {
|
|
4470
|
+
// CJS/UMD modules won't have `export default` — they get CJS-wrapped
|
|
4471
|
+
// by the serving pipeline. Only warn, don't fatally block the importer.
|
|
4472
|
+
const hasCjsPattern = /\bmodule\s*\.\s*exports\b/.test(targetCode) || /\bexports\s*\.\s*\w/.test(targetCode);
|
|
4473
|
+
if (hasCjsPattern) {
|
|
4474
|
+
try {
|
|
4475
|
+
console.warn(`[ns:m][link-check] CJS module without export default: ${u.pathname} (will be CJS-wrapped at serve time)`);
|
|
4476
|
+
}
|
|
4477
|
+
catch { }
|
|
4478
|
+
continue;
|
|
4479
|
+
}
|
|
3039
4480
|
const msg = `[link-check] Missing default export in ${u.pathname}${u.search} (imported by ${resolvedCandidate || spec})`;
|
|
3040
4481
|
// Emit a module that throws to surface the exact offender
|
|
3041
4482
|
res.statusCode = 200;
|
|
@@ -3052,6 +4493,16 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3052
4493
|
}
|
|
3053
4494
|
catch { }
|
|
3054
4495
|
}
|
|
4496
|
+
// Diagnostic: dump served code to terminal when ?__diag=1 is in the original URL
|
|
4497
|
+
try {
|
|
4498
|
+
if (urlObj?.searchParams?.get('__diag') === '1') {
|
|
4499
|
+
const specId = resolvedCandidate || spec;
|
|
4500
|
+
console.log(`\n${'='.repeat(80)}\n[ns:m][DIAG] ${specId}\n${'='.repeat(80)}`);
|
|
4501
|
+
console.log(code);
|
|
4502
|
+
console.log(`${'='.repeat(80)}\n[ns:m][DIAG] END ${specId}\n${'='.repeat(80)}\n`);
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
catch { }
|
|
3055
4506
|
res.statusCode = 200;
|
|
3056
4507
|
res.end(code);
|
|
3057
4508
|
}
|
|
@@ -3251,17 +4702,59 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3251
4702
|
server.middlewares.use(async (req, res, next) => {
|
|
3252
4703
|
try {
|
|
3253
4704
|
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3254
|
-
|
|
4705
|
+
// Match /ns/core, /ns/core/<ver>, and /ns/core/<subpath> (path-based deep imports)
|
|
4706
|
+
if (!urlObj.pathname.startsWith('/ns/core'))
|
|
3255
4707
|
return next();
|
|
3256
4708
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
3257
4709
|
res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
|
|
3258
4710
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
|
|
3259
4711
|
res.setHeader('Pragma', 'no-cache');
|
|
3260
4712
|
res.setHeader('Expires', '0');
|
|
3261
|
-
const
|
|
3262
|
-
const
|
|
3263
|
-
const
|
|
4713
|
+
const afterCore = urlObj.pathname.replace(/^\/ns\/core\/?/, '');
|
|
4714
|
+
const hasExplicitVersion = /^[0-9]+$/.test(afterCore);
|
|
4715
|
+
const ver = /^[0-9]+$/.test(afterCore) ? afterCore : String(graphVersion || 0);
|
|
4716
|
+
// Support both query-based (?p=data/observable/index.js) and
|
|
4717
|
+
// path-based (/ns/core/data/observable/index.js) subpath formats.
|
|
4718
|
+
// The device's HTTP ESM loader may use either depending on the import map.
|
|
4719
|
+
const sub = urlObj.searchParams.get('p') || (afterCore && !/^[0-9]+$/.test(afterCore) ? afterCore : '');
|
|
3264
4720
|
const key = sub ? `@nativescript/core/${sub}` : `@nativescript/core`;
|
|
4721
|
+
// Any @nativescript/core subpath import (including shallow ones like
|
|
4722
|
+
// `utils`) may expose exports that are not available from the root
|
|
4723
|
+
// vendor bundle namespace. Serve the actual transformed module content
|
|
4724
|
+
// instead of the lightweight proxy bridge.
|
|
4725
|
+
if (sub) {
|
|
4726
|
+
try {
|
|
4727
|
+
const normalizedSub = sub.replace(/^\/+/, '');
|
|
4728
|
+
if (!hasExplicitVersion) {
|
|
4729
|
+
res.statusCode = 200;
|
|
4730
|
+
res.end(buildVersionedCoreSubpathAliasModule(normalizedSub, ver));
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
const coreSpecifier = `@nativescript/core/${normalizedSub}`;
|
|
4734
|
+
const resolved = await server.pluginContainer?.resolveId?.(coreSpecifier, undefined);
|
|
4735
|
+
const resolvedId = typeof resolved === 'string' ? resolved : resolved?.id || null;
|
|
4736
|
+
const modulePath = resolvedId || `/node_modules/@nativescript/core/${normalizedSub}`;
|
|
4737
|
+
const transformed = await sharedTransformRequest(modulePath);
|
|
4738
|
+
if (transformed?.code) {
|
|
4739
|
+
// Minimal pipeline: Vite already produces correct ESM.
|
|
4740
|
+
// ONLY rewrite specifier strings to device-fetchable URLs.
|
|
4741
|
+
// Do NOT run processCodeForDevice, rewriteImports, or any
|
|
4742
|
+
// other heavy transform — those mangle newlines, eat exports,
|
|
4743
|
+
// and cause cascading "does not provide an export" failures.
|
|
4744
|
+
const moduleCode = rewriteSpecifiersForDevice(transformed.code, getServerOrigin(server), Number(ver));
|
|
4745
|
+
res.statusCode = 200;
|
|
4746
|
+
res.end(moduleCode);
|
|
4747
|
+
return;
|
|
4748
|
+
}
|
|
4749
|
+
}
|
|
4750
|
+
catch (e) {
|
|
4751
|
+
try {
|
|
4752
|
+
console.warn('[ns-core-bridge] deep subpath serve failed:', sub, e?.message);
|
|
4753
|
+
}
|
|
4754
|
+
catch { }
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
// Main entry or shallow subpath: use proxy bridge
|
|
3265
4758
|
// HTTP-only core bridge: do NOT use require/createRequire. Export a proxy that maps
|
|
3266
4759
|
// property access to globalThis first, then to any available vendor registry module.
|
|
3267
4760
|
let code = REQUIRE_GUARD_SNIPPET +
|
|
@@ -3269,7 +4762,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3269
4762
|
`const g = globalThis;\n` +
|
|
3270
4763
|
`const reg = (g.__nsVendorRegistry ||= new Map());\n` +
|
|
3271
4764
|
`const __getVendorCore = () => { try { const m = reg && reg.get ? (reg.get(${JSON.stringify(key)}) || reg.get('@nativescript/core')) : null; return (m && (m.__esModule && m.default ? m.default : (m.default || m))) || m || null; } catch { return null; } };\n` +
|
|
3272
|
-
`const __core = new Proxy({}, { get(_t, p){ if (p === 'default') return __core; if (p === Symbol.toStringTag) return 'Module'; try { const
|
|
4765
|
+
`const __core = new Proxy({}, { get(_t, p){ if (p === 'default') return __core; if (p === Symbol.toStringTag) return 'Module'; try { const vc = __getVendorCore(); if (vc) { const vv = vc[p]; if (vv !== undefined) return vv; } } catch {} try { const v = g[p]; if (v !== undefined) return v; } catch {} return undefined; } });\n` +
|
|
3273
4766
|
`// Default export: namespace-like proxy\n` +
|
|
3274
4767
|
`export default __core;\n`;
|
|
3275
4768
|
res.statusCode = 200;
|
|
@@ -3300,18 +4793,40 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3300
4793
|
res.setHeader('Expires', '0');
|
|
3301
4794
|
let content = '';
|
|
3302
4795
|
try {
|
|
3303
|
-
const
|
|
3304
|
-
const entryRtPath =
|
|
3305
|
-
|
|
3306
|
-
content = fs.readFileSync(entryRtPath, 'utf-8');
|
|
4796
|
+
const _req = createRequire(import.meta.url);
|
|
4797
|
+
const entryRtPath = _req.resolve('@nativescript/vite/hmr/entry-runtime.js');
|
|
4798
|
+
content = readFileSync(entryRtPath, 'utf-8');
|
|
3307
4799
|
}
|
|
3308
4800
|
catch (e) {
|
|
3309
|
-
|
|
4801
|
+
// .js not found (source tree without build) — transform .ts on the fly
|
|
4802
|
+
try {
|
|
4803
|
+
const tsPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'entry-runtime.ts');
|
|
4804
|
+
if (existsSync(tsPath)) {
|
|
4805
|
+
const tsSource = readFileSync(tsPath, 'utf-8');
|
|
4806
|
+
const result = babelCore.transformSync(tsSource, {
|
|
4807
|
+
filename: tsPath,
|
|
4808
|
+
plugins: [[pluginTransformTypescript, { isTSX: false, allowDeclareFields: true }]],
|
|
4809
|
+
sourceType: 'module',
|
|
4810
|
+
});
|
|
4811
|
+
if (result?.code) {
|
|
4812
|
+
content = result.code;
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
catch (e2) {
|
|
4817
|
+
if (verbose)
|
|
4818
|
+
console.warn('[hmr-http] entry-runtime.ts transform failed', e2);
|
|
4819
|
+
}
|
|
4820
|
+
if (!content) {
|
|
4821
|
+
content = 'export default async function start(){ console.error("[/ns/entry-rt] not found"); }\n';
|
|
4822
|
+
}
|
|
3310
4823
|
}
|
|
4824
|
+
console.log('[hmr-http] /ns/entry-rt serving', content.length, 'bytes');
|
|
3311
4825
|
res.statusCode = 200;
|
|
3312
4826
|
res.end(content);
|
|
3313
4827
|
}
|
|
3314
4828
|
catch (e) {
|
|
4829
|
+
console.warn('[hmr-http] /ns/entry-rt error', e);
|
|
3315
4830
|
next();
|
|
3316
4831
|
}
|
|
3317
4832
|
});
|
|
@@ -3741,7 +5256,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
3741
5256
|
code = outCode;
|
|
3742
5257
|
}
|
|
3743
5258
|
catch { }
|
|
3744
|
-
code = processCodeForDevice(code, false);
|
|
5259
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(fullSpec), fullSpec);
|
|
3745
5260
|
// Transform static .vue imports into static imports from the assembler (no TLA) via AST
|
|
3746
5261
|
try {
|
|
3747
5262
|
const importerPath = fullSpec.replace(/[?#].*$/, '');
|
|
@@ -4297,7 +5812,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4297
5812
|
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
5813
|
parts.push(`export default __ns_sfc__`);
|
|
4299
5814
|
let inlineCode = parts.filter(Boolean).join('\n');
|
|
4300
|
-
inlineCode = processCodeForDevice(inlineCode, false);
|
|
5815
|
+
inlineCode = processCodeForDevice(inlineCode, false, true);
|
|
4301
5816
|
try {
|
|
4302
5817
|
inlineCode = ensureVersionedCoreImports(inlineCode, getServerOrigin(server), Number(ver));
|
|
4303
5818
|
}
|
|
@@ -4377,7 +5892,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4377
5892
|
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
5893
|
outParts.push('export default __ns_sfc__');
|
|
4379
5894
|
let inlineCode2 = outParts.filter(Boolean).join('\n');
|
|
4380
|
-
inlineCode2 = processCodeForDevice(inlineCode2, false);
|
|
5895
|
+
inlineCode2 = processCodeForDevice(inlineCode2, false, true);
|
|
4381
5896
|
try {
|
|
4382
5897
|
inlineCode2 = ensureVersionedCoreImports(inlineCode2, getServerOrigin(server), Number(ver));
|
|
4383
5898
|
}
|
|
@@ -4689,7 +6204,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4689
6204
|
}
|
|
4690
6205
|
// Run full device processing so helper aliasing and globals are consistent in this path too
|
|
4691
6206
|
let code = REQUIRE_GUARD_SNIPPET + asm;
|
|
4692
|
-
code = processCodeForDevice(code, false);
|
|
6207
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(base), base);
|
|
4693
6208
|
try {
|
|
4694
6209
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), Number(ver));
|
|
4695
6210
|
}
|
|
@@ -4862,7 +6377,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4862
6377
|
let code = transformed.code;
|
|
4863
6378
|
// Reuse existing sanitation chain (lightweight)
|
|
4864
6379
|
code = cleanCode(code);
|
|
4865
|
-
code = processCodeForDevice(code, false);
|
|
6380
|
+
code = processCodeForDevice(code, false, true, /(?:^|\/)node_modules\//.test(resolvedCandidate || spec), resolvedCandidate || spec);
|
|
4866
6381
|
try {
|
|
4867
6382
|
code = ensureVersionedCoreImports(code, getServerOrigin(server), graphVersion);
|
|
4868
6383
|
}
|
|
@@ -4917,7 +6432,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4917
6432
|
if (depTrans?.code && depResolved) {
|
|
4918
6433
|
let depCode = depTrans.code;
|
|
4919
6434
|
depCode = cleanCode(depCode);
|
|
4920
|
-
depCode = processCodeForDevice(depCode, false);
|
|
6435
|
+
depCode = processCodeForDevice(depCode, false, true, /(?:^|\/)node_modules\//.test(depResolved), depResolved);
|
|
4921
6436
|
try {
|
|
4922
6437
|
depCode = ensureVersionedCoreImports(depCode, getServerOrigin(server), graphVersion);
|
|
4923
6438
|
}
|
|
@@ -4990,32 +6505,33 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4990
6505
|
if (verbose)
|
|
4991
6506
|
console.warn('[hmr-ws][graph] initial population failed', e);
|
|
4992
6507
|
}
|
|
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
|
-
|
|
6508
|
+
// Send SFC registry on every connection (not just the first).
|
|
6509
|
+
// When the NativeScript app restarts (e.g. CLI auto-reload), the new
|
|
6510
|
+
// JS context has an empty sfcArtifactMap. Without the registry the
|
|
6511
|
+
// rescue-mount cannot find the root .vue component.
|
|
6512
|
+
try {
|
|
6513
|
+
await ACTIVE_STRATEGY.buildRegistry({
|
|
6514
|
+
server,
|
|
6515
|
+
sfcFileMap,
|
|
6516
|
+
depFileMap,
|
|
6517
|
+
wss: wss,
|
|
6518
|
+
verbose,
|
|
6519
|
+
helpers: {
|
|
6520
|
+
cleanCode,
|
|
6521
|
+
collectImportDependencies,
|
|
6522
|
+
isCoreGlobalsReference,
|
|
6523
|
+
isNativeScriptCoreModule,
|
|
6524
|
+
isNativeScriptPluginModule,
|
|
6525
|
+
resolveVendorFromCandidate,
|
|
6526
|
+
createHash: (value) => createHash('md5').update(value).digest('hex'),
|
|
6527
|
+
rewriteImports,
|
|
6528
|
+
processSfcCode,
|
|
6529
|
+
},
|
|
6530
|
+
});
|
|
6531
|
+
registrySent = true;
|
|
6532
|
+
}
|
|
6533
|
+
catch (error) {
|
|
6534
|
+
console.warn('[hmr-ws] Failed to send registry:', error);
|
|
5019
6535
|
}
|
|
5020
6536
|
emitFullGraph(ws);
|
|
5021
6537
|
// After sending registry & graph also send current module manifest if any
|
|
@@ -5040,14 +6556,30 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5040
6556
|
}
|
|
5041
6557
|
// Graph update for this file change (wrapped to avoid aborting rest of handler)
|
|
5042
6558
|
try {
|
|
5043
|
-
const
|
|
5044
|
-
if (
|
|
5045
|
-
const
|
|
5046
|
-
|
|
5047
|
-
.
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
6559
|
+
const skipAngularHtmlGraphUpdate = ACTIVE_STRATEGY.flavor === 'angular' && /\.(html|htm)$/i.test(file);
|
|
6560
|
+
if (!skipAngularHtmlGraphUpdate) {
|
|
6561
|
+
const graphTargets = collectGraphUpdateModulesForHotUpdate({
|
|
6562
|
+
file,
|
|
6563
|
+
flavor: ACTIVE_STRATEGY.flavor,
|
|
6564
|
+
modules: ctx.modules,
|
|
6565
|
+
getModuleById: (id) => server.moduleGraph.getModuleById(id),
|
|
6566
|
+
});
|
|
6567
|
+
for (const mod of graphTargets) {
|
|
6568
|
+
if (!mod?.id)
|
|
6569
|
+
continue;
|
|
6570
|
+
try {
|
|
6571
|
+
const deps = Array.from(mod.importedModules || [])
|
|
6572
|
+
.map((m) => (m.id || '').replace(/\?.*$/, ''))
|
|
6573
|
+
.filter(Boolean);
|
|
6574
|
+
const transformed = await server.transformRequest(mod.id);
|
|
6575
|
+
const code = transformed?.code || '';
|
|
6576
|
+
upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps);
|
|
6577
|
+
}
|
|
6578
|
+
catch (error) {
|
|
6579
|
+
if (verbose)
|
|
6580
|
+
console.warn('[hmr-ws][v2] failed graph update target', mod.id, error);
|
|
6581
|
+
}
|
|
6582
|
+
}
|
|
5051
6583
|
}
|
|
5052
6584
|
}
|
|
5053
6585
|
catch (e) {
|
|
@@ -5099,11 +6631,93 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5099
6631
|
// For Angular, react to component TS or external template HTML changes under /src
|
|
5100
6632
|
const isHtml = file.endsWith('.html');
|
|
5101
6633
|
const isTs = file.endsWith('.ts');
|
|
6634
|
+
const angularHotUpdateRoots = collectAngularHotUpdateRoots({
|
|
6635
|
+
file,
|
|
6636
|
+
modules: ctx.modules,
|
|
6637
|
+
getModuleById: (id) => server.moduleGraph.getModuleById(id),
|
|
6638
|
+
getModulesByFile: (targetFile) => server.moduleGraph.getModulesByFile?.(targetFile),
|
|
6639
|
+
});
|
|
5102
6640
|
if (!(isHtml || isTs))
|
|
5103
6641
|
return;
|
|
6642
|
+
if (angularHotUpdateRoots.length) {
|
|
6643
|
+
for (const mod of angularHotUpdateRoots) {
|
|
6644
|
+
try {
|
|
6645
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6646
|
+
}
|
|
6647
|
+
catch (invalidationError) {
|
|
6648
|
+
if (verbose) {
|
|
6649
|
+
console.warn('[hmr-ws][angular] hot-update root invalidation failed', mod?.id, invalidationError);
|
|
6650
|
+
}
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
if (verbose) {
|
|
6654
|
+
console.log('[hmr-ws][angular] invalidated hot-update root modules:', angularHotUpdateRoots.length);
|
|
6655
|
+
}
|
|
6656
|
+
}
|
|
6657
|
+
const angularTransitiveInvalidationRoots = (angularHotUpdateRoots.length ? angularHotUpdateRoots : ctx.modules);
|
|
6658
|
+
if (shouldInvalidateAngularTransitiveImporters({ flavor: ACTIVE_STRATEGY.flavor, file })) {
|
|
6659
|
+
try {
|
|
6660
|
+
const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
6661
|
+
modules: angularTransitiveInvalidationRoots,
|
|
6662
|
+
isExcluded: (id) => id.includes('/node_modules/'),
|
|
6663
|
+
maxDepth: 16,
|
|
6664
|
+
});
|
|
6665
|
+
for (const mod of transitiveImporters) {
|
|
6666
|
+
try {
|
|
6667
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6668
|
+
}
|
|
6669
|
+
catch (invalidationError) {
|
|
6670
|
+
if (verbose) {
|
|
6671
|
+
console.warn('[hmr-ws][angular] transitive importer invalidation failed', mod?.id, invalidationError);
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
}
|
|
6675
|
+
if (verbose && transitiveImporters.length) {
|
|
6676
|
+
console.log('[hmr-ws][angular] invalidated transitive importers:', transitiveImporters.length);
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6679
|
+
catch (error) {
|
|
6680
|
+
if (verbose)
|
|
6681
|
+
console.warn('[hmr-ws][angular] transitive importer collection failed', error);
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
try {
|
|
6685
|
+
const transformCacheInvalidationUrls = new Set();
|
|
6686
|
+
if (isTs) {
|
|
6687
|
+
transformCacheInvalidationUrls.add(file);
|
|
6688
|
+
}
|
|
6689
|
+
for (const mod of angularHotUpdateRoots) {
|
|
6690
|
+
if (mod?.id) {
|
|
6691
|
+
transformCacheInvalidationUrls.add(mod.id);
|
|
6692
|
+
}
|
|
6693
|
+
}
|
|
6694
|
+
if (shouldInvalidateAngularTransitiveImporters({ flavor: ACTIVE_STRATEGY.flavor, file })) {
|
|
6695
|
+
const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
6696
|
+
modules: angularTransitiveInvalidationRoots,
|
|
6697
|
+
isExcluded: (id) => id.includes('/node_modules/'),
|
|
6698
|
+
maxDepth: 16,
|
|
6699
|
+
});
|
|
6700
|
+
for (const mod of transitiveImporters) {
|
|
6701
|
+
if (mod?.id) {
|
|
6702
|
+
transformCacheInvalidationUrls.add(mod.id);
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
if (transformCacheInvalidationUrls.size) {
|
|
6707
|
+
sharedTransformRequest.invalidateMany(transformCacheInvalidationUrls);
|
|
6708
|
+
if (verbose) {
|
|
6709
|
+
console.log('[hmr-ws][angular] purged shared transform cache entries:', transformCacheInvalidationUrls.size);
|
|
6710
|
+
}
|
|
6711
|
+
}
|
|
6712
|
+
}
|
|
6713
|
+
catch (error) {
|
|
6714
|
+
if (verbose)
|
|
6715
|
+
console.warn('[hmr-ws][angular] shared transform cache purge failed', error);
|
|
6716
|
+
}
|
|
5104
6717
|
try {
|
|
5105
6718
|
const root = server.config.root || process.cwd();
|
|
5106
6719
|
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6720
|
+
rememberAngularReloadSuppression(root, file);
|
|
5107
6721
|
const origin = getServerOrigin(server);
|
|
5108
6722
|
const msg = {
|
|
5109
6723
|
type: 'ns:angular-update',
|
|
@@ -5120,6 +6734,9 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5120
6734
|
catch (error) {
|
|
5121
6735
|
console.warn('[hmr-ws][angular] update failed:', error);
|
|
5122
6736
|
}
|
|
6737
|
+
if (shouldSuppressDefaultViteHotUpdate({ flavor: ACTIVE_STRATEGY.flavor, file })) {
|
|
6738
|
+
return [];
|
|
6739
|
+
}
|
|
5123
6740
|
return;
|
|
5124
6741
|
}
|
|
5125
6742
|
// TypeScript flavor: emit generic graph delta for app XML/TS/style changes
|
|
@@ -5142,6 +6759,43 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5142
6759
|
}
|
|
5143
6760
|
return;
|
|
5144
6761
|
}
|
|
6762
|
+
// Solid flavor: emit graph delta for app TSX/TS/JSX file changes.
|
|
6763
|
+
// The common graph-update block above (moduleGraph lookup) may have
|
|
6764
|
+
// already emitted a delta if the file was in Vite's module graph.
|
|
6765
|
+
// This handler ensures a delta is emitted even if the module wasn't
|
|
6766
|
+
// found (e.g. new file, or moduleGraph mismatch), and provides
|
|
6767
|
+
// Solid-specific logging. The client-side processQueue handles
|
|
6768
|
+
// propagation from non-component .ts files to .tsx component boundaries.
|
|
6769
|
+
if (ACTIVE_STRATEGY.flavor === 'solid') {
|
|
6770
|
+
const isSolidFile = /\.(tsx?|jsx?)$/i.test(file);
|
|
6771
|
+
if (!isSolidFile)
|
|
6772
|
+
return;
|
|
6773
|
+
try {
|
|
6774
|
+
const rel = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6775
|
+
if (verbose)
|
|
6776
|
+
console.log('[hmr-ws][solid] app file hot update', { file, rel });
|
|
6777
|
+
// If the common block already upserted (hash changed), this will
|
|
6778
|
+
// detect unchanged hash and no-op. If the common block missed it
|
|
6779
|
+
// (module not in Vite's graph), this forces the delta emission.
|
|
6780
|
+
const normalizedId = normalizeGraphId(rel);
|
|
6781
|
+
const existing = graph.get(normalizedId);
|
|
6782
|
+
if (!existing) {
|
|
6783
|
+
// Module not in graph yet — force upsert with timestamp-based
|
|
6784
|
+
// hash so the client sees a change.
|
|
6785
|
+
upsertGraphModule(rel, `/* solid-hmr ${Date.now()} */`, []);
|
|
6786
|
+
}
|
|
6787
|
+
// Log what we're sending so devs can trace the flow on the server side.
|
|
6788
|
+
if (verbose) {
|
|
6789
|
+
const gm = graph.get(normalizedId);
|
|
6790
|
+
console.log('[hmr-ws][solid] delta module', { id: gm?.id, hash: gm?.hash });
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
catch (e) {
|
|
6794
|
+
if (verbose)
|
|
6795
|
+
console.warn('[hmr-ws][solid] failed to handle hot update for', file, e);
|
|
6796
|
+
}
|
|
6797
|
+
return;
|
|
6798
|
+
}
|
|
5145
6799
|
// Handle .vue file updates
|
|
5146
6800
|
if (!file.endsWith('.vue')) {
|
|
5147
6801
|
if (verbose)
|
|
@@ -5249,6 +6903,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
5249
6903
|
// Rewrite ONLY .vue imports (everything else is now inlined)
|
|
5250
6904
|
const projectRoot = server.config.root || process.cwd();
|
|
5251
6905
|
code = rewriteImports(code, rel, sfcFileMap, depFileMap, projectRoot, opts.verbose, undefined);
|
|
6906
|
+
upsertGraphModule(rel, code, [...deps, ...vueDeps]);
|
|
5252
6907
|
// Add HMR runtime prelude (CRITICAL for runtime)
|
|
5253
6908
|
const hmrPrelude = `
|
|
5254
6909
|
// Embedded HMR Runtime for NativeScript runtime
|