Skip to content

Commit ca264bc

Browse files
eneajahohoebbelsB
andauthored
chore: introduced new SSR hydration app (#1886)
including hydration tracking, dynamic render strategies feat: refactored `strategy-provider.service` to handle `primaryStrategy` as either a static value or an observable. * chore: reduce parallelism on ci --------- Co-authored-by: Julian Jandl <julian@jandl.wien>
1 parent 2e48488 commit ca264bc

24 files changed

+1307
-20
lines changed

.github/workflows/build-and-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ jobs:
6363
- name: Run commands in parallel
6464
run: |
6565
npx nx-cloud record -- npx nx format:check
66-
npx nx affected -t lint build test component-test e2e --parallel=4 --exclude=docs
66+
npx nx affected -t lint build test component-test e2e --parallel=3 --exclude=docs
6767
npx nx-cloud complete-ci-run
6868
6969
# Upload coverage reports to Codecov

apps/ssr-hydration/.eslintrc.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"extends": ["../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts"],
7+
"extends": [
8+
"plugin:@nx/angular",
9+
"plugin:@angular-eslint/template/process-inline-templates"
10+
],
11+
"rules": {
12+
"@angular-eslint/directive-selector": [
13+
"error",
14+
{
15+
"type": "attribute",
16+
"prefix": "app",
17+
"style": "camelCase"
18+
}
19+
],
20+
"@angular-eslint/component-selector": [
21+
"error",
22+
{
23+
"type": "element",
24+
"prefix": "app",
25+
"style": "kebab-case"
26+
}
27+
]
28+
}
29+
},
30+
{
31+
"files": ["*.html"],
32+
"extends": ["plugin:@nx/angular-template"],
33+
"rules": {}
34+
}
35+
]
36+
}

apps/ssr-hydration/project.json

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"name": "ssr-hydration",
3+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4+
"projectType": "application",
5+
"prefix": "app",
6+
"sourceRoot": "apps/ssr-hydration/src",
7+
"tags": ["type:app"],
8+
"targets": {
9+
"build": {
10+
"executor": "@angular/build:application",
11+
"outputs": ["{options.outputPath}"],
12+
"options": {
13+
"outputPath": "dist/apps/ssr-hydration",
14+
"browser": "apps/ssr-hydration/src/main.ts",
15+
"polyfills": ["zone.js"],
16+
"tsConfig": "apps/ssr-hydration/tsconfig.app.json",
17+
"inlineStyleLanguage": "scss",
18+
"assets": [
19+
{
20+
"glob": "**/*",
21+
"input": "apps/ssr-hydration/public"
22+
}
23+
],
24+
"styles": ["apps/ssr-hydration/src/styles.scss"],
25+
"server": "apps/ssr-hydration/src/main.server.ts",
26+
"ssr": {
27+
"entry": "apps/ssr-hydration/src/server.ts"
28+
},
29+
"outputMode": "server"
30+
},
31+
"configurations": {
32+
"production": {
33+
"budgets": [
34+
{
35+
"type": "initial",
36+
"maximumWarning": "500kb",
37+
"maximumError": "1mb"
38+
},
39+
{
40+
"type": "anyComponentStyle",
41+
"maximumWarning": "4kb",
42+
"maximumError": "8kb"
43+
}
44+
],
45+
"outputHashing": "all"
46+
},
47+
"development": {
48+
"optimization": false,
49+
"extractLicenses": false,
50+
"sourceMap": true
51+
}
52+
},
53+
"defaultConfiguration": "production"
54+
},
55+
"serve": {
56+
"continuous": true,
57+
"executor": "@angular/build:dev-server",
58+
"configurations": {
59+
"production": {
60+
"buildTarget": "ssr-hydration:build:production"
61+
},
62+
"development": {
63+
"buildTarget": "ssr-hydration:build:development"
64+
}
65+
},
66+
"defaultConfiguration": "development"
67+
},
68+
"extract-i18n": {
69+
"executor": "@angular/build:extract-i18n",
70+
"options": {
71+
"buildTarget": "ssr-hydration:build"
72+
}
73+
},
74+
"lint": {
75+
"executor": "@nx/eslint:lint"
76+
},
77+
"serve-static": {
78+
"continuous": true,
79+
"executor": "@nx/web:file-server",
80+
"options": {
81+
"buildTarget": "ssr-hydration:build",
82+
"staticFilePath": "dist/apps/ssr-hydration/browser",
83+
"spa": true
84+
}
85+
}
86+
}
87+
}
14.7 KB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<app-hydration-demo />

apps/ssr-hydration/src/app/app.component.scss

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Component } from '@angular/core';
2+
import { HydrationDemo } from './hydration-demo';
3+
4+
@Component({
5+
imports: [HydrationDemo],
6+
selector: 'app-root',
7+
templateUrl: './app.component.html',
8+
styleUrl: './app.component.scss',
9+
})
10+
export class AppComponent {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
2+
import { provideServerRendering, withRoutes } from '@angular/ssr';
3+
import { appConfig } from './app.config';
4+
import { serverRoutes } from './app.routes.server';
5+
6+
const serverConfig: ApplicationConfig = {
7+
providers: [provideServerRendering(withRoutes(serverRoutes))],
8+
};
9+
10+
export const config = mergeApplicationConfig(appConfig, serverConfig);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {
2+
ApplicationConfig,
3+
inject,
4+
provideBrowserGlobalErrorListeners,
5+
provideZoneChangeDetection,
6+
} from '@angular/core';
7+
import {
8+
provideClientHydration,
9+
withEventReplay,
10+
} from '@angular/platform-browser';
11+
import {
12+
provideRxRenderStrategies,
13+
RX_CONCURRENT_STRATEGIES,
14+
} from '@rx-angular/cdk/render-strategies';
15+
import { switchMap, tap } from 'rxjs';
16+
import { HydrationTrackerService } from './hydration-tracker';
17+
18+
export const appConfig: ApplicationConfig = {
19+
providers: [
20+
provideClientHydration(withEventReplay()),
21+
provideBrowserGlobalErrorListeners(),
22+
provideZoneChangeDetection({ eventCoalescing: true }),
23+
24+
provideRxRenderStrategies(() => {
25+
const hydrationTracker = inject(HydrationTrackerService);
26+
const strategyFactory = (name: string) => {
27+
return {
28+
[name]: {
29+
name,
30+
work: hydrationTracker.isFullyHydrated()
31+
? RX_CONCURRENT_STRATEGIES[name].work
32+
: (cdRef) => cdRef.markForCheck(),
33+
behavior: ({ work, scope, ngZone }) => {
34+
return (o$) =>
35+
o$.pipe(
36+
switchMap(() =>
37+
hydrationTracker.isFullyHydrated$.pipe((o$) =>
38+
hydrationTracker.isFullyHydrated()
39+
? RX_CONCURRENT_STRATEGIES[name].behavior({
40+
work,
41+
scope,
42+
ngZone,
43+
})(o$)
44+
: o$.pipe(tap(work)),
45+
),
46+
),
47+
);
48+
},
49+
},
50+
};
51+
};
52+
return {
53+
customStrategies: {
54+
...['immediate', 'userBlocking', 'normal', 'low', 'idle'].reduce(
55+
(previousValue, currentValue) => {
56+
return { ...previousValue, ...strategyFactory(currentValue) };
57+
},
58+
{},
59+
),
60+
},
61+
};
62+
}),
63+
],
64+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { RenderMode, ServerRoute } from '@angular/ssr';
2+
3+
export const serverRoutes: ServerRoute[] = [
4+
{
5+
path: '**',
6+
renderMode: RenderMode.Prerender,
7+
},
8+
];

0 commit comments

Comments
 (0)