@angular/platform-server
Version:
Angular - library for using Angular in Node.js
308 lines (301 loc) • 12.7 kB
JavaScript
/**
* @license Angular v20.0.0
* (c) 2010-2025 Google LLC. https://angular.io/
* License: MIT
*/
import { PLATFORM_SERVER_PROVIDERS, PlatformState, BEFORE_APP_SERIALIZED, platformServer, INITIAL_CONFIG, createScript } from './server-_br4z1cG.mjs';
export { ServerModule, DominoAdapter as ɵDominoAdapter, ENABLE_DOM_EMULATION as ɵENABLE_DOM_EMULATION, INTERNAL_SERVER_PLATFORM_PROVIDERS as ɵINTERNAL_SERVER_PLATFORM_PROVIDERS, SERVER_RENDER_PROVIDERS as ɵSERVER_RENDER_PROVIDERS } from './server-_br4z1cG.mjs';
import { makeEnvironmentProviders, InjectionToken, ɵstartMeasuring as _startMeasuring, ɵstopMeasuring as _stopMeasuring, ApplicationRef, ɵIS_HYDRATION_DOM_REUSE_ENABLED as _IS_HYDRATION_DOM_REUSE_ENABLED, ɵannotateForHydration as _annotateForHydration, CSP_NONCE, APP_ID, Renderer2, ɵSSR_CONTENT_INTEGRITY_MARKER as _SSR_CONTENT_INTEGRITY_MARKER, Version } from '@angular/core';
import '@angular/common';
import '@angular/platform-browser';
import '@angular/common/http';
import 'rxjs';
/**
* Sets up providers necessary to enable server rendering functionality for the application.
*
* @usageNotes
*
* Basic example of how you can add server support to your application:
* ```ts
* bootstrapApplication(AppComponent, {
* providers: [provideServerRendering()]
* });
* ```
*
* @publicApi
* @returns A set of providers to setup the server.
*/
function provideServerRendering() {
if (typeof ngServerMode === 'undefined') {
globalThis['ngServerMode'] = true;
}
return makeEnvironmentProviders([...PLATFORM_SERVER_PROVIDERS]);
}
/**
* Event dispatch (JSAction) script is inlined into the HTML by the build
* process to avoid extra blocking request on a page. The script looks like this:
* ```html
* <script type="text/javascript" id="ng-event-dispatch-contract">...</script>
* ```
* This const represents the "id" attribute value.
*/
const EVENT_DISPATCH_SCRIPT_ID = 'ng-event-dispatch-contract';
/**
* Creates an instance of a server platform (with or without JIT compiler support
* depending on the `ngJitMode` global const value), using provided options.
*/
function createServerPlatform(options) {
const extraProviders = options.platformProviders ?? [];
const measuringLabel = 'createServerPlatform';
_startMeasuring(measuringLabel);
const platform = platformServer([
{ provide: INITIAL_CONFIG, useValue: { document: options.document, url: options.url } },
extraProviders,
]);
_stopMeasuring(measuringLabel);
return platform;
}
/**
* Finds and returns inlined event dispatch script if it exists.
* See the `EVENT_DISPATCH_SCRIPT_ID` const docs for additional info.
*/
function findEventDispatchScript(doc) {
return doc.getElementById(EVENT_DISPATCH_SCRIPT_ID);
}
/**
* Removes inlined event dispatch script if it exists.
* See the `EVENT_DISPATCH_SCRIPT_ID` const docs for additional info.
*/
function removeEventDispatchScript(doc) {
findEventDispatchScript(doc)?.remove();
}
/**
* Annotate nodes for hydration and remove event dispatch script when not needed.
*/
function prepareForHydration(platformState, applicationRef) {
const measuringLabel = 'prepareForHydration';
_startMeasuring(measuringLabel);
const environmentInjector = applicationRef.injector;
const doc = platformState.getDocument();
if (!environmentInjector.get(_IS_HYDRATION_DOM_REUSE_ENABLED, false)) {
// Hydration is diabled, remove inlined event dispatch script.
// (which was injected by the build process) from the HTML.
removeEventDispatchScript(doc);
return;
}
appendSsrContentIntegrityMarker(doc);
const eventTypesToReplay = _annotateForHydration(applicationRef, doc);
if (eventTypesToReplay.regular.size || eventTypesToReplay.capture.size) {
insertEventRecordScript(environmentInjector.get(APP_ID), doc, eventTypesToReplay, environmentInjector.get(CSP_NONCE, null));
}
else {
// No events to replay, we should remove inlined event dispatch script
// (which was injected by the build process) from the HTML.
removeEventDispatchScript(doc);
}
_stopMeasuring(measuringLabel);
}
/**
* Creates a marker comment node and append it into the `<body>`.
* Some CDNs have mechanisms to remove all comment node from HTML.
* This behaviour breaks hydration, so we'll detect on the client side if this
* marker comment is still available or else throw an error
*/
function appendSsrContentIntegrityMarker(doc) {
// Adding a ng hydration marker comment
const comment = doc.createComment(_SSR_CONTENT_INTEGRITY_MARKER);
doc.body.firstChild
? doc.body.insertBefore(comment, doc.body.firstChild)
: doc.body.append(comment);
}
/**
* Adds the `ng-server-context` attribute to host elements of all bootstrapped components
* within a given application.
*/
function appendServerContextInfo(applicationRef) {
const injector = applicationRef.injector;
let serverContext = sanitizeServerContext(injector.get(SERVER_CONTEXT, DEFAULT_SERVER_CONTEXT));
applicationRef.components.forEach((componentRef) => {
const renderer = componentRef.injector.get(Renderer2);
const element = componentRef.location.nativeElement;
if (element) {
renderer.setAttribute(element, 'ng-server-context', serverContext);
}
});
}
function insertEventRecordScript(appId, doc, eventTypesToReplay, nonce) {
const measuringLabel = 'insertEventRecordScript';
_startMeasuring(measuringLabel);
const { regular, capture } = eventTypesToReplay;
const eventDispatchScript = findEventDispatchScript(doc);
// Note: this is only true when build with the CLI tooling, which inserts the script in the HTML
if (eventDispatchScript) {
// This is defined in packages/core/primitives/event-dispatch/contract_binary.ts
const replayScriptContents = `window.__jsaction_bootstrap(` +
`document.body,` +
`"${appId}",` +
`${JSON.stringify(Array.from(regular))},` +
`${JSON.stringify(Array.from(capture))}` +
`);`;
const replayScript = createScript(doc, replayScriptContents, nonce);
// Insert replay script right after inlined event dispatch script, since it
// relies on `__jsaction_bootstrap` to be defined in the global scope.
eventDispatchScript.after(replayScript);
}
_stopMeasuring(measuringLabel);
}
/**
* Renders an Angular application to a string.
*
* @private
*
* @param platformRef - Reference to the Angular platform.
* @param applicationRef - Reference to the Angular application.
* @returns A promise that resolves to the rendered string.
*/
async function renderInternal(platformRef, applicationRef) {
const platformState = platformRef.injector.get(PlatformState);
prepareForHydration(platformState, applicationRef);
appendServerContextInfo(applicationRef);
// Run any BEFORE_APP_SERIALIZED callbacks just before rendering to string.
const environmentInjector = applicationRef.injector;
const callbacks = environmentInjector.get(BEFORE_APP_SERIALIZED, null);
if (callbacks) {
const asyncCallbacks = [];
for (const callback of callbacks) {
try {
const callbackResult = callback();
if (callbackResult) {
asyncCallbacks.push(callbackResult);
}
}
catch (e) {
// Ignore exceptions.
console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', e);
}
}
if (asyncCallbacks.length) {
for (const result of await Promise.allSettled(asyncCallbacks)) {
if (result.status === 'rejected') {
console.warn('Ignoring BEFORE_APP_SERIALIZED Exception: ', result.reason);
}
}
}
}
return platformState.renderToString();
}
/**
* Destroy the application in a macrotask, this allows pending promises to be settled and errors
* to be surfaced to the users.
*/
function asyncDestroyPlatform(platformRef) {
return new Promise((resolve) => {
setTimeout(() => {
platformRef.destroy();
resolve();
}, 0);
});
}
/**
* Specifies the value that should be used if no server context value has been provided.
*/
const DEFAULT_SERVER_CONTEXT = 'other';
/**
* An internal token that allows providing extra information about the server context
* (e.g. whether SSR or SSG was used). The value is a string and characters other
* than [a-zA-Z0-9\-] are removed. See the default value in `DEFAULT_SERVER_CONTEXT` const.
*/
const SERVER_CONTEXT = new InjectionToken('SERVER_CONTEXT');
/**
* Sanitizes provided server context:
* - removes all characters other than a-z, A-Z, 0-9 and `-`
* - returns `other` if nothing is provided or the string is empty after sanitization
*/
function sanitizeServerContext(serverContext) {
const context = serverContext.replace(/[^a-zA-Z0-9\-]/g, '');
return context.length > 0 ? context : DEFAULT_SERVER_CONTEXT;
}
/**
* Bootstraps an application using provided NgModule and serializes the page content to string.
*
* @param moduleType A reference to an NgModule that should be used for bootstrap.
* @param options Additional configuration for the render operation:
* - `document` - the document of the page to render, either as an HTML string or
* as a reference to the `document` instance.
* - `url` - the URL for the current render request.
* - `extraProviders` - set of platform level providers for the current render request.
*
* @publicApi
*/
async function renderModule(moduleType, options) {
const { document, url, extraProviders: platformProviders } = options;
const platformRef = createServerPlatform({ document, url, platformProviders });
try {
const moduleRef = await platformRef.bootstrapModule(moduleType);
const applicationRef = moduleRef.injector.get(ApplicationRef);
const measuringLabel = 'whenStable';
_startMeasuring(measuringLabel);
// Block until application is stable.
await applicationRef.whenStable();
_stopMeasuring(measuringLabel);
return await renderInternal(platformRef, applicationRef);
}
finally {
await asyncDestroyPlatform(platformRef);
}
}
/**
* Bootstraps an instance of an Angular application and renders it to a string.
* ```ts
* const bootstrap = () => bootstrapApplication(RootComponent, appConfig);
* const output: string = await renderApplication(bootstrap);
* ```
*
* @param bootstrap A method that when invoked returns a promise that returns an `ApplicationRef`
* instance once resolved.
* @param options Additional configuration for the render operation:
* - `document` - the document of the page to render, either as an HTML string or
* as a reference to the `document` instance.
* - `url` - the URL for the current render request.
* - `platformProviders` - the platform level providers for the current render request.
*
* @returns A Promise, that returns serialized (to a string) rendered page, once resolved.
*
* @publicApi
*/
async function renderApplication(bootstrap, options) {
const renderAppLabel = 'renderApplication';
const bootstrapLabel = 'bootstrap';
const _renderLabel = '_render';
_startMeasuring(renderAppLabel);
const platformRef = createServerPlatform(options);
try {
_startMeasuring(bootstrapLabel);
const applicationRef = await bootstrap();
_stopMeasuring(bootstrapLabel);
_startMeasuring(_renderLabel);
const measuringLabel = 'whenStable';
_startMeasuring(measuringLabel);
// Block until application is stable.
await applicationRef.whenStable();
_stopMeasuring(measuringLabel);
const rendered = await renderInternal(platformRef, applicationRef);
_stopMeasuring(_renderLabel);
return rendered;
}
finally {
await asyncDestroyPlatform(platformRef);
_stopMeasuring(renderAppLabel);
}
}
/**
* @module
* @description
* Entry point for all public APIs of the platform-server package.
*/
/**
* @publicApi
*/
const VERSION = new Version('20.0.0');
export { BEFORE_APP_SERIALIZED, INITIAL_CONFIG, PlatformState, VERSION, platformServer, provideServerRendering, renderApplication, renderModule, SERVER_CONTEXT as ɵSERVER_CONTEXT, renderInternal as ɵrenderInternal };
//# sourceMappingURL=platform-server.mjs.map