From 59d873892949719aa6fda2965a5b275ec174d0d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Tue, 12 Nov 2024 12:43:32 +0100 Subject: [PATCH] feat(angular): integrate `withEventReplay()` in `provideClientHydration` for ssr apps --- .../host/__snapshots__/host.spec.ts.snap | 20 +++- .../setup-ssr/lib/add-hydration.spec.ts | 8 +- .../generators/setup-ssr/lib/add-hydration.ts | 30 +++-- .../generators/setup-ssr/setup-ssr.spec.ts | 103 ++++++++++++++++-- 4 files changed, 133 insertions(+), 28 deletions(-) diff --git a/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap b/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap index b92327ba46050..fdd39c48636aa 100644 --- a/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap +++ b/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap @@ -52,6 +52,7 @@ exports[`Host App Generator --ssr should generate the correct files 1`] = ` import { BrowserModule, provideClientHydration, + withEventReplay, } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; @@ -61,7 +62,7 @@ import { NxWelcomeComponent } from './nx-welcome.component'; @NgModule({ declarations: [AppComponent, NxWelcomeComponent], imports: [BrowserModule, RouterModule.forRoot(appRoutes)], - providers: [provideClientHydration()], + providers: [provideClientHydration(withEventReplay())], bootstrap: [AppComponent], }) export class AppModule {} @@ -405,11 +406,14 @@ exports[`Host App Generator --ssr should generate the correct files for standalo "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { appRoutes } from './app.routes'; -import { provideClientHydration } from '@angular/platform-browser'; +import { + provideClientHydration, + withEventReplay, +} from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { providers: [ - provideClientHydration(), + provideClientHydration(withEventReplay()), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes), ], @@ -627,11 +631,14 @@ exports[`Host App Generator --ssr should generate the correct files for standalo "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { appRoutes } from './app.routes'; -import { provideClientHydration } from '@angular/platform-browser'; +import { + provideClientHydration, + withEventReplay, +} from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { providers: [ - provideClientHydration(), + provideClientHydration(withEventReplay()), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes), ], @@ -703,6 +710,7 @@ exports[`Host App Generator --ssr should generate the correct files when --types import { BrowserModule, provideClientHydration, + withEventReplay, } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; @@ -712,7 +720,7 @@ import { NxWelcomeComponent } from './nx-welcome.component'; @NgModule({ declarations: [AppComponent, NxWelcomeComponent], imports: [BrowserModule, RouterModule.forRoot(appRoutes)], - providers: [provideClientHydration()], + providers: [provideClientHydration(withEventReplay())], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/packages/angular/src/generators/setup-ssr/lib/add-hydration.spec.ts b/packages/angular/src/generators/setup-ssr/lib/add-hydration.spec.ts index 269ffffbe7736..24478ced9cf5f 100644 --- a/packages/angular/src/generators/setup-ssr/lib/add-hydration.spec.ts +++ b/packages/angular/src/generators/setup-ssr/lib/add-hydration.spec.ts @@ -35,10 +35,10 @@ export const appConfig: ApplicationConfig = { "import { ApplicationConfig } from '@angular/core'; import { provideRouter } from '@angular/router'; import { appRoutes } from './app.routes'; - import { provideClientHydration } from '@angular/platform-browser'; + import { provideClientHydration, withEventReplay } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { - providers: [provideClientHydration(),provideRouter(appRoutes)], + providers: [provideClientHydration(withEventReplay()),provideRouter(appRoutes)], }; " `); @@ -98,7 +98,7 @@ export class AppModule {} expect(tree.read('app1/src/app/app.module.ts', 'utf-8')) .toMatchInlineSnapshot(` "import { NgModule } from '@angular/core'; - import { BrowserModule, provideClientHydration } from '@angular/platform-browser'; + import { BrowserModule, provideClientHydration, withEventReplay } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { appRoutes } from './app.routes'; @@ -108,7 +108,7 @@ export class AppModule {} declarations: [AppComponent, NxWelcomeComponent], imports: [BrowserModule, RouterModule.forRoot(appRoutes)], bootstrap: [AppComponent], - providers: [provideClientHydration()], + providers: [provideClientHydration(withEventReplay())], }) export class AppModule {} " diff --git a/packages/angular/src/generators/setup-ssr/lib/add-hydration.ts b/packages/angular/src/generators/setup-ssr/lib/add-hydration.ts index 9dc8fc5ef1a10..537c32e0bdd00 100644 --- a/packages/angular/src/generators/setup-ssr/lib/add-hydration.ts +++ b/packages/angular/src/generators/setup-ssr/lib/add-hydration.ts @@ -10,6 +10,7 @@ import { addProviderToAppConfig, addProviderToModule, } from '../../../utils/nx-devkit/ast-utils'; +import { getInstalledAngularVersionInfo } from '../../utils/version-utils'; import { type Schema } from '../schema'; let tsModule: typeof import('typescript'); @@ -60,6 +61,8 @@ export function addHydration(tree: Tree, options: Schema) { ); }; + const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree); + sourceFile = addImport( sourceFile, 'provideClientHydration', @@ -67,18 +70,23 @@ export function addHydration(tree: Tree, options: Schema) { pathToClientConfigFile ); - if (options.standalone) { - addProviderToAppConfig( - tree, - pathToClientConfigFile, - 'provideClientHydration()' - ); - } else { - addProviderToModule( - tree, + if (angularMajorVersion >= 19) { + sourceFile = addImport( sourceFile, - pathToClientConfigFile, - 'provideClientHydration()' + 'withEventReplay', + '@angular/platform-browser', + pathToClientConfigFile ); } + + const provider = + angularMajorVersion >= 19 + ? 'provideClientHydration(withEventReplay())' + : 'provideClientHydration()'; + + if (options.standalone) { + addProviderToAppConfig(tree, pathToClientConfigFile, provider); + } else { + addProviderToModule(tree, sourceFile, pathToClientConfigFile, provider); + } } diff --git a/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts b/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts index 6a65074f98d39..d529f3fb263b1 100644 --- a/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts +++ b/packages/angular/src/generators/setup-ssr/setup-ssr.spec.ts @@ -81,6 +81,7 @@ describe('setupSSR', () => { import { BrowserModule, provideClientHydration, + withEventReplay, } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; @@ -90,7 +91,7 @@ describe('setupSSR', () => { @NgModule({ declarations: [AppComponent, NxWelcomeComponent], imports: [BrowserModule, RouterModule.forRoot(appRoutes)], - providers: [provideClientHydration()], + providers: [provideClientHydration(withEventReplay())], bootstrap: [AppComponent], }) export class AppModule {} @@ -269,7 +270,7 @@ describe('setupSSR', () => { expect(tree.read('app1/src/app/app.module.ts', 'utf-8')) .toMatchInlineSnapshot(` "import { NgModule } from '@angular/core'; - import { BrowserModule, provideClientHydration } from '@angular/platform-browser'; + import { BrowserModule, provideClientHydration, withEventReplay } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { appRoutes } from './app.routes'; @@ -281,7 +282,7 @@ describe('setupSSR', () => { BrowserModule, RouterModule.forRoot(appRoutes), ], - providers: [provideClientHydration()], + providers: [provideClientHydration(withEventReplay())], bootstrap: [AppComponent], }) export class AppModule {} @@ -444,7 +445,7 @@ describe('setupSSR', () => { expect(tree.read('app1/src/app/app.module.ts', 'utf-8')) .toMatchInlineSnapshot(` "import { NgModule } from '@angular/core'; - import { BrowserModule, provideClientHydration } from '@angular/platform-browser'; + import { BrowserModule, provideClientHydration, withEventReplay } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { appRoutes } from './app.routes'; @@ -456,7 +457,7 @@ describe('setupSSR', () => { BrowserModule, RouterModule.forRoot(appRoutes), ], - providers: [provideClientHydration()], + providers: [provideClientHydration(withEventReplay())], bootstrap: [AppComponent], }) export class AppModule {} @@ -486,10 +487,10 @@ describe('setupSSR', () => { "import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; import { appRoutes } from './app.routes'; - import { provideClientHydration } from '@angular/platform-browser'; + import { provideClientHydration, withEventReplay } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { - providers: [provideClientHydration(),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ] + providers: [provideClientHydration(withEventReplay()),provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(appRoutes) ] }; " `); @@ -616,5 +617,93 @@ describe('setupSSR', () => { ); expect(pkgJson.devDependencies['@nguniversal/builders']).toBeUndefined(); }); + + it('should add hydration correctly for NgModule apps', async () => { + const tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { '@angular/core': '17.2.0' }, + })); + await generateTestApplication(tree, { + directory: 'app1', + standalone: false, + skipFormat: true, + }); + + await setupSsr(tree, { + project: 'app1', + hydration: true, + skipFormat: true, + }); + + expect(tree.read('app1/src/app/app.module.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { NgModule } from '@angular/core'; + import { BrowserModule, provideClientHydration } from '@angular/platform-browser'; + import { RouterModule } from '@angular/router'; + import { AppComponent } from './app.component'; + import { appRoutes } from './app.routes'; + import { NxWelcomeComponent } from './nx-welcome.component'; + + @NgModule({ + declarations: [AppComponent, NxWelcomeComponent], + imports: [ + BrowserModule, + RouterModule.forRoot(appRoutes), + ], + providers: [provideClientHydration()], + bootstrap: [AppComponent], + }) + export class AppModule {} + " + `); + }); + + it('should add hydration correctly to standalone', async () => { + const tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => ({ + ...json, + dependencies: { '@angular/core': '17.2.0' }, + })); + await generateTestApplication(tree, { + directory: 'app1', + skipFormat: true, + }); + + await setupSsr(tree, { + project: 'app1', + hydration: true, + skipFormat: true, + }); + + expect(tree.read('app1/src/app/app.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { ApplicationConfig } from '@angular/core'; + import { provideRouter } from '@angular/router'; + import { appRoutes } from './app.routes'; + import { provideClientHydration } from '@angular/platform-browser'; + + export const appConfig: ApplicationConfig = { + providers: [provideClientHydration(),provideRouter(appRoutes) ] + }; + " + `); + + expect(tree.read('app1/src/app/app.config.server.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; + import { provideServerRendering } from '@angular/platform-server'; + import { appConfig } from './app.config'; + + const serverConfig: ApplicationConfig = { + providers: [ + provideServerRendering() + ] + }; + + export const config = mergeApplicationConfig(appConfig, serverConfig); + " + `); + }); }); });