Skip to content

Commit 33b9de3

Browse files
committed
feat(@angular/ssr): expose provideServerRendering and remove provideServerRouting
This commit introduces `provideServerRendering` as the primary function for configuring server-side rendering, replacing `provideServerRouting`. `provideServerRendering` now includes the functionality of `provideServerRouting` through the use of the `withRoutes` feature. This change consolidates server-side rendering configuration into a single, more flexible function, aligning with the evolution of Angular SSR. **Before:** ```ts import { provideServerRouting } from '@angular/ssr'; import { serverRoutes } from './app.routes'; provideServerRouting(serverRoutes); ``` **After:** ```ts import { provideServerRendering, withRoutes } from '@angular/ssr'; import { serverRoutes } from './app.routes'; provideServerRendering(withRoutes(serverRoutes)); ```
1 parent fe69a9b commit 33b9de3

File tree

11 files changed

+171
-74
lines changed

11 files changed

+171
-74
lines changed

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export enum PrerenderFallback {
2727
}
2828

2929
// @public
30-
export function provideServerRouting(routes: ServerRoute[], ...features: ServerRoutesFeature<ServerRoutesFeatureKind>[]): EnvironmentProviders;
30+
export function provideServerRendering(...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]): EnvironmentProviders;
3131

3232
// @public
3333
export enum RenderMode {
@@ -72,7 +72,10 @@ export interface ServerRouteServer extends ServerRouteCommon {
7272
}
7373

7474
// @public
75-
export function withAppShell(component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>)): ServerRoutesFeature<ServerRoutesFeatureKind.AppShell>;
75+
export function withAppShell(component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>)): ServerRenderingFeature<ServerRenderingFeatureKind.AppShell>;
76+
77+
// @public
78+
export function withRoutes(routes: ServerRoute[]): ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes>;
7679

7780
// (No @packageDocumentation comment for this package)
7881

packages/angular/ssr/public_api.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ export { createRequestHandler, type RequestHandlerFunction } from './src/handler
1414
export {
1515
PrerenderFallback,
1616
type ServerRoute,
17-
provideServerRouting,
17+
provideServerRendering,
1818
withAppShell,
19+
withRoutes,
1920
RenderMode,
2021
type ServerRouteClient,
2122
type ServerRoutePrerender,

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

+152-47
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ import {
1111
InjectionToken,
1212
Provider,
1313
Type,
14+
inject,
1415
makeEnvironmentProviders,
16+
provideEnvironmentInitializer,
1517
} from '@angular/core';
18+
import { provideServerRendering as provideServerRenderingPlatformServer } from '@angular/platform-server';
1619
import { type DefaultExport, ROUTES, type Route } from '@angular/router';
1720

1821
/**
@@ -22,25 +25,26 @@ import { type DefaultExport, ROUTES, type Route } from '@angular/router';
2225
const APP_SHELL_ROUTE = 'ng-app-shell';
2326

2427
/**
25-
* Identifies a particular kind of `ServerRoutesFeatureKind`.
26-
* @see {@link ServerRoutesFeature}
28+
* Identifies a particular kind of `ServerRenderingFeatureKind`.
29+
* @see {@link ServerRenderingFeature}
2730
*/
28-
enum ServerRoutesFeatureKind {
31+
enum ServerRenderingFeatureKind {
2932
AppShell,
33+
ServerRoutes,
3034
}
3135

3236
/**
3337
* Helper type to represent a server routes feature.
34-
* @see {@link ServerRoutesFeatureKind}
38+
* @see {@link ServerRenderingFeatureKind}
3539
*/
36-
interface ServerRoutesFeature<FeatureKind extends ServerRoutesFeatureKind> {
40+
interface ServerRenderingFeature<FeatureKind extends ServerRenderingFeatureKind> {
3741
ɵkind: FeatureKind;
38-
ɵproviders: Provider[];
42+
ɵproviders: (Provider | EnvironmentProviders)[];
3943
}
4044

4145
/**
4246
* Different rendering modes for server routes.
43-
* @see {@link provideServerRouting}
47+
* @see {@link withRoutes}
4448
* @see {@link ServerRoute}
4549
*/
4650
export enum RenderMode {
@@ -171,7 +175,7 @@ export interface ServerRouteServer extends ServerRouteCommon {
171175

172176
/**
173177
* Server route configuration.
174-
* @see {@link provideServerRouting}
178+
* @see {@link withRoutes}
175179
*/
176180
export type ServerRoute =
177181
| ServerRouteClient
@@ -200,62 +204,103 @@ export interface ServerRoutesConfig {
200204
export const SERVER_ROUTES_CONFIG = new InjectionToken<ServerRoutesConfig>('SERVER_ROUTES_CONFIG');
201205

202206
/**
203-
* Sets up the necessary providers for configuring server routes.
204-
* This function accepts an array of server routes and optional configuration
205-
* options, returning an `EnvironmentProviders` object that encapsulates
206-
* the server routes and configuration settings.
207+
* Configures server-side routing for the application.
207208
*
208-
* @param routes - An array of server routes to be provided.
209-
* @param features - (Optional) server routes features.
210-
* @returns An `EnvironmentProviders` instance with the server routes configuration.
209+
* This function registers an array of `ServerRoute` definitions, enabling server-side rendering
210+
* for specific URL paths. These routes are used to pre-render content on the server, improving
211+
* initial load performance and SEO.
211212
*
213+
* @param routes - An array of `ServerRoute` objects, each defining a server-rendered route.
214+
* @returns A `ServerRenderingFeature` object configuring server-side routes.
215+
*
216+
* @example
217+
* ```ts
218+
* import { provideServerRendering, withRoutes, ServerRoute, RenderMode } from '@angular/ssr';
219+
*
220+
* const serverRoutes: ServerRoute[] = [
221+
* {
222+
* route: '', // This renders the "/" route on the client (CSR)
223+
* renderMode: RenderMode.Client,
224+
* },
225+
* {
226+
* route: 'about', // This page is static, so we prerender it (SSG)
227+
* renderMode: RenderMode.Prerender,
228+
* },
229+
* {
230+
* route: 'profile', // This page requires user-specific data, so we use SSR
231+
* renderMode: RenderMode.Server,
232+
* },
233+
* {
234+
* route: '**', // All other routes will be rendered on the server (SSR)
235+
* renderMode: RenderMode.Server,
236+
* },
237+
* ];
238+
*
239+
* provideServerRendering(withRoutes(serverRoutes));
240+
* ```
241+
*
242+
* @see {@link provideServerRendering}
212243
* @see {@link ServerRoute}
213-
* @see {@link withAppShell}
214244
*/
215-
export function provideServerRouting(
245+
export function withRoutes(
216246
routes: ServerRoute[],
217-
...features: ServerRoutesFeature<ServerRoutesFeatureKind>[]
218-
): EnvironmentProviders {
247+
): ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes> {
219248
const config: ServerRoutesConfig = { routes };
220-
const hasAppShell = features.some((f) => f.ɵkind === ServerRoutesFeatureKind.AppShell);
221-
if (hasAppShell) {
222-
config.appShellRoute = APP_SHELL_ROUTE;
223-
}
224-
225-
const providers: Provider[] = [
226-
{
227-
provide: SERVER_ROUTES_CONFIG,
228-
useValue: config,
229-
},
230-
];
231-
232-
for (const feature of features) {
233-
providers.push(...feature.ɵproviders);
234-
}
235249

236-
return makeEnvironmentProviders(providers);
250+
return {
251+
ɵkind: ServerRenderingFeatureKind.ServerRoutes,
252+
ɵproviders: [
253+
{
254+
provide: SERVER_ROUTES_CONFIG,
255+
useValue: config,
256+
},
257+
],
258+
};
237259
}
238260

239261
/**
240-
* Configures the app shell route with the provided component.
262+
* Configures the shell of the application.
263+
*
264+
* The app shell is a minimal, static HTML page that is served immediately, while the
265+
* full Angular application loads in the background. This improves perceived performance
266+
* by providing instant feedback to the user.
267+
*
268+
* This function configures the app shell route, which serves the provided component for
269+
* requests that do not match any defined server routes.
241270
*
242-
* The app shell serves as the main entry point for the application and is commonly used
243-
* to enable server-side rendering (SSR) of the application shell. It handles requests
244-
* that do not match any specific server route, providing a fallback mechanism and improving
245-
* perceived performance during navigation.
271+
* @param component - The Angular component to render for the app shell. Can be a direct
272+
* component type or a dynamic import function.
273+
* @returns A `ServerRenderingFeature` object configuring the app shell.
246274
*
247-
* This configuration is particularly useful in applications leveraging Progressive Web App (PWA)
248-
* patterns, such as service workers, to deliver a seamless user experience.
275+
* @example
276+
* ```ts
277+
* import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';
278+
* import { AppShellComponent } from './app-shell.component';
249279
*
250-
* @param component The Angular component to render for the app shell route.
251-
* @returns A server routes feature configuration for the app shell.
280+
* provideServerRendering(
281+
* withRoutes(serverRoutes),
282+
* withAppShell(AppShellComponent)
283+
* );
284+
* ```
252285
*
253-
* @see {@link provideServerRouting}
286+
* @example
287+
* ```ts
288+
* import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';
289+
*
290+
* provideServerRendering(
291+
* withRoutes(serverRoutes),
292+
* withAppShell(() =>
293+
* import('./app-shell.component').then((m) => m.AppShellComponent)
294+
* )
295+
* );
296+
* ```
297+
*
298+
* @see {@link provideServerRendering}
254299
* @see {@link https://angular.dev/ecosystem/service-workers/app-shell | App shell pattern on Angular.dev}
255300
*/
256301
export function withAppShell(
257302
component: Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>),
258-
): ServerRoutesFeature<ServerRoutesFeatureKind.AppShell> {
303+
): ServerRenderingFeature<ServerRenderingFeatureKind.AppShell> {
259304
const routeConfig: Route = {
260305
path: APP_SHELL_ROUTE,
261306
};
@@ -267,13 +312,73 @@ export function withAppShell(
267312
}
268313

269314
return {
270-
ɵkind: ServerRoutesFeatureKind.AppShell,
315+
ɵkind: ServerRenderingFeatureKind.AppShell,
271316
ɵproviders: [
272317
{
273318
provide: ROUTES,
274319
useValue: routeConfig,
275320
multi: true,
276321
},
322+
provideEnvironmentInitializer(() => {
323+
const config = inject(SERVER_ROUTES_CONFIG);
324+
config.appShellRoute = APP_SHELL_ROUTE;
325+
}),
277326
],
278327
};
279328
}
329+
330+
/**
331+
* Configures server-side rendering for an Angular application.
332+
*
333+
* This function sets up the necessary providers for server-side rendering, including
334+
* support for server routes and app shell. It combines features configured using
335+
* `withRoutes` and `withAppShell` to provide a comprehensive server-side rendering setup.
336+
*
337+
* @param features - Optional features to configure additional server rendering behaviors.
338+
* @returns An `EnvironmentProviders` instance with the server-side rendering configuration.
339+
*
340+
* @example
341+
* Basic example of how you can enable server-side rendering in your application
342+
* when using the `bootstrapApplication` function:
343+
*
344+
* ```ts
345+
* import { bootstrapApplication } from '@angular/platform-browser';
346+
* import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';
347+
* import { AppComponent } from './app/app.component';
348+
* import { SERVER_ROUTES } from './app/app.server.routes';
349+
* import { AppShellComponent } from './app/app-shell.component';
350+
*
351+
* bootstrapApplication(AppComponent, {
352+
* providers: [
353+
* provideServerRendering(
354+
* withRoutes(SERVER_ROUTES),
355+
* withAppShell(AppShellComponent)
356+
* )
357+
* ]
358+
* });
359+
* ```
360+
* @see {@link withRoutes} configures server-side routing
361+
* @see {@link withAppShell} configures the application shell
362+
*/
363+
export function provideServerRendering(
364+
...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]
365+
): EnvironmentProviders {
366+
let hasAppShell = false;
367+
let hasServerRoutes = false;
368+
const providers: (Provider | EnvironmentProviders)[] = [provideServerRenderingPlatformServer()];
369+
370+
for (const { ɵkind, ɵproviders } of features) {
371+
hasAppShell ||= ɵkind === ServerRenderingFeatureKind.AppShell;
372+
hasServerRoutes ||= ɵkind === ServerRenderingFeatureKind.ServerRoutes;
373+
providers.push(...ɵproviders);
374+
}
375+
376+
if (!hasServerRoutes && hasAppShell) {
377+
throw new Error(
378+
`Configuration error: found 'withAppShell()' without 'withRoutes()' in the same call to 'provideServerRendering()'.` +
379+
`The 'withAppShell()' function requires 'withRoutes()' to be used.`,
380+
);
381+
}
382+
383+
return makeEnvironmentProviders(providers);
384+
}

packages/angular/ssr/test/BUILD.bazel

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ ts_project(
1313
"//:node_modules/@angular/compiler",
1414
"//:node_modules/@angular/core",
1515
"//:node_modules/@angular/platform-browser",
16-
"//:node_modules/@angular/platform-server",
1716
"//:node_modules/@angular/router",
1817
"//:node_modules/@types/node",
1918
"//packages/angular/ssr",

packages/angular/ssr/test/npm_package/package_spec.ts

-6
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@ const CRITTERS_ACTUAL_LICENSE_FILE_PATH = join(
2424
'third_party/beasties/THIRD_PARTY_LICENSES.txt',
2525
);
2626

27-
/**
28-
* Path to the golden reference license file for the Beasties library.
29-
* This file is used as a reference for comparison and is located in the same directory as this script.
30-
*/
31-
const CRITTERS_GOLDEN_LICENSE_FILE_PATH = join(__dirname, 'THIRD_PARTY_LICENSES.txt.golden');
32-
3327
describe('NPM Package Tests', () => {
3428
it('should not include the contents of third_party/beasties/index.js in the FESM bundle', async () => {
3529
const fesmFilePath = join(ANGULAR_SSR_PACKAGE_PATH, 'fesm2022/ssr.mjs');

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ import {
1414
provideExperimentalZonelessChangeDetection,
1515
} from '@angular/core';
1616
import { bootstrapApplication } from '@angular/platform-browser';
17-
import { provideServerRendering } from '@angular/platform-server';
1817
import { RouterOutlet, Routes, provideRouter } from '@angular/router';
1918
import { destroyAngularServerApp } from '../src/app';
2019
import { ServerAsset, setAngularAppManifest } from '../src/manifest';
21-
import { ServerRoute, provideServerRouting } from '../src/routes/route-config';
20+
import { ServerRoute, provideServerRendering, withRoutes } from '../src/routes/route-config';
2221

2322
@Component({
2423
standalone: true,
@@ -94,10 +93,9 @@ export function setAngularAppTestingManifest(
9493
bootstrap: async () => () => {
9594
return bootstrapApplication(rootComponent, {
9695
providers: [
97-
provideServerRendering(),
9896
provideExperimentalZonelessChangeDetection(),
9997
provideRouter(routes),
100-
provideServerRouting(serverRoutes),
98+
provideServerRendering(withRoutes(serverRoutes)),
10199
...extraProviders,
102100
],
103101
});

packages/schematics/angular/app-shell/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,12 @@ function addServerRoutingConfig(options: AppShellOptions, isStandalone: boolean)
300300
/** max */ undefined,
301301
/** recursive */ true,
302302
).find(
303-
(n) => ts.isIdentifier(n.expression) && n.expression.getText() === 'provideServerRouting',
303+
(n) => ts.isIdentifier(n.expression) && n.expression.getText() === 'provideServerRendering',
304304
);
305305

306306
if (!functionCall) {
307307
throw new SchematicsException(
308-
`Cannot find the "provideServerRouting" function call in "${configFilePath}".`,
308+
`Cannot find the "provideServerRendering" function call in "${configFilePath}".`,
309309
);
310310
}
311311

packages/schematics/angular/app-shell/index_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,11 @@ describe('App Shell Schematic', () => {
123123
expect(content).toMatch(/app-shell/);
124124
});
125125

126-
it(`should update the 'provideServerRouting' call to include 'withAppShell'`, async () => {
126+
it(`should update the 'provideServerRendering' call to include 'withAppShell'`, async () => {
127127
const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree);
128128
const content = tree.readContent('/projects/bar/src/app/app.config.server.ts');
129129
expect(tags.oneLine`${content}`).toContain(
130-
tags.oneLine`provideServerRouting(serverRoutes, withAppShell(AppShell))`,
130+
tags.oneLine`provideServerRendering(withRoutes(serverRoutes), withAppShell(AppShell))`,
131131
);
132132
});
133133

Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { NgModule } from '@angular/core';
2-
import { ServerModule } from '@angular/platform-server';
3-
import { provideServerRouting } from '@angular/ssr';
2+
import { provideServerRendering, withRoutes } from '@angular/ssr';
43
import { App } from './app';
54
import { AppModule } from './app.module';
65
import { serverRoutes } from './app.routes.server';
76

87
@NgModule({
9-
imports: [AppModule, ServerModule],
10-
providers: [provideServerRouting(serverRoutes)],
8+
imports: [AppModule],
9+
providers: [provideServerRendering(withRoutes(serverRoutes))],
1110
bootstrap: [App],
1211
})
1312
export class AppServerModule {}

0 commit comments

Comments
 (0)