@nativescript/vite 8.0.0-alpha.10 → 8.0.0-alpha.12
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 +34 -1
- package/configuration/angular.js +165 -2
- package/configuration/angular.js.map +1 -1
- package/configuration/solid.js +20 -0
- package/configuration/solid.js.map +1 -1
- package/helpers/angular/inject-hmr-vite-ignore.d.ts +75 -0
- package/helpers/angular/inject-hmr-vite-ignore.js +288 -0
- package/helpers/angular/inject-hmr-vite-ignore.js.map +1 -0
- package/helpers/main-entry.js +10 -0
- package/helpers/main-entry.js.map +1 -1
- package/helpers/resolver.js +9 -1
- package/helpers/resolver.js.map +1 -1
- package/hmr/client/index.js +275 -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 +553 -13
- package/hmr/server/websocket.js.map +1 -1
- package/package.json +1 -1
package/hmr/server/websocket.js
CHANGED
|
@@ -2701,6 +2701,74 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2701
2701
|
if (!wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__) {
|
|
2702
2702
|
const originalSend = server.ws.send.bind(server.ws);
|
|
2703
2703
|
wsAny.__NS_ANGULAR_FULL_RELOAD_FILTER_INSTALLED__ = true;
|
|
2704
|
+
// Bridge Vite's stock WS broadcasts (`server.ws.send(...)`)
|
|
2705
|
+
// to our `/ns-hmr` WebSocket. Vite v8 keeps two completely
|
|
2706
|
+
// separate `WebSocketServer` instances: its own (default
|
|
2707
|
+
// path `/`, accepting `vite-hmr`/`vite-ping` protocols) and
|
|
2708
|
+
// ours (`/ns-hmr`, where the iOS device actually connects).
|
|
2709
|
+
// Plugin-emitted events like Analog's
|
|
2710
|
+
// `server.ws.send('angular:component-update', { id, ts })`
|
|
2711
|
+
// flow through Vite's `normalizedHotChannel.send` →
|
|
2712
|
+
// `wss.clients.forEach`, but those `wss.clients` are
|
|
2713
|
+
// EMPTY in NativeScript dev — the device never speaks the
|
|
2714
|
+
// `vite-hmr` protocol nor connects to `/`. Without a
|
|
2715
|
+
// bridge, every plugin-emitted custom event is logged on
|
|
2716
|
+
// the server (e.g. `(client) hmr update <html>`) but
|
|
2717
|
+
// silently dropped before reaching the device. Symptom:
|
|
2718
|
+
// the iOS HMR-applying overlay sticks at 5%
|
|
2719
|
+
// ("Preparing update") forever because Angular's compiled
|
|
2720
|
+
// `import.meta.hot.on('angular:component-update', cb)`
|
|
2721
|
+
// listeners never fire. We mirror the payload onto our
|
|
2722
|
+
// `/ns-hmr` clients here so the existing custom-event
|
|
2723
|
+
// dispatcher in `hmr/client/index.ts` (which forwards to
|
|
2724
|
+
// `__NS_DISPATCH_HOT_EVENT__`) actually runs.
|
|
2725
|
+
const bridgeToNsHmrClients = (payload, args) => {
|
|
2726
|
+
try {
|
|
2727
|
+
let normalized;
|
|
2728
|
+
if (typeof args[0] === 'string') {
|
|
2729
|
+
normalized = { type: 'custom', event: args[0], data: args[1] };
|
|
2730
|
+
}
|
|
2731
|
+
else {
|
|
2732
|
+
normalized = payload;
|
|
2733
|
+
}
|
|
2734
|
+
if (!normalized)
|
|
2735
|
+
return;
|
|
2736
|
+
// Vite's stock `update` payload includes per-module
|
|
2737
|
+
// HMR boundary info that our device-side client
|
|
2738
|
+
// has no handler for (we drive HMR via our own
|
|
2739
|
+
// `ns:angular-update`/`ns:hmr-delta`/`ns:css-updates`
|
|
2740
|
+
// messages). Forwarding it would just look like
|
|
2741
|
+
// noise to the client. Custom events
|
|
2742
|
+
// (`type: 'custom'`) — including
|
|
2743
|
+
// `angular:component-update` and Analog's
|
|
2744
|
+
// CSS-direct/inline `update` shorthand — DO need
|
|
2745
|
+
// to reach the device, since they drive the
|
|
2746
|
+
// in-place `ɵɵreplaceMetadata` template-swap path.
|
|
2747
|
+
// Filter the relay to those.
|
|
2748
|
+
if (normalized.type !== 'custom')
|
|
2749
|
+
return;
|
|
2750
|
+
const stringified = JSON.stringify(normalized);
|
|
2751
|
+
let recipients = 0;
|
|
2752
|
+
wss?.clients.forEach((client) => {
|
|
2753
|
+
try {
|
|
2754
|
+
if (client && client.readyState === 1) {
|
|
2755
|
+
client.send(stringified);
|
|
2756
|
+
recipients++;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
catch { }
|
|
2760
|
+
});
|
|
2761
|
+
if (verbose) {
|
|
2762
|
+
const event = normalized?.event;
|
|
2763
|
+
console.log(`[hmr-ws][bridge] forwarded ${normalized.type}${event ? `:${event}` : ''} payload to ${recipients} /ns-hmr client(s)`);
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
catch (err) {
|
|
2767
|
+
if (verbose) {
|
|
2768
|
+
console.warn('[hmr-ws][bridge] failed to forward payload to /ns-hmr clients', err);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2704
2772
|
server.ws.send = ((payload, ...rest) => {
|
|
2705
2773
|
pruneAngularReloadSuppressions();
|
|
2706
2774
|
if (shouldSuppressViteFullReloadPayload({
|
|
@@ -2713,6 +2781,7 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
2713
2781
|
}
|
|
2714
2782
|
return;
|
|
2715
2783
|
}
|
|
2784
|
+
bridgeToNsHmrClients(payload, [payload, ...rest]);
|
|
2716
2785
|
return originalSend(payload, ...rest);
|
|
2717
2786
|
});
|
|
2718
2787
|
}
|
|
@@ -3126,6 +3195,84 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
3126
3195
|
const urlObj = new URL(req.url || '', 'http://localhost');
|
|
3127
3196
|
if (!urlObj.pathname.startsWith('/ns/m'))
|
|
3128
3197
|
return next();
|
|
3198
|
+
// Delegate AnalogJS Angular component live-reload endpoints.
|
|
3199
|
+
//
|
|
3200
|
+
// Angular 21's `ɵɵgetReplaceMetadataURL` (in @angular/core
|
|
3201
|
+
// _debug_node-chunk.mjs) builds the metadata-replacement URL as
|
|
3202
|
+
// `new URL('./@ng/component?c=<id>&t=<ts>', import.meta.url).href`.
|
|
3203
|
+
// Because `import.meta.url` for a NS-served module is
|
|
3204
|
+
// `http://host:port/ns/m/<project-relative>/component.ts`, the
|
|
3205
|
+
// resolved metadata URL ends up *nested* under the component's
|
|
3206
|
+
// directory: `/ns/m/<dir>/@ng/component?c=...&t=...`.
|
|
3207
|
+
//
|
|
3208
|
+
// AnalogJS's `liveReloadPlugin` registers a middleware that matches
|
|
3209
|
+
// `/@ng/component` anywhere in `req.url` and returns either an empty
|
|
3210
|
+
// module body (no HMR update available) or the metadata-replacement
|
|
3211
|
+
// code (after a save invalidates the file). Without this delegation
|
|
3212
|
+
// the NS `/ns/m/` middleware would treat the path as a file lookup,
|
|
3213
|
+
// fail to resolve `@ng/component` against disk, and respond with
|
|
3214
|
+
// 404 — which surfaces as `HTTP fetch/compile failed` at the
|
|
3215
|
+
// component's own `_HmrLoad(Date.now())` call on initial boot and
|
|
3216
|
+
// blocks Angular component bootstrapping.
|
|
3217
|
+
//
|
|
3218
|
+
// Calling `next()` here lets AnalogJS's middleware (or any other
|
|
3219
|
+
// middleware later in the chain) handle the request. Analog's
|
|
3220
|
+
// middleware reads only the `?c=` query string and is pathname-
|
|
3221
|
+
// agnostic, so we don't need to rewrite `req.url` for it to work.
|
|
3222
|
+
//
|
|
3223
|
+
// HOWEVER: AnalogJS responds with an EMPTY body (`res.end('')`)
|
|
3224
|
+
// for non-invalidated component IDs (initial boot, before any
|
|
3225
|
+
// file save). The iOS HTTP ESM loader's
|
|
3226
|
+
// `LoadHttpModuleForUrl` (ModuleInternalCallbacks.mm) treats an
|
|
3227
|
+
// empty body as a fetch failure (`body.empty() → reject`), even
|
|
3228
|
+
// when the HTTP status is 200 OK. That bubbles up as
|
|
3229
|
+
// `HTTP fetch/compile failed` at the device's `__ns_import(...)`
|
|
3230
|
+
// inside each component's `_HmrLoad(Date.now())` and crashes
|
|
3231
|
+
// Angular's component bootstrap. To make Analog's empty
|
|
3232
|
+
// "no-update" response acceptable to the iOS loader, we wrap
|
|
3233
|
+
// `res.write` / `res.end` and substitute a minimal valid ESM
|
|
3234
|
+
// module body (`export {}`) when downstream writes nothing.
|
|
3235
|
+
// Non-empty bodies (real HMR update payloads after a save)
|
|
3236
|
+
// pass through unchanged.
|
|
3237
|
+
if (urlObj.pathname.includes('/@ng/component')) {
|
|
3238
|
+
const chunks = [];
|
|
3239
|
+
const origWrite = res.write.bind(res);
|
|
3240
|
+
const origEnd = res.end.bind(res);
|
|
3241
|
+
let ended = false;
|
|
3242
|
+
const captureChunk = (chunk) => {
|
|
3243
|
+
if (chunk == null)
|
|
3244
|
+
return;
|
|
3245
|
+
if (typeof chunk === 'string') {
|
|
3246
|
+
chunks.push(chunk);
|
|
3247
|
+
}
|
|
3248
|
+
else if (Buffer.isBuffer(chunk)) {
|
|
3249
|
+
chunks.push(chunk.toString('utf8'));
|
|
3250
|
+
}
|
|
3251
|
+
else {
|
|
3252
|
+
chunks.push(String(chunk));
|
|
3253
|
+
}
|
|
3254
|
+
};
|
|
3255
|
+
res.write = function (chunk, ..._args) {
|
|
3256
|
+
captureChunk(chunk);
|
|
3257
|
+
return true;
|
|
3258
|
+
};
|
|
3259
|
+
res.end = function (chunk, ..._args) {
|
|
3260
|
+
if (ended)
|
|
3261
|
+
return true;
|
|
3262
|
+
ended = true;
|
|
3263
|
+
captureChunk(chunk);
|
|
3264
|
+
let body = chunks.join('');
|
|
3265
|
+
if (body.length === 0) {
|
|
3266
|
+
body = '// [ns:m] empty Angular component metadata — substituted with valid empty module to satisfy iOS HTTP loader (rejects empty bodies)\nexport {};\n';
|
|
3267
|
+
}
|
|
3268
|
+
try {
|
|
3269
|
+
res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'));
|
|
3270
|
+
}
|
|
3271
|
+
catch { }
|
|
3272
|
+
return origEnd(body);
|
|
3273
|
+
};
|
|
3274
|
+
return next();
|
|
3275
|
+
}
|
|
3129
3276
|
// Previously we awaited `populateInitialGraph(server)` here so
|
|
3130
3277
|
// graphVersion would be non-zero for the first /ns/m request.
|
|
3131
3278
|
// That gave deterministic URL tags but blocked the cold boot on a
|
|
@@ -3389,28 +3536,87 @@ function createHmrWebSocketPlugin(opts) {
|
|
|
3389
3536
|
console.log('[hmr-ws][solid] forced createMemo path in createProxy for NativeScript HMR');
|
|
3390
3537
|
}
|
|
3391
3538
|
}
|
|
3539
|
+
// Patch 2b (NS HMR escape hatch): expose `source` on the proxy
|
|
3540
|
+
// itself via a non-enumerable, well-known symbol-ish key. This lets
|
|
3541
|
+
// our HMR remount path bypass solid-refresh's proxy chain and call
|
|
3542
|
+
// the freshly-patched underlying Home function directly. solid-refresh's
|
|
3543
|
+
// proxy wraps everything in nested createMemo's, which under
|
|
3544
|
+
// universal-renderer + nested-context (TanStack RouterContextProvider)
|
|
3545
|
+
// causes accumulating zombie memos that all subscribe to M_initial.source
|
|
3546
|
+
// — the visible symptom is "every other save applies".
|
|
3547
|
+
//
|
|
3548
|
+
// With this hatch we can do `proxy.__$$ns_resolve()` to obtain the
|
|
3549
|
+
// current underlying component (e.g. M_initial.proxy after first save,
|
|
3550
|
+
// or the actual Home function on the deepest hop) and then call
|
|
3551
|
+
// untrack(() => extract until we reach the actual function), then mount
|
|
3552
|
+
// against THAT function — no HMRComp memo proxy chain at the page level.
|
|
3553
|
+
// NS HMR escape hatch: stash `source` on the HMRComp function
|
|
3554
|
+
// itself AND short-circuit the Proxy's `get` handler so that
|
|
3555
|
+
// `proxy.__$ns_resolveSource` returns our exposed function.
|
|
3556
|
+
// Without the get-handler short-circuit, accessing the property
|
|
3557
|
+
// on the Proxy goes through `return source()[property]` — which
|
|
3558
|
+
// asks the *current source value* (which may itself be another
|
|
3559
|
+
// solid-refresh proxy or the underlying user function) for the
|
|
3560
|
+
// property. The user function doesn't have `__$ns_resolveSource`,
|
|
3561
|
+
// and a chained proxy would re-enter its own get handler. Either
|
|
3562
|
+
// way we end up with `undefined` at the page-level remount and
|
|
3563
|
+
// can't unwrap.
|
|
3564
|
+
//
|
|
3565
|
+
// NOTE: `$$` in String.prototype.replace replacement is treated
|
|
3566
|
+
// as a literal `$`. We use a single `$` to avoid that footgun.
|
|
3567
|
+
// Match only the unique opening fragment to avoid getting tripped up
|
|
3568
|
+
// by whitespace differences after the AST normalizer ran.
|
|
3569
|
+
const newProxyMarker = `if (property === 'location' || property === 'name') {`;
|
|
3570
|
+
if (patchedCode.includes(newProxyMarker)) {
|
|
3571
|
+
// 1. Inject `__$ns_resolveSource` as a property on the HMRComp
|
|
3572
|
+
// function itself (so its closure captures `source`).
|
|
3573
|
+
// CRITICAL: assign at module-eval time (right after HMRComp
|
|
3574
|
+
// is defined / before `return new Proxy(...)`), NOT inside
|
|
3575
|
+
// the HMRComp body — the body only runs when the proxy is
|
|
3576
|
+
// called, so before first call the property is undefined
|
|
3577
|
+
// and the page-level remount unwrap finds nothing.
|
|
3578
|
+
const setupMarker = `setComponentProperty(HMRComp, 'name', refreshName);`;
|
|
3579
|
+
patchedCode = patchedCode.replace(setupMarker, `HMRComp.__$ns_resolveSource = function() { return source(); }; ${setupMarker}`);
|
|
3580
|
+
// 2. Make the Proxy `get` handler short-circuit our property
|
|
3581
|
+
// so callers can do `proxy.__$ns_resolveSource()` without
|
|
3582
|
+
// going through `source()[property]` (which would unwrap one
|
|
3583
|
+
// hop early or reach the user function which doesn't have it).
|
|
3584
|
+
patchedCode = patchedCode.replace(newProxyMarker, `if (property === '__$ns_resolveSource') { return HMRComp.__$ns_resolveSource; } ${newProxyMarker}`);
|
|
3585
|
+
if (verbose) {
|
|
3586
|
+
console.log('[hmr-ws][solid] exposed __$ns_resolveSource on createProxy for NS HMR escape hatch');
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3392
3589
|
// Patch 3: Inline patchRegistry call so updates apply immediately
|
|
3393
3590
|
// on module re-evaluation (accept callbacks are not invoked by the HMR client).
|
|
3394
|
-
//
|
|
3395
|
-
//
|
|
3396
|
-
//
|
|
3397
|
-
//
|
|
3398
|
-
//
|
|
3591
|
+
//
|
|
3592
|
+
// The injected diagnostic logs are gated on
|
|
3593
|
+
// `globalThis.__NS_ENV_VERBOSE__` so they're silent in
|
|
3594
|
+
// normal use but resurface immediately when the user
|
|
3595
|
+
// re-runs with verbose logging enabled. The flag is
|
|
3596
|
+
// seeded by `mainEntryPlugin` from the same `verbose`
|
|
3597
|
+
// option that drives this server-side log gating.
|
|
3598
|
+
// Without these the visible HMR signal is just "did it
|
|
3599
|
+
// apply" — with them, devs can answer "did `hot.data`
|
|
3600
|
+
// persist", "did `patchRegistry` actually swap the
|
|
3601
|
+
// proxy's signal source", and "did the registry
|
|
3602
|
+
// component count change" without reaching for an
|
|
3603
|
+
// inspector.
|
|
3399
3604
|
const marker = 'hot.data[SOLID_REFRESH] = hot.data[SOLID_REFRESH] || registry;';
|
|
3400
3605
|
if (patchedCode.includes(marker)) {
|
|
3401
3606
|
const patchCode = [
|
|
3402
|
-
`
|
|
3607
|
+
`var __nsRefreshVerbose = (typeof globalThis !== 'undefined') && !!globalThis.__NS_ENV_VERBOSE__;`,
|
|
3608
|
+
`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
3609
|
`if (hot.data[SOLID_REFRESH]) {`,
|
|
3404
|
-
`
|
|
3405
|
-
` var
|
|
3406
|
-
` console.log('[solid-refresh][$$refreshESM]
|
|
3610
|
+
` var __nsOldComponents = hot.data[SOLID_REFRESH].components ? hot.data[SOLID_REFRESH].components.size : 0;`,
|
|
3611
|
+
` var __nsShouldInvalidate = patchRegistry(hot.data[SOLID_REFRESH], registry);`,
|
|
3612
|
+
` if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] patched: oldComponents=', __nsOldComponents, 'newComponents=', registry.components ? registry.components.size : 0, 'shouldInvalidate=', __nsShouldInvalidate);`,
|
|
3407
3613
|
`} else {`,
|
|
3408
|
-
` console.log('[solid-refresh][$$refreshESM] first load —
|
|
3614
|
+
` if (__nsRefreshVerbose) console.log('[ns-hmr][solid-refresh][$$refreshESM] first load — no prior registry to patch');`,
|
|
3409
3615
|
`}`,
|
|
3410
3616
|
].join('\n ');
|
|
3411
3617
|
patchedCode = patchedCode.replace(marker, `${patchCode}\n ${marker}`);
|
|
3412
3618
|
if (verbose) {
|
|
3413
|
-
console.log('[hmr-ws][solid] added inline patchRegistry for NativeScript HMR');
|
|
3619
|
+
console.log('[hmr-ws][solid] added inline patchRegistry (with diagnostics) for NativeScript HMR');
|
|
3414
3620
|
}
|
|
3415
3621
|
}
|
|
3416
3622
|
// Work on a copy to avoid mutating Vite's cached TransformResult
|
|
@@ -4009,6 +4215,40 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4009
4215
|
`export const $navigateTo = (...a) => { const vm = (__cached_vm || (void __ensure(), __cached_vm)); const rt = __ensure(); try { if (!(g && g.Frame)) { const ns = (__ns_core_bridge && (__ns_core_bridge.__esModule && __ns_core_bridge.default ? __ns_core_bridge.default : (__ns_core_bridge.default || __ns_core_bridge))) || __ns_core_bridge || {}; if (ns) { if (!g.Frame && ns.Frame) g.Frame = ns.Frame; if (!g.Page && ns.Page) g.Page = ns.Page; if (!g.Application && (ns.Application||ns.app||ns.application)) g.Application = (ns.Application||ns.app||ns.application); } } } catch {} try { const hmrRealm = (g && g.__NS_HMR_REALM__) || 'unknown'; const hasTop = !!(g && g.Frame && g.Frame.topmost && g.Frame.topmost()); const top = hasTop ? g.Frame.topmost() : null; const ctor = top && top.constructor && top.constructor.name; } catch {} if (g && typeof g.__nsNavigateUsingApp === 'function') { try { return g.__nsNavigateUsingApp(...a); } catch (e) { console.error('[ns-rt] $navigateTo app navigator error', e); throw e; } } console.error('[ns-rt] $navigateTo unavailable: app navigator missing'); throw new Error('$navigateTo unavailable: app navigator missing'); } ;\n` +
|
|
4010
4216
|
`export const $navigateBack = (...a) => { const vm = (__cached_vm || (void __ensure(), __cached_vm)); const rt = __ensure(); const impl = (vm && (vm.$navigateBack || (vm.default && vm.default.$navigateBack))) || (rt && (rt.$navigateBack || (rt.runtimeHelpers && rt.runtimeHelpers.navigateBack))); let res; try { const via = (impl && (impl === (vm && vm.$navigateBack) || impl === (vm && vm.default && vm.default.$navigateBack))) ? 'vm' : (impl ? 'rt' : 'none'); } catch {} try { if (typeof impl === 'function') res = impl(...a); } catch {} try { const top = (g && g.Frame && g.Frame.topmost && g.Frame.topmost()); if (!res && top && top.canGoBack && top.canGoBack()) { res = top.goBack(); } } catch {} try { const hook = g && (g.__NS_HMR_ON_NAVIGATE_BACK || g.__NS_HMR_ON_BACK || g.__nsAttemptBackRemount); if (typeof hook === 'function') hook(); } catch {} return res; }\n` +
|
|
4011
4217
|
`export const $showModal = (...a) => { const vm = (__cached_vm || (void __ensure(), __cached_vm)); const rt = __ensure(); const impl = (vm && (vm.$showModal || (vm.default && vm.default.$showModal))) || (rt && (rt.$showModal || (rt.runtimeHelpers && rt.runtimeHelpers.showModal))); try { if (typeof impl === 'function') return impl(...a); } catch (e) { } return undefined; }\n` +
|
|
4218
|
+
// Vite client helpers re-exported through the runtime bridge.
|
|
4219
|
+
//
|
|
4220
|
+
// Vite's `vite:import-analysis` plugin rewrites unresolvable dynamic
|
|
4221
|
+
// imports (where the URL is not a static string literal) as
|
|
4222
|
+
// `__vite__injectQuery(<expr>, 'import')` and prepends an import
|
|
4223
|
+
// from `/@vite/client`. The /* @vite-ignore */ comment only
|
|
4224
|
+
// suppresses the warning, not the rewrite — Vite gates the rewrite
|
|
4225
|
+
// on `urlIsStringRE`, not `hasViteIgnoreRE`.
|
|
4226
|
+
//
|
|
4227
|
+
// In NativeScript dev, the AST normalizer (packages/vite/hmr/helpers/
|
|
4228
|
+
// ast-normalizer.ts) correctly strips the /@vite/client import (the
|
|
4229
|
+
// browser-only client module is not loadable on-device), then sees
|
|
4230
|
+
// the unbound `__vite__injectQuery` identifier and synthesizes
|
|
4231
|
+
// `const { vite__injectQuery: __vite__injectQuery } = __ns_rt_ns_1`
|
|
4232
|
+
// from this bridge. Without this export the destructure binds to
|
|
4233
|
+
// undefined and Angular 21's component HMR loader (and any other
|
|
4234
|
+
// caller of dynamic-import-with-non-literal-URL) fails with
|
|
4235
|
+
// `__vite__injectQuery is not a function` at module evaluation.
|
|
4236
|
+
//
|
|
4237
|
+
// This polyfill mirrors Vite 8's `__vite__injectQuery` in
|
|
4238
|
+
// node_modules/vite/dist/node/chunks/node.js — for relative or
|
|
4239
|
+
// absolute-path URLs it appends `?<queryToInject>` (preserving
|
|
4240
|
+
// existing search/hash); for already-absolute URLs (http(s):, etc.)
|
|
4241
|
+
// it returns the URL unchanged. Angular's `ɵɵgetReplaceMetadataURL`
|
|
4242
|
+
// returns absolute HTTP URLs, so this acts as a passthrough at
|
|
4243
|
+
// runtime, matching Vite's web behavior.
|
|
4244
|
+
`export const vite__injectQuery = (url, queryToInject) => {\n` +
|
|
4245
|
+
` if (typeof url !== 'string') return url;\n` +
|
|
4246
|
+
` if (url[0] !== '.' && url[0] !== '/') return url;\n` +
|
|
4247
|
+
` const pathname = url.replace(/[?#].*$/, '');\n` +
|
|
4248
|
+
` let search = '', hash = '';\n` +
|
|
4249
|
+
` try { const u = new URL(url, 'http://vite.dev'); search = u.search || ''; hash = u.hash || ''; } catch {}\n` +
|
|
4250
|
+
` return pathname + '?' + queryToInject + (search ? '&' + search.slice(1) : '') + (hash || '');\n` +
|
|
4251
|
+
`};\n` +
|
|
4012
4252
|
`export default {\n` +
|
|
4013
4253
|
` defineComponent, resolveComponent, createVNode, createTextVNode, createCommentVNode,\n` +
|
|
4014
4254
|
` Fragment, Teleport, Transition, TransitionGroup, KeepAlive, Suspense, withCtx, openBlock,\n` +
|
|
@@ -4018,7 +4258,8 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4018
4258
|
` isVNode, cloneVNode, isRef, ref, shallowRef, unref, computed, reactive, readonly, isReactive, isReadonly, toRaw, markRaw, shallowReactive, shallowReadonly,\n` +
|
|
4019
4259
|
` watch, watchEffect, watchPostEffect, watchSyncEffect, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,\n` +
|
|
4020
4260
|
` onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured, onRenderTracked, onRenderTriggered, nextTick, h, provide, inject, vShow, createApp, registerElement,\n` +
|
|
4021
|
-
` $navigateTo, $navigateBack, $showModal
|
|
4261
|
+
` $navigateTo, $navigateBack, $showModal,\n` +
|
|
4262
|
+
` vite__injectQuery\n` +
|
|
4022
4263
|
`};\n`;
|
|
4023
4264
|
// Prepend guard and ship (harmless, keeps diagnostics consistent)
|
|
4024
4265
|
code = REQUIRE_GUARD_SNIPPET + code;
|
|
@@ -6179,7 +6420,21 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
6179
6420
|
const code = transformed?.code || '';
|
|
6180
6421
|
upsertGraphModule((mod.id || '').replace(/\?.*$/, ''), code, deps, {
|
|
6181
6422
|
emitDeltaOnInsert: true,
|
|
6182
|
-
|
|
6423
|
+
// Defer the delta broadcast until AFTER the framework
|
|
6424
|
+
// hot-update handler has had a chance to invalidate the
|
|
6425
|
+
// shared transform-request cache + Vite's moduleGraph
|
|
6426
|
+
// for the changed file and its transitive importers.
|
|
6427
|
+
// Otherwise the client races: it receives the delta
|
|
6428
|
+
// (eviction + re-import via tagged URL) before the
|
|
6429
|
+
// server has purged its caches, and the re-import is
|
|
6430
|
+
// served from cache → V8 evaluates the previous save's
|
|
6431
|
+
// transformed code → patchRegistry runs against an
|
|
6432
|
+
// unchanged source → the visible page is "one save
|
|
6433
|
+
// behind". Angular has always taken this path; Solid
|
|
6434
|
+
// needs the same contract because Solid HMR depends
|
|
6435
|
+
// on the client re-fetching the just-changed module
|
|
6436
|
+
// to drive `solid-refresh.patchRegistry`.
|
|
6437
|
+
broadcastDelta: ACTIVE_STRATEGY.flavor !== 'angular' && ACTIVE_STRATEGY.flavor !== 'solid',
|
|
6183
6438
|
});
|
|
6184
6439
|
}
|
|
6185
6440
|
catch (error) {
|
|
@@ -6241,6 +6496,118 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
6241
6496
|
// For Angular, react to component TS or external template HTML changes under /src
|
|
6242
6497
|
const isHtml = file.endsWith('.html');
|
|
6243
6498
|
const isTs = file.endsWith('.ts');
|
|
6499
|
+
// Web-style template HMR opt-in: when the user enables Angular's
|
|
6500
|
+
// `liveReload` (Analog's flag, mirrored from `--hmr` in
|
|
6501
|
+
// `configuration/angular.ts`), `.html` edits are owned by
|
|
6502
|
+
// Analog's `handleHotUpdate` which sends
|
|
6503
|
+
// `server.ws.send('angular:component-update', { id, timestamp })`.
|
|
6504
|
+
// The runtime listener registered in each compiled component
|
|
6505
|
+
// `.mjs` then dynamic-imports `/@ng/component?c=<id>&t=<ts>` and
|
|
6506
|
+
// calls `ɵɵreplaceMetadata` on the live class — swapping the
|
|
6507
|
+
// template definition AND walking live `LView`s to recreate
|
|
6508
|
+
// matching views in-place. NO Angular reboot, NO route navigation.
|
|
6509
|
+
//
|
|
6510
|
+
// The NS reboot path (`ns:angular-update` → `__reboot_ng_modules__`)
|
|
6511
|
+
// must be SKIPPED for HTML edits when this is on; otherwise both
|
|
6512
|
+
// fire, the reboot wins, and we lose the in-place swap. The
|
|
6513
|
+
// reboot path stays intact for `.ts` edits — those genuinely
|
|
6514
|
+
// change module-level code (services, route configs, NgModule
|
|
6515
|
+
// providers) that Angular's `ɵɵreplaceMetadata` can't reach.
|
|
6516
|
+
//
|
|
6517
|
+
// We detect "live reload mode is on" by checking that the
|
|
6518
|
+
// `analogjs-live-reload-plugin` registered itself with the
|
|
6519
|
+
// dev server. That plugin only exists when `liveReload: true`
|
|
6520
|
+
// was passed to `angular()` in `configuration/angular.ts`,
|
|
6521
|
+
// which gates on `hmrActive`. So this check is a clean
|
|
6522
|
+
// boolean: true iff the in-place pipeline is wired up.
|
|
6523
|
+
const angularLiveReloadActive = (server.config?.plugins ?? []).some((plugin) => plugin?.name === 'analogjs-live-reload-plugin');
|
|
6524
|
+
if (isHtml && angularLiveReloadActive) {
|
|
6525
|
+
updateMetrics.tAfterFramework = Date.now();
|
|
6526
|
+
if (verbose) {
|
|
6527
|
+
const rel = '/' +
|
|
6528
|
+
path.posix
|
|
6529
|
+
.normalize(path.relative(server.config.root || process.cwd(), file))
|
|
6530
|
+
.split(path.sep)
|
|
6531
|
+
.join('/');
|
|
6532
|
+
console.info(`[ns-hmr-diag][server] HTML edit handed off to Analog component-update path; skipping ns:angular-update broadcast (file=${rel})`);
|
|
6533
|
+
}
|
|
6534
|
+
// Re-query the moduleGraph for this file AFTER awaiting
|
|
6535
|
+
// `graphInitialPopulationPromise` (done at the top of
|
|
6536
|
+
// `handleHotUpdate`) and return the freshly-discovered
|
|
6537
|
+
// modules so they propagate to Analog's `handleHotUpdate`
|
|
6538
|
+
// in the same chain.
|
|
6539
|
+
//
|
|
6540
|
+
// Vite v8 builds the initial `mixedHmrContext.modules`
|
|
6541
|
+
// from `mixedModuleGraph.getModulesByFile(file)` BEFORE
|
|
6542
|
+
// any plugin runs. On the very first save after a cold
|
|
6543
|
+
// dev-server start, the moduleGraph for the changed
|
|
6544
|
+
// `.html` template has not yet been populated — that
|
|
6545
|
+
// population happens lazily via `populateInitialGraph`
|
|
6546
|
+
// → `transformRequest` → Analog's `transform` hook →
|
|
6547
|
+
// `addWatchFile(htmlFile)` → `vite:import-analysis`
|
|
6548
|
+
// consumes `_addedImports` and finally calls
|
|
6549
|
+
// `moduleGraph.updateModuleInfo` which registers the
|
|
6550
|
+
// `html → component.ts` importer relationship in
|
|
6551
|
+
// `fileToModulesMap`. All of that work races against the
|
|
6552
|
+
// file-watcher event for the `.html` edit, and the
|
|
6553
|
+
// watcher event almost always wins — so `ctx.modules`
|
|
6554
|
+
// arrives as `[]` even though the component is fully
|
|
6555
|
+
// compiled and ready to receive an in-place template
|
|
6556
|
+
// swap.
|
|
6557
|
+
//
|
|
6558
|
+
// Returning `undefined` here would propagate that empty
|
|
6559
|
+
// `ctx.modules` to the next plugin (Analog's handler),
|
|
6560
|
+
// which iterates with `ctx.modules.forEach(mod => mod
|
|
6561
|
+
// .importers.forEach(imp => …))` — a no-op when
|
|
6562
|
+
// `ctx.modules` is empty. Analog never broadcasts
|
|
6563
|
+
// `angular:component-update`, never marks anything
|
|
6564
|
+
// self-accepting, and Vite falls back to a `full-reload`
|
|
6565
|
+
// payload that the device runtime cannot honor (NS apps
|
|
6566
|
+
// don't have a browser-style page reload). The
|
|
6567
|
+
// user-visible symptom is exactly the "first save logs
|
|
6568
|
+
// `(client) page reload` and the simulator gets stuck
|
|
6569
|
+
// on the HMR-applying overlay forever" failure we hit
|
|
6570
|
+
// before this re-query was added.
|
|
6571
|
+
//
|
|
6572
|
+
// Since we already `await graphInitialPopulationPromise`
|
|
6573
|
+
// at the top of this function, by this point the
|
|
6574
|
+
// moduleGraph IS populated (every component file in
|
|
6575
|
+
// `src/` has been transformed and `addWatchFile` has
|
|
6576
|
+
// been consumed by `import-analysis`). A fresh
|
|
6577
|
+
// `getModulesByFile(file)` call now returns the template
|
|
6578
|
+
// module with the importing component's module in
|
|
6579
|
+
// `.importers`. Returning that array overwrites
|
|
6580
|
+
// `mixedHmrContext.modules` so Analog's handler — which
|
|
6581
|
+
// runs RIGHT AFTER us in the same chain — sees the
|
|
6582
|
+
// populated importer graph, identifies the component
|
|
6583
|
+
// class via `classNames.get(imp.id)`, and broadcasts
|
|
6584
|
+
// `angular:component-update` for `ɵɵreplaceMetadata`.
|
|
6585
|
+
//
|
|
6586
|
+
// We still skip the reboot path (`ns:angular-update`)
|
|
6587
|
+
// for HTML edits — control never reaches the
|
|
6588
|
+
// reboot-broadcast block below because of the `return`
|
|
6589
|
+
// here. The default-Vite-full-reload suppression is now
|
|
6590
|
+
// Analog's responsibility: it marks the changed module
|
|
6591
|
+
// self-accepting, which tells Vite the update is
|
|
6592
|
+
// handled and prevents the fallback.
|
|
6593
|
+
let resolvedModules = ctx.modules;
|
|
6594
|
+
try {
|
|
6595
|
+
const fresh = server.moduleGraph?.getModulesByFile?.(file);
|
|
6596
|
+
if (fresh && fresh.size > 0) {
|
|
6597
|
+
resolvedModules = [...fresh];
|
|
6598
|
+
if (verbose) {
|
|
6599
|
+
console.info(`[ns-hmr-diag][server] re-queried modules after graph population: count=${resolvedModules.length} (was ${ctx.modules?.length ?? 0})`);
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
catch (refetchErr) {
|
|
6604
|
+
if (verbose) {
|
|
6605
|
+
console.warn('[ns-hmr-diag][server] failed to re-query moduleGraph for html update', refetchErr);
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
emitHmrUpdateSummary();
|
|
6609
|
+
return resolvedModules;
|
|
6610
|
+
}
|
|
6244
6611
|
const angularHotUpdateRoots = collectAngularHotUpdateRoots({
|
|
6245
6612
|
file,
|
|
6246
6613
|
modules: ctx.modules,
|
|
@@ -6563,6 +6930,179 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
6563
6930
|
const gm = graph.get(normalizedId);
|
|
6564
6931
|
console.log('[hmr-ws][solid] delta module', { id: gm?.id, hash: gm?.hash });
|
|
6565
6932
|
}
|
|
6933
|
+
// Purge the shared transform-request cache AND Vite's own
|
|
6934
|
+
// moduleGraph transformResult cache for the changed file
|
|
6935
|
+
// AND every transitive importer.
|
|
6936
|
+
//
|
|
6937
|
+
// Why this matters for Solid HMR specifically:
|
|
6938
|
+
// - The HMR client evicts V8's module cache for the
|
|
6939
|
+
// canonical /ns/m/<path> URL and re-imports the module.
|
|
6940
|
+
// - The dev server resolves /ns/m/* by calling
|
|
6941
|
+
// `sharedTransformRequest(...)`, which has a 60s TTL on
|
|
6942
|
+
// transform results to amortize cost across HMR
|
|
6943
|
+
// cycles. The shared cache wraps `server.transformRequest`,
|
|
6944
|
+
// which itself caches the compiled output on each
|
|
6945
|
+
// `ModuleNode.transformResult`. Both layers must be
|
|
6946
|
+
// invalidated, or the re-import resolves to whatever
|
|
6947
|
+
// the previous save populated.
|
|
6948
|
+
// - Without invalidation at *both* layers, the second
|
|
6949
|
+
// save of a file within the cache window returns the
|
|
6950
|
+
// FIRST save's transform — V8 evaluates stale code,
|
|
6951
|
+
// `solid-refresh.patchRegistry` runs against an
|
|
6952
|
+
// unchanged source body, and the visible page picks
|
|
6953
|
+
// up the previous save's edit instead of the current
|
|
6954
|
+
// one (the "one-save-behind" symptom users reported).
|
|
6955
|
+
//
|
|
6956
|
+
// Critically, transitive importers must also be invalidated
|
|
6957
|
+
// because TanStack file-based routing (and similar frameworks)
|
|
6958
|
+
// use route files that statically import their components.
|
|
6959
|
+
// When `home.tsx` changes, `routes/index.tsx`'s transform
|
|
6960
|
+
// output references the imported home module identity. Even
|
|
6961
|
+
// though the route file's source bytes did not change, its
|
|
6962
|
+
// *resolved* import target has — and its cached transform
|
|
6963
|
+
// might still encode the previous resolution. Forcing a
|
|
6964
|
+
// fresh transform of the importer guarantees the route
|
|
6965
|
+
// file's `import Home from ...` re-resolves against the
|
|
6966
|
+
// freshly evaluated home module on V8 side.
|
|
6967
|
+
//
|
|
6968
|
+
// The Angular path performs the equivalent purge via
|
|
6969
|
+
// `collectAngularTransformCacheInvalidationUrls` /
|
|
6970
|
+
// `sharedTransformRequest.invalidateMany`. We replicate
|
|
6971
|
+
// that contract for Solid here. The transitive walk is
|
|
6972
|
+
// bounded the same way (max depth 16, node_modules /
|
|
6973
|
+
// virtual ids excluded) so vendor packages stay hot.
|
|
6974
|
+
try {
|
|
6975
|
+
const projectRoot = server.config.root || process.cwd();
|
|
6976
|
+
const cacheInvalidationUrls = new Set();
|
|
6977
|
+
const addCacheKey = (rawId) => {
|
|
6978
|
+
const id = String(rawId || '');
|
|
6979
|
+
if (!id)
|
|
6980
|
+
return;
|
|
6981
|
+
const cacheKey = canonicalizeTransformRequestCacheKey(id, projectRoot);
|
|
6982
|
+
cacheInvalidationUrls.add(cacheKey);
|
|
6983
|
+
const noQuery = cacheKey.replace(/\?.*$/, '');
|
|
6984
|
+
const stripped = noQuery.replace(/\.(?:[mc]?[jt]sx?)$/i, '');
|
|
6985
|
+
if (stripped !== noQuery) {
|
|
6986
|
+
cacheInvalidationUrls.add(stripped);
|
|
6987
|
+
}
|
|
6988
|
+
};
|
|
6989
|
+
addCacheKey(file);
|
|
6990
|
+
const rootModules = server.moduleGraph.getModulesByFile?.(file);
|
|
6991
|
+
const transitiveImporters = collectAngularTransitiveImportersForInvalidation({
|
|
6992
|
+
modules: rootModules ? Array.from(rootModules) : [],
|
|
6993
|
+
isExcluded: (id) => id.includes('/node_modules/') || isRuntimeGraphExcludedPath(id),
|
|
6994
|
+
maxDepth: 16,
|
|
6995
|
+
});
|
|
6996
|
+
// Invalidate Vite's moduleGraph for the changed file +
|
|
6997
|
+
// every transitive importer so `server.transformRequest`
|
|
6998
|
+
// re-runs the transform pipeline instead of returning
|
|
6999
|
+
// the cached `ModuleNode.transformResult`. We call
|
|
7000
|
+
// `onFileChange` (Vite's authoritative file-changed
|
|
7001
|
+
// signal — walks all module variants including `?v=`,
|
|
7002
|
+
// `?import`, `?t=`) AND per-module `invalidateModule`
|
|
7003
|
+
// for transitive importers (which onFileChange
|
|
7004
|
+
// doesn't reach).
|
|
7005
|
+
try {
|
|
7006
|
+
server.moduleGraph.onFileChange(file);
|
|
7007
|
+
}
|
|
7008
|
+
catch { }
|
|
7009
|
+
if (rootModules) {
|
|
7010
|
+
for (const mod of rootModules) {
|
|
7011
|
+
try {
|
|
7012
|
+
server.moduleGraph.invalidateModule(mod);
|
|
7013
|
+
}
|
|
7014
|
+
catch { }
|
|
7015
|
+
}
|
|
7016
|
+
}
|
|
7017
|
+
for (const mod of transitiveImporters) {
|
|
7018
|
+
addCacheKey(mod?.id);
|
|
7019
|
+
try {
|
|
7020
|
+
server.moduleGraph.invalidateModule(mod);
|
|
7021
|
+
}
|
|
7022
|
+
catch { }
|
|
7023
|
+
}
|
|
7024
|
+
if (cacheInvalidationUrls.size && sharedTransformRequest) {
|
|
7025
|
+
sharedTransformRequest.invalidateMany(cacheInvalidationUrls);
|
|
7026
|
+
if (verbose) {
|
|
7027
|
+
console.log('[hmr-ws][solid] purged shared transform cache entries:', cacheInvalidationUrls.size, 'transitiveImporters=', transitiveImporters.length);
|
|
7028
|
+
}
|
|
7029
|
+
}
|
|
7030
|
+
// Sledgehammer: nuke EVERY entry in sharedTransformRequest's
|
|
7031
|
+
// result cache. The targeted `invalidateMany` above only
|
|
7032
|
+
// clears keys we know about. The `/ns/m/` handler iterates
|
|
7033
|
+
// a long list of candidate extensions (`.ts`, `.js`, `.tsx`,
|
|
7034
|
+
// `.jsx`, `.mjs`, `.mts`, `.cts`, `.vue`, `index.*`) and
|
|
7035
|
+
// EACH candidate is a separate cache key. If a previous
|
|
7036
|
+
// serve populated cache for `/src/components/home.js` (via
|
|
7037
|
+
// extension fallback that resolves to `home.tsx`), our
|
|
7038
|
+
// targeted invalidate misses it and iOS HITs the stale
|
|
7039
|
+
// entry — serving the previous save's transformed code.
|
|
7040
|
+
try {
|
|
7041
|
+
sharedTransformRequest.clear();
|
|
7042
|
+
}
|
|
7043
|
+
catch { }
|
|
7044
|
+
}
|
|
7045
|
+
catch (e) {
|
|
7046
|
+
if (verbose)
|
|
7047
|
+
console.warn('[hmr-ws][solid] transform cache invalidation failed', e);
|
|
7048
|
+
}
|
|
7049
|
+
// Re-run the transform AFTER all caches are invalidated, then
|
|
7050
|
+
// re-upsert the graph so the broadcast hash matches the freshly-
|
|
7051
|
+
// transformed content. The common upsert block above ran
|
|
7052
|
+
// `server.transformRequest` BEFORE invalidation — at that
|
|
7053
|
+
// moment Vite's auto-invalidate hadn't fired yet (it runs after
|
|
7054
|
+
// `plugin.handleHotUpdate`), so the result it cached was the
|
|
7055
|
+
// previous save's. Without this re-transform, the broadcast
|
|
7056
|
+
// carries a stale hash and iOS evaluates the previous save's
|
|
7057
|
+
// bytes ("one save behind").
|
|
7058
|
+
//
|
|
7059
|
+
// We pre-populate the cache for every extension variant Vite's
|
|
7060
|
+
// /ns/m/ handler might try, so the first request from iOS hits
|
|
7061
|
+
// fresh data regardless of which candidate it resolves first.
|
|
7062
|
+
try {
|
|
7063
|
+
const ext = file.match(/\.(?:[mc]?[jt]sx?)$/i)?.[0] || '';
|
|
7064
|
+
const baseSpec = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
7065
|
+
const baseNoExt = ext ? baseSpec.replace(/\.(?:[mc]?[jt]sx?)$/i, '') : baseSpec;
|
|
7066
|
+
const candidates = Array.from(new Set([baseSpec, baseNoExt, baseNoExt + '.ts', baseNoExt + '.tsx', baseNoExt + '.js', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', file]));
|
|
7067
|
+
let freshCode = '';
|
|
7068
|
+
for (const cand of candidates) {
|
|
7069
|
+
try {
|
|
7070
|
+
const fresh = await sharedTransformRequest(cand, 30000);
|
|
7071
|
+
if (fresh?.code && !freshCode)
|
|
7072
|
+
freshCode = fresh.code;
|
|
7073
|
+
}
|
|
7074
|
+
catch { }
|
|
7075
|
+
}
|
|
7076
|
+
if (freshCode) {
|
|
7077
|
+
const existingGm = graph.get(normalizedId);
|
|
7078
|
+
const existingDeps = existingGm?.deps || [];
|
|
7079
|
+
upsertGraphModule(normalizedId, freshCode, existingDeps, {
|
|
7080
|
+
broadcastDelta: false,
|
|
7081
|
+
});
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
catch (e) {
|
|
7085
|
+
if (verbose)
|
|
7086
|
+
console.warn('[hmr-ws][solid] post-invalidation re-transform failed', e);
|
|
7087
|
+
}
|
|
7088
|
+
// Broadcast the (now-fresh) delta. Suppressing this in the
|
|
7089
|
+
// common upsert block (`broadcastDelta: ACTIVE_STRATEGY.flavor
|
|
7090
|
+
// !== 'solid'`) and emitting it here ensures the client's
|
|
7091
|
+
// eviction + re-import doesn't race the server's cache
|
|
7092
|
+
// invalidation.
|
|
7093
|
+
try {
|
|
7094
|
+
const gm = graph.get(normalizedId);
|
|
7095
|
+
if (gm) {
|
|
7096
|
+
emitDelta([gm], []);
|
|
7097
|
+
if (verbose) {
|
|
7098
|
+
console.log('[hmr-ws][solid] broadcast delta after cache invalidation', { id: gm.id, hash: gm.hash });
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
catch (e) {
|
|
7103
|
+
if (verbose)
|
|
7104
|
+
console.warn('[hmr-ws][solid] post-invalidation broadcast failed', e);
|
|
7105
|
+
}
|
|
6566
7106
|
}
|
|
6567
7107
|
catch (e) {
|
|
6568
7108
|
if (verbose)
|