Skip to content

Commit

Permalink
fix(core): make component id generation more stable between client an…
Browse files Browse the repository at this point in the history
…d 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 angular#58713.
  • Loading branch information
AndrewKushnir committed Nov 22, 2024
1 parent bd08d1d commit 380b2b6
Showing 1 changed file with 25 additions and 4 deletions.
29 changes: 25 additions & 4 deletions packages/core/src/render3/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
//
Expand All @@ -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,
Expand All @@ -717,9 +725,22 @@ function getComponentId<T>(componentDef: ComponentDef<T>): 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;
}

Expand Down

0 comments on commit 380b2b6

Please # to comment.