Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 32980f7

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 32980f7

File tree

13 files changed

+48
-25
lines changed

13 files changed

+48
-25
lines changed

goldens/public-api/angular/ssr/node/index.api.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
```ts
66

77
import { ApplicationRef } from '@angular/core';
8+
import { BootstrapContext } from '@angular/platform-browser';
89
import { Http2ServerRequest } from 'node:http2';
910
import { Http2ServerResponse } from 'node:http2';
1011
import { IncomingMessage } from 'node:http';
@@ -25,14 +26,14 @@ export class CommonEngine {
2526

2627
// @public (undocumented)
2728
export interface CommonEngineOptions {
28-
bootstrap?: Type<{}> | (() => Promise<ApplicationRef>);
29+
bootstrap?: Type<{}> | ((context: BootstrapContext) => Promise<ApplicationRef>);
2930
enablePerformanceProfiler?: boolean;
3031
providers?: StaticProvider[];
3132
}
3233

3334
// @public (undocumented)
3435
export interface CommonEngineRenderOptions {
35-
bootstrap?: Type<{}> | (() => Promise<ApplicationRef>);
36+
bootstrap?: Type<{}> | ((context: BootstrapContext) => Promise<ApplicationRef>);
3637
// (undocumented)
3738
document?: string;
3839
// (undocumented)

packages/angular/build/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ ts_project(
8787
"//:node_modules/@angular/compiler-cli",
8888
"//:node_modules/@angular/core",
8989
"//:node_modules/@angular/localize",
90+
"//:node_modules/@angular/platform-browser",
9091
"//:node_modules/@angular/platform-server",
9192
"//:node_modules/@angular/service-worker",
9293
"//:node_modules/@babel/core",

packages/angular/build/src/utils/server-rendering/load-esm-from-memory.ts

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

99
import type { ApplicationRef, Type } from '@angular/core';
10+
import type { BootstrapContext } from '@angular/platform-browser';
1011
import type { ɵextractRoutesAndCreateRouteTree, ɵgetOrCreateAngularServerApp } from '@angular/ssr';
1112
import { assertIsError } from '../error';
1213
import { loadEsmModule } from '../load-esm';
@@ -15,7 +16,7 @@ import { loadEsmModule } from '../load-esm';
1516
* Represents the exports available from the main server bundle.
1617
*/
1718
interface MainServerBundleExports {
18-
default: (() => Promise<ApplicationRef>) | Type<unknown>;
19+
default: ((context: BootstrapContext) => Promise<ApplicationRef>) | Type<unknown>;
1920
ɵextractRoutesAndCreateRouteTree: typeof ɵextractRoutesAndCreateRouteTree;
2021
ɵgetOrCreateAngularServerApp: typeof ɵgetOrCreateAngularServerApp;
2122
}

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

Lines changed: 6 additions & 3 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[];
@@ -33,7 +34,7 @@ export interface CommonEngineOptions {
3334

3435
export interface CommonEngineRenderOptions {
3536
/** A method that when invoked returns a promise that returns an `ApplicationRef` instance once resolved or an NgModule. */
36-
bootstrap?: Type<{}> | (() => Promise<ApplicationRef>);
37+
bootstrap?: Type<{}> | ((context: BootstrapContext) => Promise<ApplicationRef>);
3738

3839
/** A set of platform level providers for the current request. */
3940
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/manifest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
import type { BootstrapContext } from '@angular/platform-browser';
910
import type { SerializableRouteTreeNode } from './routes/route-tree';
1011
import { AngularBootstrap } from './utils/ng';
1112

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/ssr/test/testing-utils.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,20 @@ export function setAngularAppTestingManifest(
9191
`,
9292
},
9393
},
94-
bootstrap: async () => () => {
95-
return bootstrapApplication(rootComponent, {
96-
providers: [
97-
provideServerRendering(),
98-
provideExperimentalZonelessChangeDetection(),
99-
provideRouter(routes),
100-
provideServerRouting(serverRoutes),
101-
...extraProviders,
102-
],
103-
});
94+
bootstrap: async () => (context) => {
95+
return bootstrapApplication(
96+
rootComponent,
97+
{
98+
providers: [
99+
provideServerRendering(),
100+
provideExperimentalZonelessChangeDetection(),
101+
provideRouter(routes),
102+
provideServerRouting(serverRoutes),
103+
...extraProviders,
104+
],
105+
},
106+
context,
107+
);
104108
},
105109
});
106110
}

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
}

0 commit comments

Comments
 (0)