mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(core): do not enable hydration when server response was incorrect (#49750)
This commit updates the logic to avoid enabling hydration in case server response doesn't contain hydration-related info serialized. It can happen when `provideClientHydration()` call only happens on the client, but not on the server. PR Close #49750
This commit is contained in:
@@ -72,27 +72,15 @@ function isBrowser(): boolean {
|
||||
/**
|
||||
* Outputs a message with hydration stats into a console.
|
||||
*/
|
||||
function printHydrationInfo(injector: Injector) {
|
||||
function printHydrationStats(injector: Injector) {
|
||||
const console = injector.get(Console);
|
||||
const transferState = injector.get(TransferState, null, {optional: true});
|
||||
if (transferState && transferState.get(NGH_DATA_KEY, null)) {
|
||||
const message = `Angular hydrated ${ngDevMode!.hydratedComponents} component(s) ` +
|
||||
`and ${ngDevMode!.hydratedNodes} node(s), ` +
|
||||
`${ngDevMode!.componentsSkippedHydration} component(s) were skipped. ` +
|
||||
`Note: this feature is in Developer Preview mode. ` +
|
||||
`Learn more at https://next.angular.io/guide/hydration.`;
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(message);
|
||||
} else {
|
||||
const message = formatRuntimeError(
|
||||
RuntimeErrorCode.MISSING_HYDRATION_ANNOTATIONS,
|
||||
'Angular hydration was enabled on the client, but there was no ' +
|
||||
'serialized information present in the server response. ' +
|
||||
'Make sure the `provideClientHydration()` is included into the list ' +
|
||||
'of providers in the server part of the application configuration.');
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn(message);
|
||||
}
|
||||
const message = `Angular hydrated ${ngDevMode!.hydratedComponents} component(s) ` +
|
||||
`and ${ngDevMode!.hydratedNodes} node(s), ` +
|
||||
`${ngDevMode!.componentsSkippedHydration} component(s) were skipped. ` +
|
||||
`Note: this feature is in Developer Preview mode. ` +
|
||||
`Learn more at https://next.angular.io/guide/hydration.`;
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +106,32 @@ function whenStable(
|
||||
*/
|
||||
export function withDomHydration(): EnvironmentProviders {
|
||||
return makeEnvironmentProviders([
|
||||
{
|
||||
provide: IS_HYDRATION_FEATURE_ENABLED,
|
||||
useFactory: () => {
|
||||
if (isBrowser()) {
|
||||
// On the client, verify that the server response contains
|
||||
// hydration annotations. Otherwise, keep hydration disabled.
|
||||
const transferState = inject(TransferState, {optional: true});
|
||||
const hasHydrationAnnotations = !!transferState?.get(NGH_DATA_KEY, null);
|
||||
if (!hasHydrationAnnotations) {
|
||||
const console = inject(Console);
|
||||
const message = formatRuntimeError(
|
||||
RuntimeErrorCode.MISSING_HYDRATION_ANNOTATIONS,
|
||||
'Angular hydration was requested on the client, but there was no ' +
|
||||
'serialized information present in the server response, ' +
|
||||
'thus hydration was not enabled. ' +
|
||||
'Make sure the `provideClientHydration()` is included into the list ' +
|
||||
'of providers in the server part of the application configuration.');
|
||||
// tslint:disable-next-line:no-console
|
||||
console.warn(message);
|
||||
}
|
||||
return hasHydrationAnnotations;
|
||||
}
|
||||
// We are running on the server, always return `true`.
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ENVIRONMENT_INITIALIZER,
|
||||
useValue: () => {
|
||||
@@ -126,27 +140,26 @@ export function withDomHydration(): EnvironmentProviders {
|
||||
// on the client. Moving forward, the `isBrowser` check should
|
||||
// be replaced with a tree-shakable alternative (e.g. `isServer`
|
||||
// flag).
|
||||
if (isBrowser()) {
|
||||
if (isBrowser() && inject(IS_HYDRATION_FEATURE_ENABLED)) {
|
||||
enableHydrationRuntimeSupport();
|
||||
}
|
||||
},
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: IS_HYDRATION_FEATURE_ENABLED,
|
||||
useValue: true,
|
||||
},
|
||||
{
|
||||
provide: PRESERVE_HOST_CONTENT,
|
||||
// Preserve host element content only in a browser
|
||||
// environment. On a server, an application is rendered
|
||||
// from scratch, so the host content needs to be empty.
|
||||
useFactory: () => isBrowser(),
|
||||
useFactory: () => {
|
||||
// Preserve host element content only in a browser
|
||||
// environment and when hydration is configured properly.
|
||||
// On a server, an application is rendered from scratch,
|
||||
// so the host content needs to be empty.
|
||||
return isBrowser() && inject(IS_HYDRATION_FEATURE_ENABLED);
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: APP_BOOTSTRAP_LISTENER,
|
||||
useFactory: () => {
|
||||
if (isBrowser()) {
|
||||
if (isBrowser() && inject(IS_HYDRATION_FEATURE_ENABLED)) {
|
||||
const appRef = inject(ApplicationRef);
|
||||
const pendingTasks = inject(InitialRenderPendingTasks);
|
||||
const injector = inject(Injector);
|
||||
@@ -159,7 +172,7 @@ export function withDomHydration(): EnvironmentProviders {
|
||||
cleanupDehydratedViews(appRef);
|
||||
|
||||
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
|
||||
printHydrationInfo(injector);
|
||||
printHydrationStats(injector);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4022,11 +4022,23 @@ describe('platform-server integration', () => {
|
||||
resetTViewsFor(SimpleComponent);
|
||||
|
||||
const appRef = await hydrate(html, SimpleComponent, [withDebugConsole()]);
|
||||
const compRef = getComponentRef<SimpleComponent>(appRef);
|
||||
appRef.tick();
|
||||
|
||||
verifyHasLog(
|
||||
appRef,
|
||||
'NG0505: Angular hydration was enabled on the client, ' +
|
||||
'but there was no serialized information present in the server response.');
|
||||
'NG0505: Angular hydration was requested on the client, ' +
|
||||
'but there was no serialized information present in the server response');
|
||||
|
||||
const clientRootNode = compRef.location.nativeElement;
|
||||
|
||||
// Make sure that no hydration logic was activated,
|
||||
// effectively re-rendering from scratch happened and
|
||||
// all the content inside the <app> host element was
|
||||
// cleared on the client (as it usually happens in client
|
||||
// rendering mode).
|
||||
verifyNoNodesWereClaimedForHydration(clientRootNode);
|
||||
verifyClientAndSSRContentsMatch(ssrContents, clientRootNode);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user