Skip to content

Commit e803857

Browse files
feat: HMR support for inline template changes
When a component .ts file changes and only the inline `template: `...`` portion differs, route it through the existing component HMR mechanism instead of triggering a full page reload. This gives inline template components the same fast HMR experience as external .html templates. Implementation: - Cache inline template content during transform - In handleHotUpdate, compare old vs new template content - If only the template changed, add to pendingHmrUpdates and send angular:component-update event (same path as external templates) - The existing HMR middleware endpoint already handles inline templates via extractInlineTemplate(), so no middleware changes needed - Falls back to full reload if non-template code also changed
1 parent 5cc1a58 commit e803857

File tree

1 file changed

+59
-1
lines changed
  • napi/angular-compiler/vite-plugin

1 file changed

+59
-1
lines changed

napi/angular-compiler/vite-plugin/index.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* - Hot Module Replacement (HMR)
99
*/
1010

11-
import { watch } from 'node:fs'
11+
import { watch, readFileSync } from 'node:fs'
1212
import { readFile } from 'node:fs/promises'
1313
import { ServerResponse } from 'node:http'
1414
import { dirname, resolve } from 'node:path'
@@ -194,6 +194,9 @@ export function angular(options: PluginOptions = {}): Plugin[] {
194194
// Track component files with pending HMR updates (set by fs.watch, checked by HMR endpoint)
195195
const pendingHmrUpdates = new Set<string>()
196196

197+
// Cache inline template content per .ts file for detecting template-only changes
198+
const inlineTemplateCache = new Map<string, string>()
199+
197200
function getMinifyComponentStyles(context?: {
198201
environment?: { config?: { build?: ResolvedConfig['build'] } }
199202
}): boolean {
@@ -622,6 +625,12 @@ export function angular(options: PluginOptions = {}): Plugin[] {
622625
componentIds.set(actualId, className)
623626
debugHmr('registered: %s -> %s', actualId, className)
624627
}
628+
629+
// Cache inline template content for detecting template-only changes in handleHotUpdate
630+
const inlineTemplate = extractInlineTemplate(code)
631+
if (inlineTemplate !== null) {
632+
inlineTemplateCache.set(actualId, inlineTemplate)
633+
}
625634
}
626635

627636
return {
@@ -669,6 +678,55 @@ export function angular(options: PluginOptions = {}): Plugin[] {
669678
return []
670679
}
671680

681+
// Check if only the inline template changed — if so, use HMR instead of full reload.
682+
// For external templates this is handled by fs.watch, but inline templates are part
683+
// of the .ts file and need explicit diffing.
684+
const cachedTemplate = inlineTemplateCache.get(ctx.file)
685+
if (cachedTemplate !== undefined) {
686+
let newContent: string
687+
try {
688+
newContent = readFileSync(ctx.file, 'utf-8')
689+
} catch {
690+
newContent = ''
691+
}
692+
const newTemplate = extractInlineTemplate(newContent)
693+
694+
if (newTemplate !== null && newTemplate !== cachedTemplate) {
695+
// Template changed — check if ONLY the template changed
696+
const TMPL_RE = /template\s*:\s*`([\s\S]*?)`/
697+
const newWithout = newContent.replace(TMPL_RE, 'template: ``')
698+
const oldReconstructed = newContent.replace(newTemplate, cachedTemplate).replace(TMPL_RE, 'template: ``')
699+
700+
if (newWithout === oldReconstructed) {
701+
debugHmr('inline template-only change detected, using HMR for %s', ctx.file)
702+
703+
// Update cache
704+
inlineTemplateCache.set(ctx.file, newTemplate)
705+
706+
// Mark as pending so the HMR endpoint serves the update module
707+
pendingHmrUpdates.add(ctx.file)
708+
709+
// Invalidate Vite's module graph
710+
const componentModule = ctx.server.moduleGraph.getModuleById(ctx.file)
711+
if (componentModule) {
712+
ctx.server.moduleGraph.invalidateModule(componentModule)
713+
}
714+
715+
// Send HMR event (same as external template changes)
716+
const className = componentIds.get(ctx.file)
717+
const componentId = `${ctx.file}@${className}`
718+
const encodedId = encodeURIComponent(componentId)
719+
ctx.server.ws.send({
720+
type: 'custom',
721+
event: 'angular:component-update',
722+
data: { id: encodedId, timestamp: Date.now() },
723+
})
724+
725+
return []
726+
}
727+
}
728+
}
729+
672730
debugHmr('triggering full reload for component file change')
673731
// Component FILE changes require a full reload because:
674732
// - Class definition changes can't be hot-swapped safely

0 commit comments

Comments
 (0)