(payload: any)
| 573 | } |
| 574 | |
| 575 | function applyDelta(payload: any) { |
| 576 | // If versions are out of sync, request a full graph resync, but still |
| 577 | // opportunistically queue the changed ids so the client can react once |
| 578 | // the resync arrives. This is especially important for simple TS flavors |
| 579 | // where we only need a signal that "something changed" to trigger a |
| 580 | // resetRootView-driven hot update. |
| 581 | const currentVersion = getGraphVersion(); |
| 582 | const versionMatches = payload.baseVersion === currentVersion; |
| 583 | if (!versionMatches) { |
| 584 | if (VERBOSE) console.warn('[hmr][graph] version mismatch requesting resync', payload.baseVersion, currentVersion); |
| 585 | try { |
| 586 | hmrSocket?.send(JSON.stringify({ type: 'ns:hmr-resync-request' })); |
| 587 | } catch {} |
| 588 | } |
| 589 | if (versionMatches) { |
| 590 | setGraphVersion(payload.newVersion); |
| 591 | } |
| 592 | |
| 593 | const changed = payload.changed || []; |
| 594 | switch (__NS_TARGET_FLAVOR__) { |
| 595 | case 'vue': |
| 596 | recordVuePayloadChanges(changed, getGraphVersion()); |
| 597 | break; |
| 598 | } |
| 599 | |
| 600 | (payload.changed || []).forEach((m: any) => { |
| 601 | if (!m || !m.id) return; |
| 602 | // Always update the local graph view with the announced module metadata |
| 603 | graph.set(m.id, { id: m.id, deps: m.deps || [], hash: m.hash || '' }); |
| 604 | }); |
| 605 | (payload.removed || []).forEach((r: string) => { |
| 606 | graph.delete(r); |
| 607 | }); |
| 608 | if (VERBOSE) console.log('[hmr][graph] delta applied newVersion', getGraphVersion(), 'changed=', (payload.changed || []).length, 'removed=', (payload.removed || []).length, 'baseVersion=', payload.baseVersion); |
| 609 | // Queue evaluation of changed modules (placeholder pipeline) |
| 610 | if (payload.changed?.length) { |
| 611 | // HARD SUPPRESS: the very first delta (baseVersion 0) commonly includes the app main entry which is already evaluated during bootstrap. |
| 612 | // Importing it again with a cache-bust often produces a spurious module-not-found (timestamp param treated as distinct file). |
| 613 | const isInitial = payload.baseVersion === 0; |
| 614 | // Filter out virtual helper ids (e.g. '\0plugin-vue:export-helper') which we do not evaluate directly |
| 615 | const realIds = payload.changed.map((c: any) => c.id).filter((id: string) => !/^\0|^\/\0|plugin-vue:export-helper/.test(id)); |
| 616 | // Proactively prefetch core/src deps for any changed .vue SFCs (before registry update triggers reset) |
| 617 | try { |
| 618 | for (const entry of payload.changed || []) { |
| 619 | if (!entry || !/\.vue$/i.test(entry.id)) continue; |
| 620 | const deps: string[] = Array.isArray(entry.deps) ? entry.deps : []; |
| 621 | const prefetchTargets = deps.filter((d) => /^\//.test(d) && !/node_modules/.test(d) && !/\.vue$/i.test(d)); |
| 622 | if (prefetchTargets.length && VERBOSE) console.log('[hmr][prefetch] SFC deps', entry.id, '->', prefetchTargets); |
| 623 | for (const dep of prefetchTargets) { |
| 624 | const norm = normalizeSpec(dep); |
| 625 | // Skip if we already wrote it (basic heuristic: moduleFetchCache has it or path exists in Documents/_ns_hmr) |
| 626 | if (moduleFetchCache.has(norm)) continue; |
| 627 | // Fire and forget; ensure rejection does not break HMR pipeline |
| 628 | requestModuleFromServer(norm).catch(() => {}); |
| 629 | } |
| 630 | } |
| 631 | } catch (e) { |
| 632 | if (VERBOSE) console.warn('[hmr][prefetch] failed', e); |
no test coverage detected