From d076c3205977b958d7af9bc8ce566fc87f0fa4a3 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:32:17 -0500 Subject: [PATCH] refactor(core): always create new renderer when applying HMR metadata update (#58527) The DOM renderer classes perform initialization that captures state from the component definition during construction. To ensure that the state is kept synchronized with any newly applied metadata from an HMR `applyMetadata` call, each renderer is now recreated during the apply process. This also allows inline component styles to be updated in cases where external component stylesheets may not be viable. PR Close #58527 --- packages/core/src/render3/hmr.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/core/src/render3/hmr.ts b/packages/core/src/render3/hmr.ts index 374409716aa..004a8092a10 100644 --- a/packages/core/src/render3/hmr.ts +++ b/packages/core/src/render3/hmr.ts @@ -40,6 +40,7 @@ import { } from './interfaces/view'; import {assertTNodeType} from './node_assert'; import {destroyLView, removeViewFromDOM} from './node_manipulation'; +import {RendererFactory} from './interfaces/renderer'; /** * Replaces the metadata of a component type and re-renders all live instances of the component. @@ -114,6 +115,19 @@ function recreateMatchingLViews(def: ComponentDef, rootLView: LView): v } } +/** + * Removes any cached renderers from the factory for the provided type. + * This is currently used by the HMR logic to ensure Renderers are kept + * synchronized with any definition metadata updates. + * @param factory A RendererFactory2 instance. + * @param def A ComponentDef instance. + */ +function clearRendererCache(factory: RendererFactory, def: ComponentDef) { + // Cast to `any` to read a private field. + // NOTE: This must be kept synchronized with the renderer factory implementation in platform-browser. + (factory as any).rendererByCompId?.remove(def.id); +} + /** * Recreates an LView in-place from a new component definition. * @param def Definition from which to recreate the view. @@ -132,6 +146,11 @@ function recreateLView(def: ComponentDef, lView: LView): void // Recreate the TView since the template might've changed. const newTView = getOrCreateComponentTView(def); + // Always force the creation of a new renderer to ensure state captured during construction + // stays consistent with the new component definition by clearing any old cached factories. + const rendererFactory = lView[ENVIRONMENT].rendererFactory; + clearRendererCache(rendererFactory, def); + // Create a new LView from the new TView, but reusing the existing TNode and DOM node. const newLView = createLView( parentLView, @@ -141,7 +160,7 @@ function recreateLView(def: ComponentDef, lView: LView): void host, tNode, null, - lView[ENVIRONMENT].rendererFactory.createRenderer(host, def), + rendererFactory.createRenderer(host, def), null, null, null,