From b2e2be052f230af92b67992945ffc2e638058ca7 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Fri, 8 Nov 2024 18:27:37 +0000 Subject: [PATCH] refactor(@angular/ssr): remove `RenderMode.AppShell` in favor of new configuration option This commit removes the `RenderMode.AppShell` option. Instead, a new configuration parameter, `{ appShellRoute: 'shell' }`, is introduced to the `provideServerRoutesConfig` method. ```ts provideServerRoutesConfig(serverRoutes, { appShellRoute: 'shell' }) ``` --- goldens/public-api/angular/ssr/index.api.md | 21 +++-- .../tests/options/app-shell_spec.ts | 2 - .../src/utils/server-rendering/models.ts | 8 +- .../src/utils/server-rendering/prerender.ts | 77 ++++++++++--------- .../routes-extractor-worker.ts | 3 +- packages/angular/ssr/public_api.ts | 2 +- packages/angular/ssr/src/app.ts | 19 +++-- packages/angular/ssr/src/routes/ng-routes.ts | 46 ++++++----- .../angular/ssr/src/routes/route-config.ts | 63 ++++++++++----- .../angular/ssr/test/routes/ng-routes_spec.ts | 22 ------ .../src/builders/app-shell/app-shell_spec.ts | 2 - .../schematics/angular/app-shell/index.ts | 24 +++++- .../angular/app-shell/index_spec.ts | 15 ++-- .../server-routes-output-mode-server.ts | 16 ++-- 14 files changed, 174 insertions(+), 146 deletions(-) diff --git a/goldens/public-api/angular/ssr/index.api.md b/goldens/public-api/angular/ssr/index.api.md index b31723be95..57487bea54 100644 --- a/goldens/public-api/angular/ssr/index.api.md +++ b/goldens/public-api/angular/ssr/index.api.md @@ -24,26 +24,20 @@ export enum PrerenderFallback { } // @public -export function provideServerRoutesConfig(routes: ServerRoute[]): EnvironmentProviders; +export function provideServerRoutesConfig(routes: ServerRoute[], options?: ServerRoutesConfigOptions): EnvironmentProviders; // @public export enum RenderMode { - AppShell = 0, - Client = 2, - Prerender = 3, - Server = 1 + Client = 1, + Prerender = 2, + Server = 0 } // @public export type RequestHandlerFunction = (request: Request) => Promise | null | Response; // @public -export type ServerRoute = ServerRouteAppShell | ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer; - -// @public -export interface ServerRouteAppShell extends Omit { - renderMode: RenderMode.AppShell; -} +export type ServerRoute = ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer; // @public export interface ServerRouteClient extends ServerRouteCommon { @@ -69,6 +63,11 @@ export interface ServerRoutePrerenderWithParams extends Omit Promise[]>; } +// @public +export interface ServerRoutesConfigOptions { + appShellRoute?: string; +} + // @public export interface ServerRouteServer extends ServerRouteCommon { renderMode: RenderMode.Server; diff --git a/packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts b/packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts index afcc67e189..a946357af4 100644 --- a/packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts +++ b/packages/angular/build/src/builders/application/tests/options/app-shell_spec.ts @@ -117,8 +117,6 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { harness.expectFile('dist/browser/main.js').toExist(); const indexFileContent = harness.expectFile('dist/browser/index.html').content; indexFileContent.toContain('app-shell works!'); - // TODO(alanagius): enable once integration of routes in complete. - // indexFileContent.toContain('ng-server-context="app-shell"'); }); it('critical CSS is inlined', async () => { diff --git a/packages/angular/build/src/utils/server-rendering/models.ts b/packages/angular/build/src/utils/server-rendering/models.ts index bafcaa6b32..9a9020d2db 100644 --- a/packages/angular/build/src/utils/server-rendering/models.ts +++ b/packages/angular/build/src/utils/server-rendering/models.ts @@ -23,6 +23,7 @@ export type WritableSerializableRouteTreeNode = Writeable = { - AppShell: 0, - Server: 1, - Client: 2, - Prerender: 3, + Server: 0, + Client: 1, + Prerender: 2, }; diff --git a/packages/angular/build/src/utils/server-rendering/prerender.ts b/packages/angular/build/src/utils/server-rendering/prerender.ts index fb7f847366..6bee2c6a43 100644 --- a/packages/angular/build/src/utils/server-rendering/prerender.ts +++ b/packages/angular/build/src/utils/server-rendering/prerender.ts @@ -97,24 +97,26 @@ export async function prerenderPages( } // Get routes to prerender - const { errors: extractionErrors, serializedRouteTree: serializableRouteTreeNode } = - await getAllRoutes( - workspaceRoot, - baseHref, - outputFilesForWorker, - assetsReversed, - appShellOptions, - prerenderOptions, - sourcemap, - outputMode, - ).catch((err) => { - return { - errors: [ - `An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`, - ], - serializedRouteTree: [], - }; - }); + const { + errors: extractionErrors, + serializedRouteTree: serializableRouteTreeNode, + appShellRoute, + } = await getAllRoutes( + workspaceRoot, + baseHref, + outputFilesForWorker, + assetsReversed, + appShellOptions, + prerenderOptions, + sourcemap, + outputMode, + ).catch((err) => { + return { + errors: [`An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err}`], + serializedRouteTree: [], + appShellRoute: undefined, + }; + }); errors.push(...extractionErrors); @@ -133,7 +135,6 @@ export async function prerenderPages( switch (metadata.renderMode) { case undefined: /* Legacy building mode */ case RouteRenderMode.Prerender: - case RouteRenderMode.AppShell: serializableRouteTreeNodeForPrerender.push(metadata); break; case RouteRenderMode.Server: @@ -166,6 +167,7 @@ export async function prerenderPages( assetsReversed, appShellOptions, outputMode, + appShellRoute ?? appShellOptions?.route, ); errors.push(...renderingErrors); @@ -188,6 +190,7 @@ async function renderPages( assetFilesForWorker: Record, appShellOptions: AppShellOptions | undefined, outputMode: OutputMode | undefined, + appShellRoute: string | undefined, ): Promise<{ output: PrerenderOutput; errors: string[]; @@ -215,7 +218,7 @@ async function renderPages( try { const renderingPromises: Promise[] = []; - const appShellRoute = appShellOptions && addLeadingSlash(appShellOptions.route); + const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute); const baseHrefWithLeadingSlash = addLeadingSlash(baseHref); for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) { @@ -232,16 +235,14 @@ async function renderPages( continue; } - const isAppShellRoute = - renderMode === RouteRenderMode.AppShell || - // Legacy handling - (renderMode === undefined && appShellRoute === routeWithoutBaseHref); - - const render: Promise = renderWorker.run({ url: route, isAppShellRoute }); + const render: Promise = renderWorker.run({ url: route }); const renderResult: Promise = render .then((content) => { if (content !== null) { - output[outPath] = { content, appShellRoute: isAppShellRoute }; + output[outPath] = { + content, + appShellRoute: appShellRouteWithLeadingSlash === routeWithoutBaseHref, + }; } }) .catch((err) => { @@ -274,14 +275,21 @@ async function getAllRoutes( prerenderOptions: PrerenderOptions | undefined, sourcemap: boolean, outputMode: OutputMode | undefined, -): Promise<{ serializedRouteTree: SerializableRouteTreeNode; errors: string[] }> { +): Promise<{ + serializedRouteTree: SerializableRouteTreeNode; + appShellRoute?: string; + errors: string[]; +}> { const { routesFile, discoverRoutes } = prerenderOptions ?? {}; const routes: WritableSerializableRouteTreeNode = []; + let appShellRoute: string | undefined; if (appShellOptions) { + appShellRoute = urlJoin(baseHref, appShellOptions.route); + routes.push({ - renderMode: RouteRenderMode.AppShell, - route: urlJoin(baseHref, appShellOptions.route), + renderMode: RouteRenderMode.Prerender, + route: appShellRoute, }); } @@ -296,7 +304,7 @@ async function getAllRoutes( } if (!discoverRoutes) { - return { errors: [], serializedRouteTree: routes }; + return { errors: [], appShellRoute, serializedRouteTree: routes }; } const workerExecArgv = [IMPORT_EXEC_ARGV]; @@ -319,12 +327,11 @@ async function getAllRoutes( }); try { - const { serializedRouteTree, errors }: RoutersExtractorWorkerResult = await renderWorker.run( - {}, - ); + const { serializedRouteTree, appShellRoute, errors }: RoutersExtractorWorkerResult = + await renderWorker.run({}); if (!routes.length) { - return { errors, serializedRouteTree }; + return { errors, appShellRoute, serializedRouteTree }; } // Merge the routing trees diff --git a/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts b/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts index 34dcfd529b..5ea3ba5b20 100644 --- a/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts +++ b/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts @@ -33,7 +33,7 @@ async function extractRoutes(): Promise { const { ɵextractRoutesAndCreateRouteTree: extractRoutesAndCreateRouteTree } = await loadEsmModuleFromMemory('./main.server.mjs'); - const { routeTree, errors } = await extractRoutesAndCreateRouteTree( + const { routeTree, appShellRoute, errors } = await extractRoutesAndCreateRouteTree( serverURL, undefined /** manifest */, true /** invokeGetPrerenderParams */, @@ -42,6 +42,7 @@ async function extractRoutes(): Promise { return { errors, + appShellRoute, serializedRouteTree: routeTree.toObject(), }; } diff --git a/packages/angular/ssr/public_api.ts b/packages/angular/ssr/public_api.ts index a8a707e13f..dc3a6662e4 100644 --- a/packages/angular/ssr/public_api.ts +++ b/packages/angular/ssr/public_api.ts @@ -14,9 +14,9 @@ export { createRequestHandler, type RequestHandlerFunction } from './src/handler export { type PrerenderFallback, type ServerRoute, + type ServerRoutesConfigOptions, provideServerRoutesConfig, RenderMode, - type ServerRouteAppShell, type ServerRouteClient, type ServerRoutePrerender, type ServerRoutePrerenderWithParams, diff --git a/packages/angular/ssr/src/app.ts b/packages/angular/ssr/src/app.ts index 5c77e33d72..7c49e34814 100644 --- a/packages/angular/ssr/src/app.ts +++ b/packages/angular/ssr/src/app.ts @@ -35,13 +35,11 @@ const MAX_INLINE_CSS_CACHE_ENTRIES = 50; * * - `RenderMode.Prerender` maps to `'ssg'` (Static Site Generation). * - `RenderMode.Server` maps to `'ssr'` (Server-Side Rendering). - * - `RenderMode.AppShell` maps to `'app-shell'` (pre-rendered application shell). * - `RenderMode.Client` maps to an empty string `''` (Client-Side Rendering, no server context needed). */ const SERVER_CONTEXT_VALUE: Record = { [RenderMode.Prerender]: 'ssg', [RenderMode.Server]: 'ssr', - [RenderMode.AppShell]: 'app-shell', [RenderMode.Client]: '', }; @@ -237,11 +235,18 @@ export class AngularServerApp { matchedRoute: RouteTreeNodeMetadata, requestContext?: unknown, ): Promise { - const { renderMode, headers, status } = matchedRoute; - if ( - !this.allowStaticRouteRender && - (renderMode === RenderMode.Prerender || renderMode === RenderMode.AppShell) - ) { + const { redirectTo, status } = matchedRoute; + + if (redirectTo !== undefined) { + // Note: The status code is validated during route extraction. + // 302 Found is used by default for redirections + // See: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#status + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return Response.redirect(new URL(redirectTo, new URL(request.url)), (status as any) ?? 302); + } + + const { renderMode, headers } = matchedRoute; + if (!this.allowStaticRouteRender && renderMode === RenderMode.Prerender) { return null; } diff --git a/packages/angular/ssr/src/routes/ng-routes.ts b/packages/angular/ssr/src/routes/ng-routes.ts index 8146b76365..2a54bbd659 100644 --- a/packages/angular/ssr/src/routes/ng-routes.ts +++ b/packages/angular/ssr/src/routes/ng-routes.ts @@ -22,7 +22,13 @@ import { Console } from '../console'; import { AngularAppManifest, getAngularAppManifest } from '../manifest'; import { AngularBootstrap, isNgModule } from '../utils/ng'; import { joinUrlParts, stripLeadingSlash } from '../utils/url'; -import { PrerenderFallback, RenderMode, SERVER_ROUTES_CONFIG, ServerRoute } from './route-config'; +import { + PrerenderFallback, + RenderMode, + SERVER_ROUTES_CONFIG, + ServerRoute, + ServerRoutesConfig, +} from './route-config'; import { RouteTree, RouteTreeNodeMetadata } from './route-tree'; /** @@ -80,6 +86,11 @@ interface AngularRouterConfigResult { * A list of errors encountered during the route extraction process. */ errors: string[]; + + /** + * The specified route for the app-shell, if configured. + */ + appShellRoute?: string; } /** @@ -317,7 +328,7 @@ function resolveRedirectTo(routePath: string, redirectTo: string): string { /** * Builds a server configuration route tree from the given server routes configuration. * - * @param serverRoutesConfig - The array of server routes to be used for configuration. + * @param serverRoutesConfig - The server routes to be used for configuration. * @returns An object containing: * - `serverConfigRouteTree`: A populated `RouteTree` instance, which organizes the server routes @@ -325,14 +336,22 @@ function resolveRedirectTo(routePath: string, redirectTo: string): string { * - `errors`: An array of strings that list any errors encountered during the route tree construction * process, such as invalid paths. */ -function buildServerConfigRouteTree(serverRoutesConfig: ServerRoute[]): { +function buildServerConfigRouteTree({ routes, appShellRoute }: ServerRoutesConfig): { errors: string[]; serverConfigRouteTree: RouteTree; } { + const serverRoutes: ServerRoute[] = [...routes]; + if (appShellRoute !== undefined) { + serverRoutes.unshift({ + path: appShellRoute, + renderMode: RenderMode.Prerender, + }); + } + const serverConfigRouteTree = new RouteTree(); const errors: string[] = []; - for (const { path, ...metadata } of serverRoutesConfig) { + for (const { path, ...metadata } of serverRoutes) { if (path[0] === '/') { errors.push(`Invalid '${path}' route configuration: the path cannot start with a slash.`); @@ -442,18 +461,6 @@ export async function getRoutesFromAngularRouterConfig( if ('error' in result) { errors.push(result.error); } else { - if (result.renderMode === RenderMode.AppShell) { - if (seenAppShellRoute !== undefined) { - errors.push( - `Error: Both '${seenAppShellRoute}' and '${stripLeadingSlash(result.route)}' routes have ` + - `their 'renderMode' set to 'AppShell'. AppShell renderMode should only be assigned to one route. ` + - `Please review your route configurations to ensure that only one route is set to 'RenderMode.AppShell'.`, - ); - } - - seenAppShellRoute = stripLeadingSlash(result.route); - } - routesResults.push(result); } } @@ -485,6 +492,7 @@ export async function getRoutesFromAngularRouterConfig( baseHref, routes: routesResults, errors, + appShellRoute: serverRoutesConfig?.appShellRoute, }; } finally { platformRef.destroy(); @@ -508,6 +516,7 @@ export async function getRoutesFromAngularRouterConfig( * * @returns A promise that resolves to an object containing: * - `routeTree`: A populated `RouteTree` containing all extracted routes from the Angular application. + * - `appShellRoute`: The specified route for the app-shell, if configured. * - `errors`: An array of strings representing any errors encountered during the route extraction process. */ export async function extractRoutesAndCreateRouteTree( @@ -515,11 +524,11 @@ export async function extractRoutesAndCreateRouteTree( manifest: AngularAppManifest = getAngularAppManifest(), invokeGetPrerenderParams = false, includePrerenderFallbackRoutes = true, -): Promise<{ routeTree: RouteTree; errors: string[] }> { +): Promise<{ routeTree: RouteTree; appShellRoute?: string; errors: string[] }> { const routeTree = new RouteTree(); const document = await new ServerAssets(manifest).getIndexServerHtml().text(); const bootstrap = await manifest.bootstrap(); - const { baseHref, routes, errors } = await getRoutesFromAngularRouterConfig( + const { baseHref, appShellRoute, routes, errors } = await getRoutesFromAngularRouterConfig( bootstrap, document, url, @@ -537,6 +546,7 @@ export async function extractRoutesAndCreateRouteTree( } return { + appShellRoute, routeTree, errors, }; diff --git a/packages/angular/ssr/src/routes/route-config.ts b/packages/angular/ssr/src/routes/route-config.ts index dc5eeecf90..c6e651aa43 100644 --- a/packages/angular/ssr/src/routes/route-config.ts +++ b/packages/angular/ssr/src/routes/route-config.ts @@ -15,9 +15,6 @@ import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from ' * @developerPreview */ export enum RenderMode { - /** AppShell rendering mode, typically used for pre-rendered shells of the application. */ - AppShell, - /** Server-Side Rendering (SSR) mode, where content is rendered on the server for each request. */ Server, @@ -69,16 +66,6 @@ export interface ServerRouteCommon { status?: number; } -/** - * A server route that uses AppShell rendering mode. - * @see {@link RenderMode} - * @developerPreview - */ -export interface ServerRouteAppShell extends Omit { - /** Specifies that the route uses AppShell rendering mode. */ - renderMode: RenderMode.AppShell; -} - /** * A server route that uses Client-Side Rendering (CSR) mode. * @see {@link RenderMode} @@ -165,27 +152,67 @@ export interface ServerRouteServer extends ServerRouteCommon { * @developerPreview */ export type ServerRoute = - | ServerRouteAppShell | ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer; +/** + * Configuration options for server routes. + * + * This interface defines the optional settings available for configuring server routes + * in the server-side environment, such as specifying a path to the app shell route. + * + * @see {@link provideServerRoutesConfig} + * @developerPreview + */ + +export interface ServerRoutesConfigOptions { + /** + * Defines the route to be used as the app shell, which serves as the main entry + * point for the application. This route is often used to enable server-side rendering + * of the application shell for requests that do not match any specific server route. + * + * @see {@link https://angular.dev/ecosystem/service-workers/app-shell | App shell pattern on Angular.dev} + */ + appShellRoute?: string; +} + +/** + * Configuration value for server routes configuration. + * @internal + */ +export interface ServerRoutesConfig extends ServerRoutesConfigOptions { + routes: ServerRoute[]; +} + /** * Token for providing the server routes configuration. * @internal */ -export const SERVER_ROUTES_CONFIG = new InjectionToken('SERVER_ROUTES_CONFIG'); +export const SERVER_ROUTES_CONFIG = new InjectionToken('SERVER_ROUTES_CONFIG'); /** - * Configures the necessary providers for server routes configuration. +/** + * Sets up the necessary providers for configuring server routes. + * This function accepts an array of server routes and optional configuration + * options, returning an `EnvironmentProviders` object that encapsulates + * the server routes and configuration settings. * * @param routes - An array of server routes to be provided. + * @param options - (Optional) An object containing additional configuration options for server routes. + * @returns An `EnvironmentProviders` instance with the server routes configuration. + * * @returns An `EnvironmentProviders` object that contains the server routes configuration. + * * @see {@link ServerRoute} + * @see {@link ServerRoutesConfigOptions} * @developerPreview */ -export function provideServerRoutesConfig(routes: ServerRoute[]): EnvironmentProviders { +export function provideServerRoutesConfig( + routes: ServerRoute[], + options?: ServerRoutesConfigOptions, +): EnvironmentProviders { if (typeof ngServerMode === 'undefined' || !ngServerMode) { throw new Error( `The 'provideServerRoutesConfig' function should not be invoked within the browser portion of the application.`, @@ -195,7 +222,7 @@ export function provideServerRoutesConfig(routes: ServerRoute[]): EnvironmentPro return makeEnvironmentProviders([ { provide: SERVER_ROUTES_CONFIG, - useValue: routes, + useValue: { routes, ...options }, }, ]); } diff --git a/packages/angular/ssr/test/routes/ng-routes_spec.ts b/packages/angular/ssr/test/routes/ng-routes_spec.ts index bb0795a156..77e3a910be 100644 --- a/packages/angular/ssr/test/routes/ng-routes_spec.ts +++ b/packages/angular/ssr/test/routes/ng-routes_spec.ts @@ -340,28 +340,6 @@ describe('extractRoutesAndCreateRouteTree', () => { ); }); - it(`should error when 'RenderMode.AppShell' is used on more than one route`, async () => { - setAngularAppTestingManifest( - [ - { path: 'home', component: DummyComponent }, - { path: 'shell', component: DummyComponent }, - ], - [{ path: '**', renderMode: RenderMode.AppShell }], - ); - - const { errors } = await extractRoutesAndCreateRouteTree( - url, - /** manifest */ undefined, - /** invokeGetPrerenderParams */ false, - /** includePrerenderFallbackRoutes */ false, - ); - - expect(errors).toHaveSize(1); - expect(errors[0]).toContain( - `Both 'home' and 'shell' routes have their 'renderMode' set to 'AppShell'.`, - ); - }); - it('should apply RenderMode matching the wildcard when no Angular routes are defined', async () => { setAngularAppTestingManifest([], [{ path: '**', renderMode: RenderMode.Server }]); diff --git a/packages/angular_devkit/build_angular/src/builders/app-shell/app-shell_spec.ts b/packages/angular_devkit/build_angular/src/builders/app-shell/app-shell_spec.ts index 3174a7db82..afabdb3094 100644 --- a/packages/angular_devkit/build_angular/src/builders/app-shell/app-shell_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/app-shell/app-shell_spec.ts @@ -126,8 +126,6 @@ describe('AppShell Builder', () => { const fileName = 'dist/index.html'; const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName))); expect(content).toMatch('Welcome to app'); - // TODO(alanagius): enable once integration of routes in complete. - // expect(content).toMatch('ng-server-context="app-shell"'); }); it('works with route', async () => { diff --git a/packages/schematics/angular/app-shell/index.ts b/packages/schematics/angular/app-shell/index.ts index 31236ee79a..46adb35f05 100644 --- a/packages/schematics/angular/app-shell/index.ts +++ b/packages/schematics/angular/app-shell/index.ts @@ -265,7 +265,7 @@ function addStandaloneServerRoute(options: AppShellOptions): Rule { throw new SchematicsException(`Cannot find "${configFilePath}".`); } - const recorder = host.beginUpdate(configFilePath); + let recorder = host.beginUpdate(configFilePath); let configSourceFile = getSourceFile(host, configFilePath); if (!isImported(configSourceFile, 'ROUTES', '@angular/router')) { const routesChange = insertImport( @@ -306,6 +306,24 @@ function addStandaloneServerRoute(options: AppShellOptions): Rule { recorder.insertRight(providersLiteral.getStart(), `[\n${updatedProvidersString.join(',\n')}]`); + if (options.serverRouting) { + host.commitUpdate(recorder); + configSourceFile = getSourceFile(host, configFilePath); + const functionCall = findNodes(configSourceFile, ts.isCallExpression).find( + (n) => + ts.isIdentifier(n.expression) && n.expression.getText() === 'provideServerRoutesConfig', + ); + + if (!functionCall) { + throw new SchematicsException( + `Cannot find the "provideServerRoutesConfig" function call in "${configFilePath}".`, + ); + } + + recorder = host.beginUpdate(configFilePath); + recorder.insertLeft(functionCall.end - 1, `, { appShellRoute: '${APP_SHELL_ROUTE}' }`); + } + // Add AppShellComponent import const appShellImportChange = insertImport( configSourceFile, @@ -376,9 +394,7 @@ export default function (options: AppShellOptions): Rule { ...(isStandalone ? [addStandaloneServerRoute(options)] : [addRouterModule(browserEntryPoint), addServerRoutes(options)]), - options.serverRouting - ? addServerRoutingConfig(options) - : addAppShellConfigToWorkspace(options), + options.serverRouting ? noop() : addAppShellConfigToWorkspace(options), schematic('component', { name: 'app-shell', module: 'app.module.server.ts', diff --git a/packages/schematics/angular/app-shell/index_spec.ts b/packages/schematics/angular/app-shell/index_spec.ts index ad2738515f..4381b05efd 100644 --- a/packages/schematics/angular/app-shell/index_spec.ts +++ b/packages/schematics/angular/app-shell/index_spec.ts @@ -200,17 +200,12 @@ describe('App Shell Schematic', () => { expect(content).toMatch(/app-shell\.component/); }); - it('should update the server routing configuration', async () => { + it(`should update the 'provideServerRoutesConfig' call to include 'appShellRoute`, async () => { const tree = await schematicRunner.runSchematic('app-shell', defaultOptions, appTree); - const content = tree.readContent('/projects/bar/src/app/app.routes.server.ts'); - expect(tags.oneLine`${content}`).toContain(tags.oneLine`{ - path: 'shell', - renderMode: RenderMode.AppShell - }, - { - path: '**', - renderMode: RenderMode.Prerender - }`); + const content = tree.readContent('/projects/bar/src/app/app.config.server.ts'); + expect(tags.oneLine`${content}`).toContain( + tags.oneLine`provideServerRoutesConfig(serverRoutes, { appShellRoute: 'shell' })`, + ); }); it('should define a server route', async () => { diff --git a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts index 9d552d9b8d..2d14c0ceec 100644 --- a/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts +++ b/tests/legacy-cli/e2e/tests/build/server-rendering/server-routes-output-mode-server.ts @@ -29,14 +29,9 @@ export default async function () { import { CsrComponent } from './csr/csr.component'; import { SsrComponent } from './ssr/ssr.component'; import { SsgComponent } from './ssg/ssg.component'; - import { AppShellComponent } from './app-shell/app-shell.component'; import { SsgWithParamsComponent } from './ssg-with-params/ssg-with-params.component'; export const routes: Routes = [ - { - path: 'app-shell', - component: AppShellComponent - }, { path: '', component: HomeComponent, @@ -88,10 +83,6 @@ export default async function () { renderMode: RenderMode.Client, headers: { 'x-custom': 'csr' }, }, - { - path: 'app-shell', - renderMode: RenderMode.AppShell, - }, { path: '**', renderMode: RenderMode.Prerender, @@ -102,12 +93,15 @@ export default async function () { ); // Generate components for the above routes - const componentNames: string[] = ['home', 'ssg', 'ssg-with-params', 'csr', 'ssr', 'app-shell']; + const componentNames: string[] = ['home', 'ssg', 'ssg-with-params', 'csr', 'ssr']; for (const componentName of componentNames) { await silentNg('generate', 'component', componentName); } + // Generate app-shell + await ng('g', 'app-shell'); + await noSilentNg('build', '--output-mode=server'); const expects: Record = { @@ -155,7 +149,7 @@ export default async function () { }, '/csr': { content: 'app-shell works', - serverContext: 'ng-server-context="app-shell"', + serverContext: 'ng-server-context="ssg"', headers: { 'x-custom': 'csr', },