@nativescript/vite 8.0.0-alpha.11 → 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/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/resolver.js +9 -1
- package/helpers/resolver.js.map +1 -1
- package/hmr/client/index.js +98 -0
- package/hmr/client/index.js.map +1 -1
- package/hmr/server/websocket.js +296 -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
|
|
@@ -4068,6 +4215,40 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4068
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` +
|
|
4069
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` +
|
|
4070
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` +
|
|
4071
4252
|
`export default {\n` +
|
|
4072
4253
|
` defineComponent, resolveComponent, createVNode, createTextVNode, createCommentVNode,\n` +
|
|
4073
4254
|
` Fragment, Teleport, Transition, TransitionGroup, KeepAlive, Suspense, withCtx, openBlock,\n` +
|
|
@@ -4077,7 +4258,8 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
4077
4258
|
` isVNode, cloneVNode, isRef, ref, shallowRef, unref, computed, reactive, readonly, isReactive, isReadonly, toRaw, markRaw, shallowReactive, shallowReadonly,\n` +
|
|
4078
4259
|
` watch, watchEffect, watchPostEffect, watchSyncEffect, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,\n` +
|
|
4079
4260
|
` onBeforeUnmount, onUnmounted, onActivated, onDeactivated, onErrorCaptured, onRenderTracked, onRenderTriggered, nextTick, h, provide, inject, vShow, createApp, registerElement,\n` +
|
|
4080
|
-
` $navigateTo, $navigateBack, $showModal
|
|
4261
|
+
` $navigateTo, $navigateBack, $showModal,\n` +
|
|
4262
|
+
` vite__injectQuery\n` +
|
|
4081
4263
|
`};\n`;
|
|
4082
4264
|
// Prepend guard and ship (harmless, keeps diagnostics consistent)
|
|
4083
4265
|
code = REQUIRE_GUARD_SNIPPET + code;
|
|
@@ -6314,6 +6496,118 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
6314
6496
|
// For Angular, react to component TS or external template HTML changes under /src
|
|
6315
6497
|
const isHtml = file.endsWith('.html');
|
|
6316
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
|
+
}
|
|
6317
6611
|
const angularHotUpdateRoots = collectAngularHotUpdateRoots({
|
|
6318
6612
|
file,
|
|
6319
6613
|
modules: ctx.modules,
|
|
@@ -6769,18 +7063,7 @@ export const piniaSymbol = p.piniaSymbol;
|
|
|
6769
7063
|
const ext = file.match(/\.(?:[mc]?[jt]sx?)$/i)?.[0] || '';
|
|
6770
7064
|
const baseSpec = '/' + path.posix.normalize(path.relative(root, file)).split(path.sep).join('/');
|
|
6771
7065
|
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
|
-
]));
|
|
7066
|
+
const candidates = Array.from(new Set([baseSpec, baseNoExt, baseNoExt + '.ts', baseNoExt + '.tsx', baseNoExt + '.js', baseNoExt + '.jsx', baseNoExt + '.mjs', baseNoExt + '.mts', baseNoExt + '.cts', file]));
|
|
6784
7067
|
let freshCode = '';
|
|
6785
7068
|
for (const cand of candidates) {
|
|
6786
7069
|
try {
|