6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
8
9
+ import { StaticProvider , ɵConsole , ɵresetCompiledComponents } from '@angular/core' ;
10
+ import { ɵSERVER_CONTEXT as SERVER_CONTEXT } from '@angular/platform-server' ;
9
11
import { ServerAssets } from './assets' ;
12
+ import { Console } from './console' ;
10
13
import { Hooks } from './hooks' ;
11
14
import { getAngularAppManifest } from './manifest' ;
12
- import { ServerRenderContext , render } from './render' ;
13
15
import { ServerRouter } from './routes/router' ;
16
+ import { REQUEST , REQUEST_CONTEXT , RESPONSE_INIT } from './tokens' ;
17
+ import { renderAngular } from './utils/ng' ;
18
+
19
+ /**
20
+ * Enum representing the different contexts in which server rendering can occur.
21
+ */
22
+ export enum ServerRenderContext {
23
+ SSR = 'ssr' ,
24
+ SSG = 'ssg' ,
25
+ AppShell = 'app-shell' ,
26
+ }
14
27
15
28
/**
16
29
* Represents a locale-specific Angular server application managed by the server application engine.
@@ -26,15 +39,13 @@ export class AngularServerApp {
26
39
27
40
/**
28
41
* The manifest associated with this server application.
29
- * @internal
30
42
*/
31
- readonly manifest = getAngularAppManifest ( ) ;
43
+ private readonly manifest = getAngularAppManifest ( ) ;
32
44
33
45
/**
34
46
* An instance of ServerAsset that handles server-side asset.
35
- * @internal
36
47
*/
37
- readonly assets = new ServerAssets ( this . manifest ) ;
48
+ private readonly assets = new ServerAssets ( this . manifest ) ;
38
49
39
50
/**
40
51
* The router instance used for route matching and handling.
@@ -52,7 +63,50 @@ export class AngularServerApp {
52
63
*
53
64
* @returns A promise that resolves to the HTTP response object resulting from the rendering, or null if no match is found.
54
65
*/
55
- async render (
66
+ render (
67
+ request : Request ,
68
+ requestContext ?: unknown ,
69
+ serverContext : ServerRenderContext = ServerRenderContext . SSR ,
70
+ ) : Promise < Response | null > {
71
+ return Promise . race ( [
72
+ this . createAbortPromise ( request ) ,
73
+ this . handleRendering ( request , requestContext , serverContext ) ,
74
+ ] ) ;
75
+ }
76
+
77
+ /**
78
+ * Creates a promise that rejects when the request is aborted.
79
+ *
80
+ * @param request - The HTTP request to monitor for abortion.
81
+ * @returns A promise that never resolves but rejects with an `AbortError` if the request is aborted.
82
+ */
83
+ private createAbortPromise ( request : Request ) : Promise < never > {
84
+ return new Promise < never > ( ( _ , reject ) => {
85
+ request . signal . addEventListener (
86
+ 'abort' ,
87
+ ( ) => {
88
+ const abortError = new Error (
89
+ `Request for: ${ request . url } was aborted.\n${ request . signal . reason } ` ,
90
+ ) ;
91
+ abortError . name = 'AbortError' ;
92
+ reject ( abortError ) ;
93
+ } ,
94
+ { once : true } ,
95
+ ) ;
96
+ } ) ;
97
+ }
98
+
99
+ /**
100
+ * Handles the server-side rendering process for the given HTTP request.
101
+ * This method matches the request URL to a route and performs rendering if a matching route is found.
102
+ *
103
+ * @param request - The incoming HTTP request to be processed.
104
+ * @param requestContext - Optional additional context for rendering, such as request metadata.
105
+ * @param serverContext - The rendering context. Defaults to server-side rendering (SSR).
106
+ *
107
+ * @returns A promise that resolves to the rendered response, or null if no matching route is found.
108
+ */
109
+ private async handleRendering (
56
110
request : Request ,
57
111
requestContext ?: unknown ,
58
112
serverContext : ServerRenderContext = ServerRenderContext . SSR ,
@@ -73,7 +127,60 @@ export class AngularServerApp {
73
127
return Response . redirect ( new URL ( redirectTo , url ) , 302 ) ;
74
128
}
75
129
76
- return render ( this , request , serverContext , requestContext ) ;
130
+ const isSsrMode = serverContext === ServerRenderContext . SSR ;
131
+ const responseInit : ResponseInit = { } ;
132
+ const platformProviders : StaticProvider = [
133
+ {
134
+ provide : SERVER_CONTEXT ,
135
+ useValue : serverContext ,
136
+ } ,
137
+ ] ;
138
+
139
+ if ( isSsrMode ) {
140
+ platformProviders . push (
141
+ {
142
+ provide : REQUEST ,
143
+ useValue : request ,
144
+ } ,
145
+ {
146
+ provide : REQUEST_CONTEXT ,
147
+ useValue : requestContext ,
148
+ } ,
149
+ {
150
+ provide : RESPONSE_INIT ,
151
+ useValue : responseInit ,
152
+ } ,
153
+ ) ;
154
+ }
155
+
156
+ if ( typeof ngDevMode === 'undefined' || ngDevMode ) {
157
+ // Need to clean up GENERATED_COMP_IDS map in `@angular/core`.
158
+ // Otherwise an incorrect component ID generation collision detected warning will be displayed in development.
159
+ // See: https://github.com/angular/angular-cli/issues/25924
160
+ ɵresetCompiledComponents ( ) ;
161
+ }
162
+
163
+ // An Angular Console Provider that does not print a set of predefined logs.
164
+ platformProviders . push ( {
165
+ provide : ɵConsole ,
166
+ // Using `useClass` would necessitate decorating `Console` with `@Injectable`,
167
+ // which would require switching from `ts_library` to `ng_module`. This change
168
+ // would also necessitate various patches of `@angular/bazel` to support ESM.
169
+ useFactory : ( ) => new Console ( ) ,
170
+ } ) ;
171
+
172
+ const { manifest, hooks, assets } = this ;
173
+
174
+ let html = await assets . getIndexServerHtml ( ) ;
175
+ // Skip extra microtask if there are no pre hooks.
176
+ if ( hooks . has ( 'html:transform:pre' ) ) {
177
+ html = await hooks . run ( 'html:transform:pre' , { html } ) ;
178
+ }
179
+
180
+ return new Response (
181
+ await renderAngular ( html , manifest . bootstrap ( ) , new URL ( request . url ) , platformProviders ) ,
182
+ responseInit ,
183
+ ) ;
77
184
}
78
185
}
79
186
0 commit comments