@@ -45,21 +45,31 @@ import {NgZone} from '../zone';
4545import { ViewEncapsulation } from '../metadata/view' ;
4646import { NG_COMP_DEF } from './fields' ;
4747
48+ /** Represents `import.meta` plus some information that's not in the built-in types. */
49+ type ImportMetaExtended = ImportMeta & {
50+ hot ?: {
51+ send ?: ( name : string , payload : unknown ) => void ;
52+ } ;
53+ } ;
54+
4855/**
4956 * Replaces the metadata of a component type and re-renders all live instances of the component.
5057 * @param type Class whose metadata will be replaced.
5158 * @param applyMetadata Callback that will apply a new set of metadata on the `type` when invoked.
5259 * @param environment Syntehtic namespace imports that need to be passed along to the callback.
5360 * @param locals Local symbols from the source location that have to be exposed to the callback.
61+ * @param importMeta `import.meta` from the call site of the replacement function. Optional since
62+ * it isn't used internally.
5463 * @param id ID to the class being replaced. **Not** the same as the component definition ID.
55- * Optional since the ID might not be available internally.
64+ * Optional since the ID might not be available internally.
5665 * @codeGenApi
5766 */
5867export function ɵɵreplaceMetadata (
5968 type : Type < unknown > ,
6069 applyMetadata : ( ...args : [ Type < unknown > , unknown [ ] , ...unknown [ ] ] ) => void ,
6170 namespaces : unknown [ ] ,
6271 locals : unknown [ ] ,
72+ importMeta : ImportMetaExtended | null = null ,
6373 id : string | null = null ,
6474) {
6575 ngDevMode && assertComponentDef ( type ) ;
@@ -87,7 +97,7 @@ export function ɵɵreplaceMetadata(
8797 // Note: we have the additional check, because `IsRoot` can also indicate
8898 // a component created through something like `createComponent`.
8999 if ( isRootView ( root ) && root [ PARENT ] === null ) {
90- recreateMatchingLViews ( newDef , oldDef , root ) ;
100+ recreateMatchingLViews ( importMeta , id , newDef , oldDef , root ) ;
91101 }
92102 }
93103 }
@@ -132,10 +142,14 @@ function mergeWithExistingDefinition(
132142
133143/**
134144 * Finds all LViews matching a specific component definition and recreates them.
145+ * @param importMeta `import.meta` information.
146+ * @param id HMR ID of the component.
135147 * @param oldDef Component definition to search for.
136148 * @param rootLView View from which to start the search.
137149 */
138150function recreateMatchingLViews (
151+ importMeta : ImportMetaExtended | null ,
152+ id : string | null ,
139153 newDef : ComponentDef < unknown > ,
140154 oldDef : ComponentDef < unknown > ,
141155 rootLView : LView ,
@@ -152,7 +166,7 @@ function recreateMatchingLViews(
152166 // produce false positives when using inheritance.
153167 if ( tView === oldDef . tView ) {
154168 ngDevMode && assertComponentDef ( oldDef . type ) ;
155- recreateLView ( newDef , oldDef , rootLView ) ;
169+ recreateLView ( importMeta , id , newDef , oldDef , rootLView ) ;
156170 return ;
157171 }
158172
@@ -162,14 +176,14 @@ function recreateMatchingLViews(
162176 if ( isLContainer ( current ) ) {
163177 // The host can be an LView if a component is injecting `ViewContainerRef`.
164178 if ( isLView ( current [ HOST ] ) ) {
165- recreateMatchingLViews ( newDef , oldDef , current [ HOST ] ) ;
179+ recreateMatchingLViews ( importMeta , id , newDef , oldDef , current [ HOST ] ) ;
166180 }
167181
168182 for ( let j = CONTAINER_HEADER_OFFSET ; j < current . length ; j ++ ) {
169- recreateMatchingLViews ( newDef , oldDef , current [ j ] ) ;
183+ recreateMatchingLViews ( importMeta , id , newDef , oldDef , current [ j ] ) ;
170184 }
171185 } else if ( isLView ( current ) ) {
172- recreateMatchingLViews ( newDef , oldDef , current ) ;
186+ recreateMatchingLViews ( importMeta , id , newDef , oldDef , current ) ;
173187 }
174188 }
175189}
@@ -190,11 +204,15 @@ function clearRendererCache(factory: RendererFactory, def: ComponentDef<unknown>
190204
191205/**
192206 * Recreates an LView in-place from a new component definition.
207+ * @param importMeta `import.meta` information.
208+ * @param id HMR ID for the component.
193209 * @param newDef Definition from which to recreate the view.
194210 * @param oldDef Previous component definition being swapped out.
195211 * @param lView View to be recreated.
196212 */
197213function recreateLView (
214+ importMeta : ImportMetaExtended | null ,
215+ id : string | null ,
198216 newDef : ComponentDef < unknown > ,
199217 oldDef : ComponentDef < unknown > ,
200218 lView : LView < unknown > ,
@@ -272,9 +290,34 @@ function recreateLView(
272290
273291 // The callback isn't guaranteed to be inside the Zone so we need to bring it in ourselves.
274292 if ( zone === null ) {
275- recreate ( ) ;
293+ executeWithInvalidateFallback ( importMeta , id , recreate ) ;
276294 } else {
277- zone . run ( recreate ) ;
295+ zone . run ( ( ) => executeWithInvalidateFallback ( importMeta , id , recreate ) ) ;
296+ }
297+ }
298+
299+ /**
300+ * Runs an HMR-related function and falls back to
301+ * invalidating the HMR data if it throws an error.
302+ */
303+ function executeWithInvalidateFallback (
304+ importMeta : ImportMetaExtended | null ,
305+ id : string | null ,
306+ callback : ( ) => void ,
307+ ) {
308+ try {
309+ callback ( ) ;
310+ } catch ( e ) {
311+ const errorMessage = ( e as { message ?: string } ) . message ;
312+
313+ // If we have all the necessary information and APIs to send off the invalidation
314+ // request, send it before rethrowing so the dev server can decide what to do.
315+ if ( id !== null && errorMessage ) {
316+ importMeta ?. hot ?. send ?.( 'angular:invalidate' , { id, message : errorMessage , error : true } ) ;
317+ }
318+
319+ // Throw the error in case the page doesn't get refreshed.
320+ throw e ;
278321 }
279322}
280323
0 commit comments