diff --git a/napi/angular-compiler/test/hmr-hot-update.test.ts b/napi/angular-compiler/test/hmr-hot-update.test.ts index 67de01cf5..d38f3994d 100644 --- a/napi/angular-compiler/test/hmr-hot-update.test.ts +++ b/napi/angular-compiler/test/hmr-hot-update.test.ts @@ -3724,3 +3724,46 @@ describe('@ng/component endpoint resolves the styles per class', () => { expect(body, 'a definitively styleless class must still be cleared').toContain('styles: []') }) }) + +describe('a template edited while its component fails to compile', () => { + it('is compiled from the edited file once the component compiles again', async () => { + const dir = join(tempDir, 'src', 'stale') + mkdirSync(dir, { recursive: true }) + const template = join(dir, 'stale.component.html') + const component = join(dir, 'stale.component.ts') + const source = ` + import { Component } from '@angular/core'; + + @Component({ selector: 'app-stale', templateUrl: './stale.component.html' }) + export class StaleComponent {} + ` + writeFileSync(component, source) + writeFileSync(template, '

Before

') + + const plugin = getAngularPlugin() + const server = await setupPluginWithServer(plugin) + const transform = async (code: string) => { + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + return (await plugin.transform.handler.call( + { error() {}, warn() {}, addWatchFile() {} } as any, + code, + component, + )) as { code: string } | undefined + } + + expect((await transform(source))?.code).toContain('Before') + + // the component stops compiling, and its template is edited meanwhile + await transform( + source.replace('export class StaleComponent {}', 'export class StaleComponent { x = ; }'), + ).catch(() => {}) + writeFileSync(template, '

After

') + await callHandleHotUpdate(plugin, createMockHmrContext(template, [], server)) + + const fixed = (await transform(source))?.code + expect(fixed).toContain('After') + expect(fixed).not.toContain('Before') + }) +}) diff --git a/napi/angular-compiler/vite-plugin/index.ts b/napi/angular-compiler/vite-plugin/index.ts index e022d15b7..4f26b15c6 100644 --- a/napi/angular-compiler/vite-plugin/index.ts +++ b/napi/angular-compiler/vite-plugin/index.ts @@ -1286,6 +1286,13 @@ export function angular(options: PluginOptions = {}): Plugin[] { // anything below can preprocess one (see `compiledStyles`). startStyleCycle() + // The file's cached contents are stale, whatever its role now. A + // template edited while its component fails to compile is not + // registered as the component's at that moment (the failed transform + // found no templateUrl), so the branches below would leave its old + // contents cached for the component's next transform. + resourceCache.delete(normalizePath(ctx.file)) + if (!pluginOptions.liveReload) return debugHmr('handleHotUpdate file=%s', ctx.file)