@nativescript/vite 8.0.0-alpha.10 → 8.0.0-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/configuration/solid.js +20 -0
- package/configuration/solid.js.map +1 -1
- package/helpers/main-entry.js +10 -0
- package/helpers/main-entry.js.map +1 -1
- package/hmr/client/index.js +177 -7
- package/hmr/client/index.js.map +1 -1
- package/hmr/client/utils.js +113 -33
- package/hmr/client/utils.js.map +1 -1
- package/hmr/server/websocket.js +269 -12
- package/hmr/server/websocket.js.map +1 -1
- package/package.json +1 -1
package/hmr/server/websocket.js
CHANGED
|
@@ -3389,28 +3389,87 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
3389
3389
|
console.log('[hmr-ws][solid] forced createMemo path in createProxy for NativeScript HMR');
|
|
3390
3390
|
}
|
|
3391
3391
|
}
|
|
3392
|
+
// Patch 2b (NS HMR escape hatch): expose `source` on the proxy
|
|
3393
|
+
// itself via a non-enumerable, well-known symbol-ish key. This lets
|
|
3394
|
+
// our HMR remount path bypass solid-refresh's proxy chain and call
|
|
3395
|
+
// the freshly-patched underlying Home function directly. solid-refresh's
|
|
3396
|
+
// proxy wraps everything in nested createMemo's, which under
|
|
3397
|
+
// universal-renderer + nested-context (TanStack RouterContextProvider)
|
|
3398
|
+
// causes accumulating zombie memos that all subscribe to M_initial.source
|
|
3399
|
+
// — the visible symptom is "every other save applies".
|
|
3400
|
+
//
|
|
3401
|
+
// With this hatch we can do `proxy.__$$ns_resolve()` to obtain the
|
|
3402
|
+
// current underlying component (e.g. M_initial.proxy after first save,
|
|
3403
|
+
// or the actual Home function on the deepest hop) and then call
|
|
3404
|
+
// untrack(() => extract until we reach the actual function), then mount
|
|
3405
|
+
// against THAT function — no HMRComp memo proxy chain at the page level.
|
|
3406
|
+
// NS HMR escape hatch: stash `source` on the HMRComp function
|
|
3407
|
+
// itself AND short-circuit the Proxy's `get` handler so that
|
|
3408
|
+
// `proxy.__$ns_resolveSource` returns our exposed function.
|
|
3409
|
+
// Without the get-handler short-circuit, accessing the property
|
|
3410
|
+
// on the Proxy goes through `return source()[property]` — which
|
|
3411
|
+
// asks the *current source value* (which may itself be another
|
|
3412
|
+
// solid-refresh proxy or the underlying user function) for the
|
|
3413
|
+
// property. The user function doesn't have `__$ns_resolveSource`,
|
|
3414
|
+
// and a chained proxy would re-enter its own get handler. Either
|
|
3415
|
+
// way we end up with `undefined` at the page-level remount and
|
|
3416
|
+
// can't unwrap.
|
|
3417
|
+
//
|
|
3418
|
+
// NOTE: `$$` in String.prototype.replace replacement is treated
|
|
3419
|
+
// as a literal `$`. We use a single `$` to avoid that footgun.
|
|
3420
|
+
// Match only the unique opening fragment to avoid getting tripped up
|
|
3421
|
+
// by whitespace differences after the AST normalizer ran.
|
|
3422
|
+
const newProxyMarker = `if (property === 'location' || property === 'name') {`;
|
|
3423
|
+
if (patchedCode.includes(newProxyMarker)) {
|
|
3424
|
+
// 1. Inject `__$ns_resolveSource` as a property on the HMRComp
|
|
3425
|
+
// function itself (so its closure captures `source`).
|
|
3426
|
+
// CRITICAL: assign at module-eval time (right after HMRComp
|
|
3427
|
+
// is defined / before `return new Proxy(...)`), NOT inside
|
|
3428
|
+
// the HMRComp body — the body only runs when the proxy is
|
|
3429
|
+
// called, so before first call the property is undefined
|
|
3430
|
+
// and the page-level remount unwrap finds nothing.
|
|
3431
|
+
const setupMarker = `setComponentProperty(HMRComp, 'name', refreshName);`;
|
|
3432
|
+
patchedCode = patchedCode.replace(setupMarker, `HMRComp.__$ns_resolveSource = function() { return source(); }; ${setupMarker}`);
|
|
3433
|
+
// 2. Make the Proxy `get` handler short-circuit our property
|
|
3434
|
+
// so callers can do `proxy.__$ns_resolveSource()` without
|
|
3435
|
+
// going through `source()[property]` (which would unwrap one
|
|
3436
|
+
// hop early or reach the user function which doesn't have it).
|
|
3437
|
+
patchedCode = patchedCode.replace(newProxyMarker, `if (property === '__$ns_resolveSource') { return HMRComp.__$ns_resolveSource; } ${newProxyMarker}`);
|
|
3438
|
+
if (verbose) {
|
|
3439
|
+
console.log('[hmr-ws][solid] exposed __$ns_resolveSource on createProxy for NS HMR escape hatch');
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3392
3442
|
// Patch 3: Inline patchRegistry call so updates apply immediately
|
|
3393
3443
|
// on module re-evaluation (accept callbacks are not invoked by the HMR client).
|
|
3394
|
-
//
|
|
3395
|
-
//
|
|
3396
|
-
//
|
|
3397
|
-
//
|
|
3398
|
-
//
|
|
3444
|
+
//
|
|
3445
|
+
// The injected diagnostic logs are gated on
|
|
3446
|
+
// `globalThis.__NS_ENV_VERBOSE__` so they're silent in
|
|
3447
|
+
// normal use but resurface immediately when the user
|
|
3448
|
+
// re-runs with verbose logging enabled. The flag is
|
|
3449
|
+
// seeded by `mainEntryPlugin` from the same `verbose`
|
|
3450
|
+
// option that drives this server-side log gating.
|
|
3451
|
+
// Without these the visible HMR signal is just "did it
|
|
3452
|
+
// apply" — with them, devs can answer "did `hot.data`
|
|
3453
|
+
// persist", "did `patchRegistry` actually swap the
|
|
3454
|
+
// proxy's signal source", and "did the registry
|
|
3455
|
+
// component count change" without reaching for an
|
|
3456
|
+
// inspector.
|
|
3399
3457
|
const marker = 'hot.data[SOLID_REFRESH] = hot.data[SOLID_REFRESH] || registry;';
|
|
3400
3458
|
if (patchedCode.includes(marker)) {
|
|
3401
3459
|
const patchCode = [
|
|
3402
|
-
`
|
|
3460
|
+
`var __nsRefreshVerbose = (typeof globalThis !== 'undefined') && !!globalThis.__NS_ENV_VERBOSE__;`,
|
|
3461
|
+
`if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] hot.data has SOLID_REFRESH=', !!(hot.data && hot.data[SOLID_REFRESH]), 'registry components=', registry.components ? registry.components.size : 0);`,
|
|
3403
3462
|
`if (hot.data[SOLID_REFRESH]) {`,
|
|
3404
|
-
`
|
|
3405
|
-
` var
|
|
3406
|
-
` console.log('[solid-refresh][$$refreshESM]
|
|
3463
|
+
` var __nsOldComponents = hot.data[SOLID_REFRESH].components ? hot.data[SOLID_REFRESH].components.size : 0;`,
|
|
3464
|
+
` var __nsShouldInvalidate = patchRegistry(hot.data[SOLID_REFRESH], registry);`,
|
|
3465
|
+
` if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] patched: oldComponents=', __nsOldComponents, 'newComponents=', registry.components ? registry.components.size : 0, 'shouldInvalidate=', __nsShouldInvalidate);`,
|
|
3407
3466
|
`} else {`,
|
|
3408
|
-
` console.log('[solid-refresh][$$refreshESM] first load —
|
|
3467
|
+
` if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] first load — no prior registry to patch');`,
|
|
3409
3468
|
`}`,
|
|
3410
3469
|
].join('\n ');
|
|
3411
3470
|
patchedCode = patchedCode.replace(marker, `${patchCode}\n ${marker}`);
|
|
3412
3471
|
if (verbose) {
|
|
3413
|
-
console.log('[hmr-ws][solid] added inline patchRegistry for NativeScript HMR');
|
|
3472
|
+
console.log('[hmr-ws][solid] added inline patchRegistry (with diagnostics) for NativeScript HMR');
|
|
3414
3473
|
}
|
|
3415
3474
|
}
|
|
3416
3475
|
// Work on a copy to avoid mutating Vite's cached TransformResult
|
|
@@ -6179,7 +6238,21 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
6179
6238
|
const code = transformed?.code || '';
|
|
6180
6239
|
upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps, {
|
|
6181
6240
|
emitDeltaOnInsert: true,
|
|
6182
|
-
|
|
6241
|
+
// Defer the delta broadcast until AFTER the framework
|
|
6242
|
+
// hot-update handler has had a chance to invalidate the
|
|
6243
|
+
// shared transform-request cache + Vite's moduleGraph
|
|
6244
|
+
// for the changed file and its transitive importers.
|
|
6245
|
+
// Otherwise the client races: it receives the delta
|
|
6246
|
+
// (eviction + re-import via tagged URL) before the
|
|
6247
|
+
// server has purged its caches, and the re-import is
|
|
6248
|
+
// served from cache → V8 evaluates the previous save's
|
|
6249
|
+
// transformed code → patchRegistry runs against an
|
|
6250
|
+
// unchanged source → the visible page is "one save
|
|
6251
|
+
// behind". Angular has always taken this path; Solid
|
|
6252
|
+
// needs the same contract because Solid HMR depends
|
|
6253
|
+
// on the client re-fetching the just-changed module
|
|
6254
|
+
// to drive `solid-refresh.patchRegistry`.
|
|
6255
|
+
broadcastDelta: ACTIVE_STRATEGY.flavor !== 'angular' && ACTIVE_STRATEGY.flavor !== 'solid',
|
|
6183
6256
|
});
|
|
6184
6257
|
}
|
|
6185
6258
|
catch (error) {
|
|
@@ -6563,6 +6636,190 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
6563
6636
|
const gm = graph.get(normalizedId);
|
|
6564
6637
|
console.log('[hmr-ws][solid] delta module', { id: gm?.id, hash: gm?.hash });
|
|
6565
6638
|
}
|
|
6639
|
+
// Purge the shared transform-request cache AND Vite's own
|
|
6640
|
+
// moduleGraph transformResult cache for the changed file
|
|
6641
|
+
// AND every transitive importer.
|
|
6642
|
+
//
|
|
6643
|
+
// Why this matters for Solid HMR specifically:
|
|
6644
|
+
// - The HMR client evicts V8's module cache for the
|
|
6645
|
+
// canonical /ns/m/<path> URL and re-imports the module.
|
|
6646
|
+
// - The dev server resolves /ns/m/* by calling
|
|
6647
|
+
// `sharedTransformRequest(...)`, which has a 60s TTL on
|
|
6648
|
+
// transform results to amortize cost across HMR
|
|
6649
|
+
// cycles. The shared cache wraps `server.transformRequest`,
|
|
6650
|
+
// which itself caches the compiled output on each
|
|
6651
|
+
// `ModuleNode.transformResult`. Both layers must be
|
|
6652
|
+
// invalidated, or the re-import resolves to whatever
|
|
6653
|
+
// the previous save populated.
|
|
6654
|
+
// - Without invalidation at *both* layers, the second
|
|
6655
|
+
// save of a file within the cache window returns the
|
|
6656
|
+
// FIRST save's transform — V8 evaluates stale code,
|
|
6657
|
+
// `solid-refresh.patchRegistry` runs against an
|
|
6658
|
+
// unchanged source body, and the visible page picks
|
|
6659
|
+
// up the previous save's edit instead of the current
|
|
6660
|
+
// one (the "one-save-behind" symptom users reported).
|
|
6661
|
+
//
|
|
6662
|
+
// Critically, transitive importers must also be invalidated
|
|
6663
|
+
// because TanStack file-based routing (and similar frameworks)
|
|
6664
|
+
// use route files that statically import their components.
|
|
6665
|
+
// When `home.tsx` changes, `routes/index.tsx`'s transform
|
|
6666
|
+
// output references the imported home module identity. Even
|
|
6667
|
+
// though the route file's source bytes did not change, its
|
|
6668
|
+
// *resolved* import target has — and its cached transform
|
|
6669
|
+
// might still encode the previous resolution. Forcing a
|
|
6670
|
+
// fresh transform of the importer guarantees the route
|
|
6671
|
+
// file's `import Home from ...` re-resolves against the
|
|
6672
|
+
// freshly evaluated home module on V8 side.
|
|
6673
|
+
//
|
|
6674
|
+
// The Angular path performs the equivalent purge via
|
|
6675
|
+
// `collectAngularTransformCacheInvalidationUrls` /
|
|
6676
|
+
// `sharedTransformRequest.invalidateMany`. We replicate
|
|
6677
|
+
// that contract for Solid here. The transitive walk is
|
|
6678
|
+
// bounded the same way (max depth 16, node_modules /
|
|
6679
|
+
// virtual ids excluded) so vendor packages stay hot.
|
|
6680
|
+
try {
|
|
6681
|
+
const projectRoot = server.config.root || process.cwd();
|
|
6682
|
+
const cacheInvalidationUrls = new Set();
|
|
6683
|
+
const addCacheKey = (rawId) => {
|
|
6684
|
+
const id = String(rawId || '');
|
|
6685
|
+
if (!id)
|
|
6686
|
+
return;
|
|
6687
|
+
const cacheKey = canonicalizeTransformRequestCacheKey(id, projectRoot);
|
|
6688
|
+
cacheInvalidationUrls.add(cacheKey);
|
|
6689
|
+
const noQuery = cacheKey.replace(/\?.*$/, '');
|
|
6690
|
+
const stripped = noQuery.replace(/\.(?:[mc]?[jt]sx?)$/i, '');
|
|
6691
|
+
if (stripped !== noQuery) {
|
|
6692
|
+
cacheInvalidationUrls.add(stripped);
|
|
6693
|
+
}
|
|
6694
|
+
};
|
|
6695
|
+
addCacheKey(file);
|
|
6696
|
+
const rootModules = server.moduleGraph.getModulesByFile?.(file);
|
|
6697
|
+
const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
6698
|
+
modules: rootModules ? Array.from(rootModules) : [],
|
|
6699
|
+
isExcluded: (id) => id.includes('/node_modules/') || isRuntimeGraphExcludedPath(id),
|
|
6700
|
+
maxDepth: 16,
|
|
6701
|
+
});
|
|
6702
|
+
// Invalidate Vite's moduleGraph for the changed file +
|
|
6703
|
+
// every transitive importer so `server.transformRequest`
|
|
6704
|
+
// re-runs the transform pipeline instead of returning
|
|
6705
|
+
// the cached `ModuleNode.transformResult`. We call
|
|
6706
|
+
// `onFileChange` (Vite's authoritative file-changed
|
|
6707
|
+
// signal — walks all module variants including `?v=`,
|
|
6708
|
+
// `?import`, `?t=`) AND per-module `invalidateModule`
|
|
6709
|
+
// for transitive importers (which onFileChange
|
|
6710
|
+
// doesn't reach).
|
|
6711
|
+
try {
|
|
6712
|
+
server.moduleGraph.onFileChange(file);
|
|
6713
|
+
}
|
|
6714
|
+
catch { }
|
|
6715
|
+
if (rootModules) {
|
|
6716
|
+
for (const mod of rootModules) {
|
|
6717
|
+
try {
|
|
6718
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6719
|
+
}
|
|
6720
|
+
catch { }
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
for (const mod of transitiveImporters) {
|
|
6724
|
+
addCacheKey(mod?.id);
|
|
6725
|
+
try {
|
|
6726
|
+
server.moduleGraph.invalidateModule(mod);
|
|
6727
|
+
}
|
|
6728
|
+
catch { }
|
|
6729
|
+
}
|
|
6730
|
+
if (cacheInvalidationUrls.size && sharedTransformRequest) {
|
|
6731
|
+
sharedTransformRequest.invalidateMany(cacheInvalidationUrls);
|
|
6732
|
+
if (verbose) {
|
|
6733
|
+
console.log('[hmr-ws][solid] purged shared transform cache entries:', cacheInvalidationUrls.size, 'transitiveImporters=', transitiveImporters.length);
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
// Sledgehammer: nuke EVERY entry in sharedTransformRequest's
|
|
6737
|
+
// result cache. The targeted `invalidateMany` above only
|
|
6738
|
+
// clears keys we know about. The `/ns/m/` handler iterates
|
|
6739
|
+
// a long list of candidate extensions (`.ts`, `.js`, `.tsx`,
|
|
6740
|
+
// `.jsx`, `.mjs`, `.mts`, `.cts`, `.vue`, `index.*`) and
|
|
6741
|
+
// EACH candidate is a separate cache key. If a previous
|
|
6742
|
+
// serve populated cache for `/src/components/home.js` (via
|
|
6743
|
+
// extension fallback that resolves to `home.tsx`), our
|
|
6744
|
+
// targeted invalidate misses it and iOS HITs the stale
|
|
6745
|
+
// entry — serving the previous save's transformed code.
|
|
6746
|
+
try {
|
|
6747
|
+
sharedTransformRequest.clear();
|
|
6748
|
+
}
|
|
6749
|
+
catch { }
|
|
6750
|
+
}
|
|
6751
|
+
catch (e) {
|
|
6752
|
+
if (verbose)
|
|
6753
|
+
console.warn('[hmr-ws][solid] transform cache invalidation failed', e);
|
|
6754
|
+
}
|
|
6755
|
+
// Re-run the transform AFTER all caches are invalidated, then
|
|
6756
|
+
// re-upsert the graph so the broadcast hash matches the freshly-
|
|
6757
|
+
// transformed content. The common upsert block above ran
|
|
6758
|
+
// `server.transformRequest` BEFORE invalidation — at that
|
|
6759
|
+
// moment Vite's auto-invalidate hadn't fired yet (it runs after
|
|
6760
|
+
// `plugin.handleHotUpdate`), so the result it cached was the
|
|
6761
|
+
// previous save's. Without this re-transform, the broadcast
|
|
6762
|
+
// carries a stale hash and iOS evaluates the previous save's
|
|
6763
|
+
// bytes ("one save behind").
|
|
6764
|
+
//
|
|
6765
|
+
// We pre-populate the cache for every extension variant Vite's
|
|
6766
|
+
// /ns/m/ handler might try, so the first request from iOS hits
|
|
6767
|
+
// fresh data regardless of which candidate it resolves first.
|
|
6768
|
+
try {
|
|
6769
|
+
const ext = file.match(/\.(?:[mc]?[jt]sx?)$/i)?.[0] || '';
|
|
6770
|
+
const baseSpec = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6771
|
+
const baseNoExt = ext ? baseSpec.replace(/\.(?:[mc]?[jt]sx?)$/i, '') : baseSpec;
|
|
6772
|
+
const candidates = Array.from(new Set([
|
|
6773
|
+
baseSpec,
|
|
6774
|
+
baseNoExt,
|
|
6775
|
+
baseNoExt + '.ts',
|
|
6776
|
+
baseNoExt + '.tsx',
|
|
6777
|
+
baseNoExt + '.js',
|
|
6778
|
+
baseNoExt + '.jsx',
|
|
6779
|
+
baseNoExt + '.mjs',
|
|
6780
|
+
baseNoExt + '.mts',
|
|
6781
|
+
baseNoExt + '.cts',
|
|
6782
|
+
file,
|
|
6783
|
+
]));
|
|
6784
|
+
let freshCode = '';
|
|
6785
|
+
for (const cand of candidates) {
|
|
6786
|
+
try {
|
|
6787
|
+
const fresh = await sharedTransformRequest(cand, 30000);
|
|
6788
|
+
if (fresh?.code && !freshCode)
|
|
6789
|
+
freshCode = fresh.code;
|
|
6790
|
+
}
|
|
6791
|
+
catch { }
|
|
6792
|
+
}
|
|
6793
|
+
if (freshCode) {
|
|
6794
|
+
const existingGm = graph.get(normalizedId);
|
|
6795
|
+
const existingDeps = existingGm?.deps || [];
|
|
6796
|
+
upsertGraphModule(normalizedId, freshCode, existingDeps, {
|
|
6797
|
+
broadcastDelta: false,
|
|
6798
|
+
});
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
catch (e) {
|
|
6802
|
+
if (verbose)
|
|
6803
|
+
console.warn('[hmr-ws][solid] post-invalidation re-transform failed', e);
|
|
6804
|
+
}
|
|
6805
|
+
// Broadcast the (now-fresh) delta. Suppressing this in the
|
|
6806
|
+
// common upsert block (`broadcastDelta: ACTIVE_STRATEGY.flavor
|
|
6807
|
+
// !== 'solid'`) and emitting it here ensures the client's
|
|
6808
|
+
// eviction + re-import doesn't race the server's cache
|
|
6809
|
+
// invalidation.
|
|
6810
|
+
try {
|
|
6811
|
+
const gm = graph.get(normalizedId);
|
|
6812
|
+
if (gm) {
|
|
6813
|
+
emitDelta([gm], []);
|
|
6814
|
+
if (verbose) {
|
|
6815
|
+
console.log('[hmr-ws][solid] broadcast delta after cache invalidation', { id: gm.id, hash: gm.hash });
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
catch (e) {
|
|
6820
|
+
if (verbose)
|
|
6821
|
+
console.warn('[hmr-ws][solid] post-invalidation broadcast failed', e);
|
|
6822
|
+
}
|
|
6566
6823
|
}
|
|
6567
6824
|
catch (e) {
|
|
6568
6825
|
if (verbose)
|