|
8 | 8 | * - Hot Module Replacement (HMR) |
9 | 9 | */ |
10 | 10 |
|
11 | | -import { watch } from 'node:fs' |
| 11 | +import { watch, readFileSync } from 'node:fs' |
12 | 12 | import { readFile } from 'node:fs/promises' |
13 | 13 | import { ServerResponse } from 'node:http' |
14 | 14 | import { dirname, resolve } from 'node:path' |
@@ -194,6 +194,9 @@ export function angular(options: PluginOptions = {}): Plugin[] { |
194 | 194 | // Track component files with pending HMR updates (set by fs.watch, checked by HMR endpoint) |
195 | 195 | const pendingHmrUpdates = new Set<string>() |
196 | 196 |
|
| 197 | + // Cache inline template content per .ts file for detecting template-only changes |
| 198 | + const inlineTemplateCache = new Map<string, string>() |
| 199 | + |
197 | 200 | function getMinifyComponentStyles(context?: { |
198 | 201 | environment?: { config?: { build?: ResolvedConfig['build'] } } |
199 | 202 | }): boolean { |
@@ -636,6 +639,12 @@ export function angular(options: PluginOptions = {}): Plugin[] { |
636 | 639 | componentIds.set(actualId, className) |
637 | 640 | debugHmr('registered: %s -> %s', actualId, className) |
638 | 641 | } |
| 642 | + |
| 643 | + // Cache inline template content for detecting template-only changes in handleHotUpdate |
| 644 | + const inlineTemplate = extractInlineTemplate(code) |
| 645 | + if (inlineTemplate !== null) { |
| 646 | + inlineTemplateCache.set(actualId, inlineTemplate) |
| 647 | + } |
639 | 648 | } |
640 | 649 |
|
641 | 650 | return { |
@@ -694,6 +703,57 @@ export function angular(options: PluginOptions = {}): Plugin[] { |
694 | 703 | return [] |
695 | 704 | } |
696 | 705 |
|
| 706 | + // Check if only the inline template changed — if so, use HMR instead of full reload. |
| 707 | + // For external templates this is handled by fs.watch, but inline templates are part |
| 708 | + // of the .ts file and need explicit diffing. |
| 709 | + const cachedTemplate = inlineTemplateCache.get(ctx.file) |
| 710 | + if (cachedTemplate !== undefined) { |
| 711 | + let newContent: string |
| 712 | + try { |
| 713 | + newContent = readFileSync(ctx.file, 'utf-8') |
| 714 | + } catch { |
| 715 | + newContent = '' |
| 716 | + } |
| 717 | + const newTemplate = extractInlineTemplate(newContent) |
| 718 | + |
| 719 | + if (newTemplate !== null && newTemplate !== cachedTemplate) { |
| 720 | + // Template changed — check if ONLY the template changed |
| 721 | + const TMPL_RE = /template\s*:\s*`([\s\S]*?)`/ |
| 722 | + const newWithout = newContent.replace(TMPL_RE, 'template: ``') |
| 723 | + const oldReconstructed = newContent |
| 724 | + .replace(newTemplate, cachedTemplate) |
| 725 | + .replace(TMPL_RE, 'template: ``') |
| 726 | + |
| 727 | + if (newWithout === oldReconstructed) { |
| 728 | + debugHmr('inline template-only change detected, using HMR for %s', ctx.file) |
| 729 | + |
| 730 | + // Update cache |
| 731 | + inlineTemplateCache.set(ctx.file, newTemplate) |
| 732 | + |
| 733 | + // Mark as pending so the HMR endpoint serves the update module |
| 734 | + pendingHmrUpdates.add(ctx.file) |
| 735 | + |
| 736 | + // Invalidate Vite's module graph |
| 737 | + const componentModule = ctx.server.moduleGraph.getModuleById(ctx.file) |
| 738 | + if (componentModule) { |
| 739 | + ctx.server.moduleGraph.invalidateModule(componentModule) |
| 740 | + } |
| 741 | + |
| 742 | + // Send HMR event (same as external template changes) |
| 743 | + const className = componentIds.get(ctx.file) |
| 744 | + const componentId = `${ctx.file}@${className}` |
| 745 | + const encodedId = encodeURIComponent(componentId) |
| 746 | + ctx.server.ws.send({ |
| 747 | + type: 'custom', |
| 748 | + event: 'angular:component-update', |
| 749 | + data: { id: encodedId, timestamp: Date.now() }, |
| 750 | + }) |
| 751 | + |
| 752 | + return [] |
| 753 | + } |
| 754 | + } |
| 755 | + } |
| 756 | + |
697 | 757 | debugHmr('triggering full reload for component file change') |
698 | 758 | // Component FILE changes require a full reload because: |
699 | 759 | // - Class definition changes can't be hot-swapped safely |
|
0 commit comments