From 380b2b6db15663df4bcd007ac9498a08bd073905 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Thu, 21 Nov 2024 18:40:19 -0800 Subject: [PATCH] fix(core): make component id generation more stable between client and server builds For components with i18n in templates, the `consts` array is generated by the compiler as a function. If client and server bundles were produced with different minification configurations, the serializable contents of the function body would be different on the client and on the server. This might result in different ids generated. To avoid this issue, this commit updates the logic to not take the `consts` contents into account if it's a function. Resolves #58713. --- packages/core/src/render3/definition.ts | 29 +++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 99d2863263ce..dee27d84135d 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -13,6 +13,7 @@ import {Type, Writable} from '../interface/type'; import {NgModuleDef} from '../metadata/ng_module_def'; import {SchemaMetadata} from '../metadata/schema'; import {ViewEncapsulation} from '../metadata/view'; +import {assertNotEqual} from '../util/assert'; import {noSideEffects} from '../util/closure'; import {EMPTY_ARRAY, EMPTY_OBJ} from '../util/empty'; import {initNgDevMode} from '../util/ng_dev_mode'; @@ -688,6 +689,14 @@ export const GENERATED_COMP_IDS = new Map>(); function getComponentId(componentDef: ComponentDef): string { let hash = 0; + // For components with i18n in templates, the `consts` array is generated by the compiler + // as a function. If client and server bundles were produced with different minification + // configurations, the serializable contents of the function body would be different on + // the client and on the server. This might result in different ids generated. To avoid this + // issue, we do not take the `consts` contents into account if it's a function. + // See https://github.com/angular/angular/issues/58713. + const componentDefConsts = typeof componentDef.consts === 'function' ? '' : componentDef.consts; + // We cannot rely solely on the component selector as the same selector can be used in different // modules. // @@ -697,13 +706,12 @@ function getComponentId(componentDef: ComponentDef): string { // Example: // https://github.com/angular/components/blob/d9f82c8f95309e77a6d82fd574c65871e91354c2/src/material/core/option/option.ts#L248 // https://github.com/angular/components/blob/285f46dc2b4c5b127d356cb7c4714b221f03ce50/src/material/legacy-core/option/option.ts#L32 - const hashSelectors = [ componentDef.selectors, componentDef.ngContentSelectors, componentDef.hostVars, componentDef.hostAttrs, - componentDef.consts, + componentDefConsts, componentDef.vars, componentDef.decls, componentDef.encapsulation, @@ -717,9 +725,22 @@ function getComponentId(componentDef: ComponentDef): string { Object.getOwnPropertyNames(componentDef.type.prototype), !!componentDef.contentQueries, !!componentDef.viewQuery, - ].join('|'); + ]; + + if (typeof ngDevMode === 'undefined' || ngDevMode) { + // If client and server bundles were produced with different minification configurations, + // the serializable contents of the function body would be different on the client and on + // the server. Ensure that we do not accidentally use functions in component id computation. + for (const item of hashSelectors) { + assertNotEqual( + typeof item, + 'function', + 'Internal error: attempting to use a function in component id computation logic.', + ); + } + } - for (const char of hashSelectors) { + for (const char of hashSelectors.join('|')) { hash = (Math.imul(31, hash) + char.charCodeAt(0)) << 0; }