66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9+ import { StaticProvider , ɵConsole , ɵresetCompiledComponents } from '@angular/core' ;
10+ import { ɵSERVER_CONTEXT as SERVER_CONTEXT } from '@angular/platform-server' ;
911import { ServerAssets } from './assets' ;
12+ import { Console } from './console' ;
1013import { Hooks } from './hooks' ;
1114import { getAngularAppManifest } from './manifest' ;
12- import { ServerRenderContext , render } from './render' ;
1315import { 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+ }
1427
1528/**
1629 * Represents a locale-specific Angular server application managed by the server application engine.
@@ -26,15 +39,13 @@ export class AngularServerApp {
2639
2740 /**
2841 * The manifest associated with this server application.
29- * @internal
3042 */
31- readonly manifest = getAngularAppManifest ( ) ;
43+ private readonly manifest = getAngularAppManifest ( ) ;
3244
3345 /**
3446 * An instance of ServerAsset that handles server-side asset.
35- * @internal
3647 */
37- readonly assets = new ServerAssets ( this . manifest ) ;
48+ private readonly assets = new ServerAssets ( this . manifest ) ;
3849
3950 /**
4051 * The router instance used for route matching and handling.
@@ -52,7 +63,50 @@ export class AngularServerApp {
5263 *
5364 * @returns A promise that resolves to the HTTP response object resulting from the rendering, or null if no match is found.
5465 */
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 (
56110 request : Request ,
57111 requestContext ?: unknown ,
58112 serverContext : ServerRenderContext = ServerRenderContext . SSR ,
@@ -73,7 +127,60 @@ export class AngularServerApp {
73127 return Response . redirect ( new URL ( redirectTo , url ) , 302 ) ;
74128 }
75129
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+ ) ;
77184 }
78185}
79186
0 commit comments