mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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); ``` A schematic is provided to automatically update `main.server.ts` files to pass the `BootstrapContext` to the `bootstrapApplication` call. In addition, `getPlatform()` and `destroyPlatform()` will now return `null` and be a no-op respectively when running in a server environment. (cherry picked from commit 8bf80c9d2314b4f2bcf3df83ae01552a6fc49834) PR Close #63636
141 lines
4 KiB
TypeScript
141 lines
4 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {
|
|
DOCUMENT,
|
|
PlatformLocation,
|
|
ViewportScroller,
|
|
ɵgetDOM as getDOM,
|
|
ɵNullViewportScroller as NullViewportScroller,
|
|
ɵPLATFORM_SERVER_ID as PLATFORM_SERVER_ID,
|
|
} from '@angular/common';
|
|
import {
|
|
createPlatformFactory,
|
|
Injector,
|
|
NgModule,
|
|
Optional,
|
|
PLATFORM_ID,
|
|
PLATFORM_INITIALIZER,
|
|
platformCore,
|
|
PlatformRef,
|
|
Provider,
|
|
StaticProvider,
|
|
Testability,
|
|
ɵsetDocument,
|
|
ɵTESTABILITY as TESTABILITY,
|
|
} from '@angular/core';
|
|
import {
|
|
BrowserModule,
|
|
EVENT_MANAGER_PLUGINS,
|
|
ɵBrowserDomAdapter as BrowserDomAdapter,
|
|
} from '@angular/platform-browser';
|
|
|
|
import {DominoAdapter, parseDocument} from './domino_adapter';
|
|
import {SERVER_HTTP_PROVIDERS} from './http';
|
|
import {ServerPlatformLocation} from './location';
|
|
import {enableDomEmulation, PlatformState} from './platform_state';
|
|
import {ServerEventManagerPlugin} from './server_events';
|
|
import {INITIAL_CONFIG, PlatformConfig} from './tokens';
|
|
import {TRANSFER_STATE_SERIALIZATION_PROVIDERS} from './transfer_state';
|
|
|
|
export const INTERNAL_SERVER_PLATFORM_PROVIDERS: StaticProvider[] = [
|
|
{provide: DOCUMENT, useFactory: _document, deps: [Injector]},
|
|
{provide: PLATFORM_ID, useValue: PLATFORM_SERVER_ID},
|
|
{provide: PLATFORM_INITIALIZER, useFactory: initDominoAdapter, multi: true, deps: [Injector]},
|
|
{
|
|
provide: PlatformLocation,
|
|
useClass: ServerPlatformLocation,
|
|
deps: [DOCUMENT, [Optional, INITIAL_CONFIG]],
|
|
},
|
|
{provide: PlatformState, deps: [DOCUMENT]},
|
|
];
|
|
|
|
function initDominoAdapter(injector: Injector) {
|
|
const _enableDomEmulation = enableDomEmulation(injector);
|
|
return () => {
|
|
if (_enableDomEmulation) {
|
|
DominoAdapter.makeCurrent();
|
|
} else {
|
|
BrowserDomAdapter.makeCurrent();
|
|
}
|
|
};
|
|
}
|
|
|
|
export const SERVER_RENDER_PROVIDERS: Provider[] = [
|
|
{provide: EVENT_MANAGER_PLUGINS, multi: true, useClass: ServerEventManagerPlugin},
|
|
];
|
|
|
|
export const PLATFORM_SERVER_PROVIDERS: Provider[] = [
|
|
TRANSFER_STATE_SERIALIZATION_PROVIDERS,
|
|
SERVER_RENDER_PROVIDERS,
|
|
SERVER_HTTP_PROVIDERS,
|
|
{provide: Testability, useValue: null}, // Keep for backwards-compatibility.
|
|
{provide: TESTABILITY, useValue: null},
|
|
{provide: ViewportScroller, useClass: NullViewportScroller},
|
|
];
|
|
|
|
/**
|
|
* The ng module for the server.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
@NgModule({
|
|
exports: [BrowserModule],
|
|
providers: PLATFORM_SERVER_PROVIDERS,
|
|
})
|
|
export class ServerModule {}
|
|
|
|
function _document(injector: Injector) {
|
|
const config: PlatformConfig | null = injector.get(INITIAL_CONFIG, null);
|
|
const _enableDomEmulation = enableDomEmulation(injector);
|
|
let document: Document;
|
|
if (config && config.document) {
|
|
document =
|
|
typeof config.document === 'string'
|
|
? _enableDomEmulation
|
|
? parseDocument(config.document, config.url)
|
|
: window.document
|
|
: config.document;
|
|
} else {
|
|
document = getDOM().createHtmlDocument();
|
|
}
|
|
// Tell ivy about the global document
|
|
ɵsetDocument(document);
|
|
return document;
|
|
}
|
|
|
|
/**
|
|
* Creates a server-side instance of an Angular platform.
|
|
*
|
|
* This platform should be used when performing server-side rendering of an Angular application.
|
|
* Standalone applications can be bootstrapped on the server using the `bootstrapApplication`
|
|
* function from `@angular/platform-browser`. When using `bootstrapApplication`, the `platformServer`
|
|
* should be created first and passed to the bootstrap function using the `BootstrapContext`.
|
|
*
|
|
* @publicApi
|
|
*/
|
|
export function platformServer(extraProviders?: StaticProvider[] | undefined): PlatformRef {
|
|
const noServerModeSet = typeof ngServerMode === 'undefined';
|
|
if (noServerModeSet) {
|
|
globalThis['ngServerMode'] = true;
|
|
}
|
|
|
|
const platform = createPlatformFactory(
|
|
platformCore,
|
|
'server',
|
|
INTERNAL_SERVER_PLATFORM_PROVIDERS,
|
|
)(extraProviders);
|
|
|
|
if (noServerModeSet) {
|
|
platform.onDestroy(() => {
|
|
globalThis['ngServerMode'] = undefined;
|
|
});
|
|
}
|
|
|
|
return platform;
|
|
}
|