fix(core): make component id generation more stable between client and server builds (#58813)

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.

PR Close #58813
This commit is contained in:
Andrew Kushnir 2024-11-21 18:40:19 -08:00 committed by Jessica Janiuk
parent 4f2df5bcb5
commit 834c7c3ccc

View file

@ -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<string, Type<unknown>>();
function getComponentId<T>(componentDef: ComponentDef<T>): 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<T>(componentDef: ComponentDef<T>): 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<T>(componentDef: ComponentDef<T>): string {
Object.getOwnPropertyNames(componentDef.type.prototype),
!!componentDef.contentQueries,
!!componentDef.viewQuery,
].join('|');
];
for (const char of hashSelectors) {
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.join('|')) {
hash = (Math.imul(31, hash) + char.charCodeAt(0)) << 0;
}