Skip to content

Commit 71bcbbe

Browse files
committed
feat(@angular/ssr): introduce BootstrapContext for isolated server-side rendering
This commit introduces a number of changes to the server bootstrapping process to make it more robust and less error-prone, especially for concurrent requests. Previously, the server rendering process relied on a module-level global platform injector. This could lead to issues in server-side rendering environments where multiple requests are processed concurrently, as they could inadvertently share or overwrite the global injector state. The new approach introduces a `BootstrapContext` that is passed to the `bootstrapApplication` function. This context provides a platform reference that is scoped to the individual request, ensuring that each server-side render has an isolated platform injector. This prevents state leakage between concurrent requests and makes the overall process more reliable. BREAKING CHANGE: The server-side bootstrapping process has been changed to eliminate the reliance on a global platform injector. Before: ```ts const bootstrap = () => bootstrapApplication(AppComponent, config); ``` After: ```ts const bootstrap = (context: BootstrapContext) => bootstrapApplication(AppComponent, config, context); ```
1 parent c6eba4d commit 71bcbbe

File tree

7 files changed

+24
-10
lines changed

7 files changed

+24
-10
lines changed

packages/angular/ssr/node/src/common-engine/common-engine.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import { ApplicationRef, StaticProvider, Type } from '@angular/core';
10+
import { BootstrapContext } from '@angular/platform-browser';
1011
import { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
1112
import * as fs from 'node:fs';
1213
import { dirname, join, normalize, resolve } from 'node:path';
@@ -22,7 +23,7 @@ const SSG_MARKER_REGEXP = /ng-server-context=["']\w*\|?ssg\|?\w*["']/;
2223

2324
export interface CommonEngineOptions {
2425
/** A method that when invoked returns a promise that returns an `ApplicationRef` instance once resolved or an NgModule. */
25-
bootstrap?: Type<{}> | (() => Promise<ApplicationRef>);
26+
bootstrap?: Type<{}> | ((context: BootstrapContext) => Promise<ApplicationRef>);
2627

2728
/** A set of platform level providers for all requests. */
2829
providers?: StaticProvider[];
@@ -194,7 +195,9 @@ async function exists(path: fs.PathLike): Promise<boolean> {
194195
}
195196
}
196197

197-
function isBootstrapFn(value: unknown): value is () => Promise<ApplicationRef> {
198+
function isBootstrapFn(
199+
value: unknown,
200+
): value is (context: BootstrapContext) => Promise<ApplicationRef> {
198201
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
199202
return typeof value === 'function' && !('ɵmod' in value);
200203
}

packages/angular/ssr/src/routes/ng-routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ export async function getRoutesFromAngularRouterConfig(
629629
const moduleRef = await platformRef.bootstrapModule(bootstrap);
630630
applicationRef = moduleRef.injector.get(ApplicationRef);
631631
} else {
632-
applicationRef = await bootstrap();
632+
applicationRef = await bootstrap({ platformRef });
633633
}
634634

635635
const injector = applicationRef.injector;

packages/angular/ssr/src/utils/ng.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import { ɵConsole } from '@angular/core';
1010
import type { ApplicationRef, StaticProvider, Type } from '@angular/core';
11+
import { BootstrapContext } from '@angular/platform-browser';
1112
import {
1213
ɵSERVER_CONTEXT as SERVER_CONTEXT,
1314
renderApplication,
@@ -23,7 +24,9 @@ import { stripIndexHtmlFromURL } from './url';
2324
* - A reference to an Angular component or module (`Type<unknown>`) that serves as the root of the application.
2425
* - A function that returns a `Promise<ApplicationRef>`, which resolves with the root application reference.
2526
*/
26-
export type AngularBootstrap = Type<unknown> | (() => Promise<ApplicationRef>);
27+
export type AngularBootstrap =
28+
| Type<unknown>
29+
| ((context: BootstrapContext) => Promise<ApplicationRef>);
2730

2831
/**
2932
* Renders an Angular application or module to an HTML string.

packages/angular_devkit/build_angular/src/builders/app-shell/render-worker.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import type { ApplicationRef, StaticProvider, Type } from '@angular/core';
10+
import type { BootstrapContext } from '@angular/platform-browser';
1011
import type { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
1112
import assert from 'node:assert';
1213
import { workerData } from 'node:worker_threads';
@@ -33,7 +34,7 @@ interface ServerBundleExports {
3334
renderApplication?: typeof renderApplication;
3435

3536
/** Standalone application bootstrapping function. */
36-
default?: () => Promise<ApplicationRef>;
37+
default?: (context: BootstrapContext) => Promise<ApplicationRef>;
3738
}
3839

3940
/**
@@ -121,7 +122,9 @@ async function render({ serverBundlePath, document, url }: RenderRequest): Promi
121122
return Promise.race([renderAppPromise, renderingTimeout]).finally(() => clearTimeout(timer));
122123
}
123124

124-
function isBootstrapFn(value: unknown): value is () => Promise<ApplicationRef> {
125+
function isBootstrapFn(
126+
value: unknown,
127+
): value is (context: BootstrapContext) => Promise<ApplicationRef> {
125128
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
126129
return typeof value === 'function' && !('ɵmod' in value);
127130
}

packages/angular_devkit/build_angular/src/builders/prerender/render-worker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import type { ApplicationRef, StaticProvider, Type } from '@angular/core';
10+
import type { BootstrapContext } from '@angular/platform-browser';
1011
import type { renderApplication, renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
1112
import assert from 'node:assert';
1213
import * as fs from 'node:fs';
@@ -148,7 +149,9 @@ async function render({
148149
return result;
149150
}
150151

151-
function isBootstrapFn(value: unknown): value is () => Promise<ApplicationRef> {
152+
function isBootstrapFn(
153+
value: unknown,
154+
): value is (context: BootstrapContext) => Promise<ApplicationRef> {
152155
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
153156
return typeof value === 'function' && !('ɵmod' in value);
154157
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { bootstrapApplication } from '@angular/platform-browser';
1+
import { BootstrapContext, bootstrapApplication } from '@angular/platform-browser';
22
import { AppComponent } from './app/app.component';
33
import { config } from './app/app.config.server';
44

5-
const bootstrap = () => bootstrapApplication(AppComponent, config);
5+
const bootstrap = (context: BootstrapContext) =>
6+
bootstrapApplication(AppComponent, config, BootstrapContext);
67

78
export default bootstrap;

packages/schematics/angular/server/files/server-builder/standalone-src/main.server.ts.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { bootstrapApplication } from '@angular/platform-browser';
22
import { AppComponent } from './app/app.component';
33
import { config } from './app/app.config.server';
44

5-
const bootstrap = () => bootstrapApplication(AppComponent, config);
5+
const bootstrap = (context: BootstrapContext) =>
6+
bootstrapApplication(AppComponent, config, BootstrapContext);
67

78
export default bootstrap;

0 commit comments

Comments
 (0)