mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-15 18:13:38 +08:00
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' }) ```
This commit is contained in:
parent
553d3d7f6e
commit
b2e2be052f
@ -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<Response | null> | null | Response;
|
||||
|
||||
// @public
|
||||
export type ServerRoute = ServerRouteAppShell | ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer;
|
||||
|
||||
// @public
|
||||
export interface ServerRouteAppShell extends Omit<ServerRouteCommon, 'headers' | 'status'> {
|
||||
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<ServerRoutePrerende
|
||||
getPrerenderParams: () => Promise<Record<string, string>[]>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface ServerRoutesConfigOptions {
|
||||
appShellRoute?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface ServerRouteServer extends ServerRouteCommon {
|
||||
renderMode: RenderMode.Server;
|
||||
|
@ -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 () => {
|
||||
|
@ -23,6 +23,7 @@ export type WritableSerializableRouteTreeNode = Writeable<SerializableRouteTreeN
|
||||
|
||||
export interface RoutersExtractorWorkerResult {
|
||||
serializedRouteTree: SerializableRouteTreeNode;
|
||||
appShellRoute?: string;
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
@ -33,8 +34,7 @@ export interface RoutersExtractorWorkerResult {
|
||||
* It maps `RenderMode` enum values to their corresponding numeric identifiers.
|
||||
*/
|
||||
export const RouteRenderMode: Record<keyof typeof RenderMode, RenderMode> = {
|
||||
AppShell: 0,
|
||||
Server: 1,
|
||||
Client: 2,
|
||||
Prerender: 3,
|
||||
Server: 0,
|
||||
Client: 1,
|
||||
Prerender: 2,
|
||||
};
|
||||
|
@ -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<string, string>,
|
||||
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<void>[] = [];
|
||||
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<string | null> = renderWorker.run({ url: route, isAppShellRoute });
|
||||
const render: Promise<string | null> = renderWorker.run({ url: route });
|
||||
const renderResult: Promise<void> = 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
|
||||
|
@ -33,7 +33,7 @@ async function extractRoutes(): Promise<RoutersExtractorWorkerResult> {
|
||||
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<RoutersExtractorWorkerResult> {
|
||||
|
||||
return {
|
||||
errors,
|
||||
appShellRoute,
|
||||
serializedRouteTree: routeTree.toObject(),
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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, string> = {
|
||||
[RenderMode.Prerender]: 'ssg',
|
||||
[RenderMode.Server]: 'ssr',
|
||||
[RenderMode.AppShell]: 'app-shell',
|
||||
[RenderMode.Client]: '',
|
||||
};
|
||||
|
||||
@ -237,11 +235,18 @@ export class AngularServerApp {
|
||||
matchedRoute: RouteTreeNodeMetadata,
|
||||
requestContext?: unknown,
|
||||
): Promise<Response | null> {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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<ServerConfigRouteTreeAdditionalMetadata>;
|
||||
} {
|
||||
const serverRoutes: ServerRoute[] = [...routes];
|
||||
if (appShellRoute !== undefined) {
|
||||
serverRoutes.unshift({
|
||||
path: appShellRoute,
|
||||
renderMode: RenderMode.Prerender,
|
||||
});
|
||||
}
|
||||
|
||||
const serverConfigRouteTree = new RouteTree<ServerConfigRouteTreeAdditionalMetadata>();
|
||||
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,
|
||||
};
|
||||
|
@ -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<ServerRouteCommon, 'headers' | 'status'> {
|
||||
/** 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<ServerRoute[]>('SERVER_ROUTES_CONFIG');
|
||||
export const SERVER_ROUTES_CONFIG = new InjectionToken<ServerRoutesConfig>('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 },
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
@ -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 }]);
|
||||
|
||||
|
@ -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 () => {
|
||||
|
@ -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',
|
||||
|
@ -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 () => {
|
||||
|
@ -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<string, string> = {
|
||||
@ -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',
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user