Skip to content

Commit

Permalink
fix(@angular/build): handle APP_BASE_HREF correctly in prerendered …
Browse files Browse the repository at this point in the history
…routes

This commit resolves path stripping issues when `APP_BASE_HREF` does not align with the expected value.

Closes #28775

(cherry picked from commit a1fa483)
  • Loading branch information
alan-agius4 committed Nov 5, 2024
1 parent 47bbaca commit c41529c
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 6 deletions.
17 changes: 11 additions & 6 deletions packages/angular/build/src/utils/server-rendering/prerender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,11 @@ async function renderPages(
const baseHrefWithLeadingSlash = addLeadingSlash(baseHref);

for (const { route, redirectTo, renderMode } of serializableRouteTreeNode) {
// Remove base href from file output path.
const routeWithoutBaseHref = addLeadingSlash(
route.slice(baseHrefWithLeadingSlash.length - 1),
);
// Remove the base href from the file output path.
const routeWithoutBaseHref = addTrailingSlash(route).startsWith(baseHrefWithLeadingSlash)
? addLeadingSlash(route.slice(baseHrefWithLeadingSlash.length - 1))
: route;

const outPath = posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');

if (typeof redirectTo === 'string') {
Expand Down Expand Up @@ -336,11 +337,15 @@ async function getAllRoutes(
}

function addLeadingSlash(value: string): string {
return value.charAt(0) === '/' ? value : '/' + value;
return value[0] === '/' ? value : '/' + value;
}

function addTrailingSlash(url: string): string {
return url[url.length - 1] === '/' ? url : `${url}/`;
}

function removeLeadingSlash(value: string): string {
return value.charAt(0) === '/' ? value.slice(1) : value;
return value[0] === '/' ? value.slice(1) : value;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { join } from 'node:path';
import { existsSync } from 'node:fs';
import assert from 'node:assert';
import { expectFileNotToExist, expectFileToMatch, writeFile } from '../../../utils/fs';
import { ng, noSilentNg, silentNg } from '../../../utils/process';
import { installWorkspacePackages, uninstallPackage } from '../../../utils/packages';
import { useSha } from '../../../utils/project';
import { getGlobalVariable } from '../../../utils/env';
import { langTranslations, setupI18nConfig } from '../../i18n/setup';

export default async function () {
assert(
getGlobalVariable('argv')['esbuild'],
'This test should not be called in the Webpack suite.',
);

// Setup project
await setupI18nConfig();

// Forcibly remove in case another test doesn't clean itself up.
await uninstallPackage('@angular/ssr');
await ng('add', '@angular/ssr', '--server-routing', '--skip-confirmation', '--skip-install');
await useSha();
await installWorkspacePackages();

// Add routes
await writeFile(
'src/app/app.routes.ts',
`
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { SsgComponent } from './ssg/ssg.component';
export const routes: Routes = [
{
path: '',
component: HomeComponent,
},
{
path: 'ssg',
component: SsgComponent,
},
{
path: '**',
component: HomeComponent,
},
];
`,
);

// Add server routing
await writeFile(
'src/app/app.routes.server.ts',
`
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: '**',
renderMode: RenderMode.Prerender,
},
];
`,
);

await writeFile(
'src/app/app.config.ts',
`
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { APP_BASE_HREF } from '@angular/common';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideClientHydration(),
{
provide: APP_BASE_HREF,
useValue: '/',
},
],
};
`,
);

// Generate components for the above routes
await silentNg('generate', 'component', 'home');
await silentNg('generate', 'component', 'ssg');

await noSilentNg('build', '--output-mode=static');

for (const { lang, outputPath } of langTranslations) {
await expectFileToMatch(join(outputPath, 'index.html'), `<p id="locale">${lang}</p>`);
await expectFileToMatch(join(outputPath, 'ssg/index.html'), `<p id="locale">${lang}</p>`);
}

// Check that server directory does not exist
assert(
!existsSync('dist/test-project/server'),
'Server directory should not exist when output-mode is static',
);
}

0 comments on commit c41529c

Please sign in to comment.