Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 1cdca66

Browse files
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 ddebe3d commit 1cdca66

File tree

14 files changed

+62
-36
lines changed

14 files changed

+62
-36
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';
@@ -26,14 +27,14 @@ export class CommonEngine {
2627

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

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

‎packages/angular/build/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ ts_project(
124124
"//:node_modules/@angular/compiler-cli",
125125
"//:node_modules/@angular/core",
126126
"//:node_modules/@angular/localize",
127+
"//:node_modules/@angular/platform-browser",
127128
"//:node_modules/@angular/platform-server",
128129
"//:node_modules/@angular/service-worker",
129130
"//:node_modules/@types/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';
@@ -23,7 +24,7 @@ const SSG_MARKER_REGEXP = /ng-server-context=["']\w*\|?ssg\|?\w*["']/;
2324

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

2829
/** A set of platform level providers for all requests. */
2930
providers?: StaticProvider[];
@@ -34,7 +35,7 @@ export interface CommonEngineOptions {
3435

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

3940
/** A set of platform level providers for the current request. */
4041
providers?: StaticProvider[];
@@ -197,7 +198,9 @@ async function exists(path: fs.PathLike): Promise<boolean> {
197198
}
198199
}
199200

200-
function isBootstrapFn(value: unknown): value is () => Promise<ApplicationRef> {
201+
function isBootstrapFn(
202+
value: unknown,
203+
): value is (context: BootstrapContext) => Promise<ApplicationRef> {
201204
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
202205
return typeof value === 'function' && !('ɵmod' in value);
203206
}

‎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
@@ -634,7 +634,7 @@ export async function getRoutesFromAngularRouterConfig(
634634
const moduleRef = await platformRef.bootstrapModule(bootstrap);
635635
applicationRef = moduleRef.injector.get(ApplicationRef);
636636
} else {
637-
applicationRef = await bootstrap();
637+
applicationRef = await bootstrap({ platformRef });
638638
}
639639

640640
const injector = applicationRef.injector;

‎packages/angular/ssr/src/routes/route-config.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -356,20 +356,23 @@ export function withAppShell(
356356
* when using the `bootstrapApplication` function:
357357
*
358358
* ```ts
359-
* import { bootstrapApplication } from '@angular/platform-browser';
359+
* import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';
360360
* import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';
361361
* import { AppComponent } from './app/app.component';
362362
* import { SERVER_ROUTES } from './app/app.server.routes';
363363
* import { AppShellComponent } from './app/app-shell.component';
364364
*
365-
* bootstrapApplication(AppComponent, {
366-
* providers: [
367-
* provideServerRendering(
368-
* withRoutes(SERVER_ROUTES),
369-
* withAppShell(AppShellComponent)
370-
* )
371-
* ]
372-
* });
365+
* const bootstrap = (context: BootstrapContext) =>
366+
* bootstrapApplication(AppComponent, {
367+
* providers: [
368+
* provideServerRendering(
369+
* withRoutes(SERVER_ROUTES),
370+
* withAppShell(AppShellComponent),
371+
* ),
372+
* ],
373+
* }, context);
374+
*
375+
* export default bootstrap;
373376
* ```
374377
* @see {@link withRoutes} configures server-side routing
375378
* @see {@link withAppShell} configures the application shell

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
type Type,
1515
ɵConsole,
1616
} from '@angular/core';
17+
import { BootstrapContext } from '@angular/platform-browser';
1718
import {
1819
INITIAL_CONFIG,
1920
ɵSERVER_CONTEXT as SERVER_CONTEXT,
@@ -31,7 +32,9 @@ import { joinUrlParts, stripIndexHtmlFromURL } from './url';
3132
* - A reference to an Angular component or module (`Type<unknown>`) that serves as the root of the application.
3233
* - A function that returns a `Promise<ApplicationRef>`, which resolves with the root application reference.
3334
*/
34-
export type AngularBootstrap = Type<unknown> | (() => Promise<ApplicationRef>);
35+
export type AngularBootstrap =
36+
| Type<unknown>
37+
| ((context: BootstrapContext) => Promise<ApplicationRef>);
3538

3639
/**
3740
* Renders an Angular application or module to an HTML string.
@@ -90,7 +93,7 @@ export async function renderAngular(
9093
const moduleRef = await platformRef.bootstrapModule(bootstrap);
9194
applicationRef = moduleRef.injector.get(ApplicationRef);
9295
} else {
93-
applicationRef = await bootstrap();
96+
applicationRef = await bootstrap({ platformRef });
9497
}
9598

9699
// Block until application is stable.

‎packages/angular/ssr/test/testing-utils.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,19 @@ export function setAngularAppTestingManifest(
9090
`,
9191
},
9292
},
93-
bootstrap: async () => () => {
94-
return bootstrapApplication(rootComponent, {
95-
providers: [
96-
provideZonelessChangeDetection(),
97-
provideRouter(routes),
98-
provideServerRendering(withRoutes(serverRoutes)),
99-
...extraProviders,
100-
],
101-
});
93+
bootstrap: async () => (context) => {
94+
return bootstrapApplication(
95+
rootComponent,
96+
{
97+
providers: [
98+
provideZonelessChangeDetection(),
99+
provideRouter(routes),
100+
provideServerRendering(withRoutes(serverRoutes)),
101+
...extraProviders,
102+
],
103+
},
104+
context,
105+
);
102106
},
103107
});
104108
}

‎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
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /