From cb356a5db6b1ec2799790a603f931a961883ab31 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Wed, 28 Aug 2024 12:16:43 +0100 Subject: [PATCH] feat(i18n): expand fallback system (#11677) * feat(i18n): expand fallback system * rebase * apply feedback * better changeset * update tests too * apply feedback * Update .changeset/blue-pens-divide.md Co-authored-by: Bjorn Lu * update docs * nitpick * Apply suggestions from code review Co-authored-by: Sarah Rainsberger * Update .changeset/blue-pens-divide.md Co-authored-by: Sarah Rainsberger * Apply suggestions from code review Co-authored-by: Sarah Rainsberger * Update .changeset/blue-pens-divide.md Co-authored-by: Sarah Rainsberger * fix regression --------- Co-authored-by: Bjorn Lu Co-authored-by: Sarah Rainsberger --- .changeset/blue-pens-divide.md | 22 ++++ .changeset/lazy-feet-join.md | 32 ++++++ packages/astro/src/@types/astro.ts | 37 ++++++ packages/astro/src/container/pipeline.ts | 10 +- packages/astro/src/core/app/pipeline.ts | 10 +- packages/astro/src/core/app/types.ts | 1 + packages/astro/src/core/base-pipeline.ts | 9 +- packages/astro/src/core/build/generate.ts | 3 +- packages/astro/src/core/build/pipeline.ts | 9 +- .../src/core/build/plugins/plugin-manifest.ts | 3 +- packages/astro/src/core/config/schema.ts | 1 + packages/astro/src/core/render-context.ts | 14 +-- packages/astro/src/core/routing/rewrite.ts | 43 ++++--- packages/astro/src/i18n/index.ts | 13 ++- packages/astro/src/i18n/utils.ts | 8 ++ packages/astro/src/virtual-modules/i18n.ts | 10 +- .../src/vite-plugin-astro-server/pipeline.ts | 10 +- .../src/vite-plugin-astro-server/plugin.ts | 3 +- .../src/pages/[slug]/title.astro | 1 - .../src/pages/foo.astro | 2 +- packages/astro/test/i18n-routing.test.js | 105 ++++++++++++++++++ packages/astro/test/rewrite.test.js | 2 +- 22 files changed, 297 insertions(+), 51 deletions(-) create mode 100644 .changeset/blue-pens-divide.md create mode 100644 .changeset/lazy-feet-join.md diff --git a/.changeset/blue-pens-divide.md b/.changeset/blue-pens-divide.md new file mode 100644 index 000000000000..a295fe7b1494 --- /dev/null +++ b/.changeset/blue-pens-divide.md @@ -0,0 +1,22 @@ +--- +'astro': patch +--- + +Fixes a bug in the logic of `Astro.rewrite()` which led to the value for `base`, if configured, being automatically prepended to the rewrite URL passed. This was unintended behavior and has been corrected, and Astro now processes the URLs exactly as passed. + +If you use the `rewrite()` function on a project that has `base` configured, you must now prepend the base to your existing rewrite URL: + +```js +// astro.config.mjs +export default defineConfig({ + base: '/blog' +}) +``` + +```diff +// src/middleware.js +export function onRequest(ctx, next) { +- return ctx.rewrite("/about") ++ return ctx.rewrite("/blog/about") +} +``` diff --git a/.changeset/lazy-feet-join.md b/.changeset/lazy-feet-join.md new file mode 100644 index 000000000000..e2b0a40776d7 --- /dev/null +++ b/.changeset/lazy-feet-join.md @@ -0,0 +1,32 @@ +--- +'astro': minor +--- + +Adds a new option `fallbackType` to `i18n.routing` configuration that allows you to control how fallback pages are handled. + +When `i18n.fallback` is configured, this new routing option controls whether to [redirect](https://docs.astro.build/en/guides/routing/#redirects) to the fallback page, or to [rewrite](https://docs.astro.build/en/guides/routing/#rewrites) the fallback page's content in place. + +The `"redirect"` option is the default value and matches the current behavior of the existing fallback system. + +The option `"rewrite"` uses the new [rewriting system](https://docs.astro.build/en/guides/routing/#rewrites) to create fallback pages that render content on the original, requested URL without a browser refresh. + +For example, the following configuration will generate a page `/fr/index.html` that will contain the same HTML rendered by the page `/en/index.html` when `src/pages/fr/index.astro` does not exist. + +```js +// astro.config.mjs +export default defineConfig({ + i18n: { + locals: ["en", "fr"], + defaultLocale: "en", + routing: { + prefixDefaultLocale: true, + fallbackType: "rewrite" + }, + fallback: { + fr: "en" + }, + } +}) +``` + + diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 50afa1423f2d..a366444b9639 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -1668,6 +1668,43 @@ export interface AstroUserConfig { * */ redirectToDefaultLocale?: boolean; + /** + * @docs + * @name i18n.routing.fallbackType + * @kind h4 + * @type {"redirect" | "rewrite"} + * @default `"redirect"` + * @version 4.15.0 + * @description + * + * When [`i18n.fallback`](#i18nfallback) is configured to avoid showing a 404 page for missing page routes, this option controls whether to [redirect](https://docs.astro.build/en/guides/routing/#redirects) to the fallback page, or to [rewrite](https://docs.astro.build/en/guides/routing/#rewrites) the fallback page's content in place. + * + * By default, Astro's i18n routing creates pages that redirect your visitors to a new destination based on your fallback configuration. The browser will refresh and show the destination address in the URL bar. + * + * When `i18n.routing.fallback: "rewrite"` is configured, Astro will create pages that render the contents of the fallback page on the original, requested URL. + * + * With the following configuration, if you have the file `src/pages/en/about.astro` but not `src/pages/fr/about.astro`, the `astro build` command will generate `dist/fr/about.html` with the same content as the `dist/en/index.html` page. + * Your site visitor will see the English version of the page at `https://example.com/fr/about/` and will not be redirected. + * + * ```js + * //astro.config.mjs + * export default defineConfig({ + * i18n: { + * defaultLocale: "en", + * locales: ["en", "fr"], + * routing: { + * prefixDefaultLocale: false, + * fallbackType: "rewrite", + * }, + * fallback: { + * fr: "en", + * } + * }, + * }) + * ``` + */ + fallbackType: "redirect" | "rewrite" + /** * @name i18n.routing.strategy * @type {"pathname"} diff --git a/packages/astro/src/container/pipeline.ts b/packages/astro/src/container/pipeline.ts index ff2718b8fbc6..481994aae45c 100644 --- a/packages/astro/src/container/pipeline.ts +++ b/packages/astro/src/container/pipeline.ts @@ -5,7 +5,7 @@ import type { SSRElement, SSRResult, } from '../@types/astro.js'; -import { type HeadElements, Pipeline } from '../core/base-pipeline.js'; +import {type HeadElements, Pipeline, type TryRewriteResult} from '../core/base-pipeline.js'; import type { SinglePageBuiltModule } from '../core/build/types.js'; import { createModuleScriptElement, @@ -71,8 +71,8 @@ export class ContainerPipeline extends Pipeline { async tryRewrite( payload: RewritePayload, request: Request, - ): Promise<[RouteData, ComponentInstance, URL]> { - const [foundRoute, finalUrl] = findRouteToRewrite({ + ): Promise { + const {newUrl,pathname,routeData} = findRouteToRewrite({ payload, request, routes: this.manifest?.routes.map((r) => r.routeData), @@ -81,8 +81,8 @@ export class ContainerPipeline extends Pipeline { base: this.manifest.base, }); - const componentInstance = await this.getComponentByRoute(foundRoute); - return [foundRoute, componentInstance, finalUrl]; + const componentInstance = await this.getComponentByRoute(routeData); + return {componentInstance, routeData, newUrl, pathname}; } insertRoute(route: RouteData, componentInstance: ComponentInstance): void { diff --git a/packages/astro/src/core/app/pipeline.ts b/packages/astro/src/core/app/pipeline.ts index b784ba916464..0b6de4f6cdd9 100644 --- a/packages/astro/src/core/app/pipeline.ts +++ b/packages/astro/src/core/app/pipeline.ts @@ -6,7 +6,7 @@ import type { SSRElement, SSRResult, } from '../../@types/astro.js'; -import { Pipeline } from '../base-pipeline.js'; +import {Pipeline, type TryRewriteResult} from '../base-pipeline.js'; import type { SinglePageBuiltModule } from '../build/types.js'; import { RedirectSinglePageBuiltModule } from '../redirects/component.js'; import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js'; @@ -94,8 +94,8 @@ export class AppPipeline extends Pipeline { payload: RewritePayload, request: Request, _sourceRoute: RouteData, - ): Promise<[RouteData, ComponentInstance, URL]> { - const [foundRoute, finalUrl] = findRouteToRewrite({ + ): Promise { + const { newUrl,pathname,routeData} = findRouteToRewrite({ payload, request, routes: this.manifest?.routes.map((r) => r.routeData), @@ -104,8 +104,8 @@ export class AppPipeline extends Pipeline { base: this.manifest.base, }); - const componentInstance = await this.getComponentByRoute(foundRoute); - return [foundRoute, componentInstance, finalUrl]; + const componentInstance = await this.getComponentByRoute(routeData); + return {newUrl, pathname, componentInstance, routeData}; } async getModuleForRoute(route: RouteData): Promise { diff --git a/packages/astro/src/core/app/types.ts b/packages/astro/src/core/app/types.ts index 00e37dacd9ba..9d5bf982e1c4 100644 --- a/packages/astro/src/core/app/types.ts +++ b/packages/astro/src/core/app/types.ts @@ -76,6 +76,7 @@ export type SSRManifest = { export type SSRManifestI18n = { fallback: Record | undefined; + fallbackType: "redirect" | "rewrite"; strategy: RoutingStrategies; locales: Locales; defaultLocale: string; diff --git a/packages/astro/src/core/base-pipeline.ts b/packages/astro/src/core/base-pipeline.ts index 01e18bfa0356..0d4dc78c24d5 100644 --- a/packages/astro/src/core/base-pipeline.ts +++ b/packages/astro/src/core/base-pipeline.ts @@ -95,7 +95,7 @@ export abstract class Pipeline { rewritePayload: RewritePayload, request: Request, sourceRoute: RouteData, - ): Promise<[RouteData, ComponentInstance, URL]>; + ): Promise; /** * Tells the pipeline how to retrieve a component give a `RouteData` @@ -106,3 +106,10 @@ export abstract class Pipeline { // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface HeadElements extends Pick {} + +export interface TryRewriteResult { + routeData: RouteData, + componentInstance: ComponentInstance, + newUrl: URL, + pathname: string +} diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index fae7896b9212..c4dd13399d1a 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -26,7 +26,7 @@ import { removeLeadingForwardSlash, removeTrailingForwardSlash, } from '../../core/path.js'; -import { toRoutingStrategy } from '../../i18n/utils.js'; +import {toFallbackType, toRoutingStrategy} from '../../i18n/utils.js'; import { runHookBuildGenerated } from '../../integrations/hooks.js'; import { getOutputDirectory } from '../../prerender/utils.js'; import type { SSRManifestI18n } from '../app/types.js'; @@ -528,6 +528,7 @@ function createBuildManifest( if (settings.config.i18n) { i18nManifest = { fallback: settings.config.i18n.fallback, + fallbackType: toFallbackType(settings.config.i18n.routing), strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains), defaultLocale: settings.config.i18n.defaultLocale, locales: settings.config.i18n.locales, diff --git a/packages/astro/src/core/build/pipeline.ts b/packages/astro/src/core/build/pipeline.ts index 4e0d94c57ea6..ae1cea799ccb 100644 --- a/packages/astro/src/core/build/pipeline.ts +++ b/packages/astro/src/core/build/pipeline.ts @@ -26,6 +26,7 @@ import { RESOLVED_SPLIT_MODULE_ID } from './plugins/plugin-ssr.js'; import { getPagesFromVirtualModulePageName, getVirtualModulePageName } from './plugins/util.js'; import type { PageBuildData, SinglePageBuiltModule, StaticBuildOptions } from './types.js'; import { i18nHasFallback } from './util.js'; +import type {TryRewriteResult} from "../base-pipeline.js"; /** * The build pipeline is responsible to gather the files emitted by the SSR build and generate the pages by executing these files. @@ -289,8 +290,8 @@ export class BuildPipeline extends Pipeline { payload: RewritePayload, request: Request, _sourceRoute: RouteData, - ): Promise<[RouteData, ComponentInstance, URL]> { - const [foundRoute, finalUrl] = findRouteToRewrite({ + ): Promise { + const { routeData, pathname, newUrl} = findRouteToRewrite({ payload, request, routes: this.options.manifest.routes, @@ -299,8 +300,8 @@ export class BuildPipeline extends Pipeline { base: this.config.base, }); - const componentInstance = await this.getComponentByRoute(foundRoute); - return [foundRoute, componentInstance, finalUrl]; + const componentInstance = await this.getComponentByRoute(routeData); + return { routeData, componentInstance, newUrl, pathname }; } async retrieveSsrEntry(route: RouteData, filePath: string): Promise { diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 6b38758c38c4..9ffc615f62d9 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -4,7 +4,7 @@ import type { OutputChunk } from 'rollup'; import type { Plugin as VitePlugin } from 'vite'; import { getAssetsPrefix } from '../../../assets/utils/getAssetsPrefix.js'; import { normalizeTheLocale } from '../../../i18n/index.js'; -import { toRoutingStrategy } from '../../../i18n/utils.js'; +import {toFallbackType, toRoutingStrategy} from '../../../i18n/utils.js'; import { runHookBuildSsr } from '../../../integrations/hooks.js'; import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js'; import type { @@ -254,6 +254,7 @@ function buildManifest( if (settings.config.i18n) { i18nManifest = { fallback: settings.config.i18n.fallback, + fallbackType: toFallbackType(settings.config.i18n.routing), strategy: toRoutingStrategy(settings.config.i18n.routing, settings.config.i18n.domains), locales: settings.config.i18n.locales, defaultLocale: settings.config.i18n.defaultLocale, diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index f49b4708e714..9c9bfd03aa03 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -403,6 +403,7 @@ export const AstroConfigSchema = z.object({ .object({ prefixDefaultLocale: z.boolean().optional().default(false), redirectToDefaultLocale: z.boolean().optional().default(true), + fallbackType: z.enum(["redirect", "rewrite"]).optional().default("redirect"), }) .refine( ({ prefixDefaultLocale, redirectToDefaultLocale }) => { diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index e81e1696495b..d7400afff1af 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -142,13 +142,13 @@ export class RenderContext { if (payload) { pipeline.logger.debug('router', 'Called rewriting to:', payload); // we intentionally let the error bubble up - const [routeData, component] = await pipeline.tryRewrite( + const { routeData, componentInstance: newComponent} = await pipeline.tryRewrite( payload, this.request, this.originalRoute, ); this.routeData = routeData; - componentInstance = component; + componentInstance = newComponent; this.isRewriting = true; this.status = 200; } @@ -234,7 +234,7 @@ export class RenderContext { async #executeRewrite(reroutePayload: RewritePayload) { this.pipeline.logger.debug('router', 'Calling rewrite: ', reroutePayload); - const [routeData, component, newURL] = await this.pipeline.tryRewrite( + const { routeData, componentInstance, newUrl, pathname } = await this.pipeline.tryRewrite( reroutePayload, this.request, this.originalRoute, @@ -243,16 +243,16 @@ export class RenderContext { if (reroutePayload instanceof Request) { this.request = reroutePayload; } else { - this.request = this.#copyRequest(newURL, this.request); + this.request = this.#copyRequest(newUrl, this.request); } this.url = new URL(this.request.url); this.cookies = new AstroCookies(this.request); - this.params = getParams(routeData, this.url.pathname); - this.pathname = this.url.pathname; + this.params = getParams(routeData, pathname); + this.pathname = pathname; this.isRewriting = true; // we found a route and a component, we can change the status code to 200 this.status = 200; - return await this.render(component); + return await this.render(componentInstance); } createActionAPIContext(): ActionAPIContext { diff --git a/packages/astro/src/core/routing/rewrite.ts b/packages/astro/src/core/routing/rewrite.ts index f30caed08c7b..43bd3a2ac95d 100644 --- a/packages/astro/src/core/routing/rewrite.ts +++ b/packages/astro/src/core/routing/rewrite.ts @@ -12,6 +12,17 @@ export type FindRouteToRewrite = { base: AstroConfig['base']; }; +export interface FindRouteToRewriteResult { + routeData: RouteData; + newUrl: URL; + pathname: string; +} + +/** + * Shared logic to retrieve the rewritten route. It returns a tuple that represents: + * 1. The new `Request` object. It contains `base` + * 2. + */ export function findRouteToRewrite({ payload, routes, @@ -19,23 +30,25 @@ export function findRouteToRewrite({ trailingSlash, buildFormat, base, -}: FindRouteToRewrite): [RouteData, URL] { - let finalUrl: URL | undefined = undefined; +}: FindRouteToRewrite): FindRouteToRewriteResult { + let newUrl: URL | undefined = undefined; if (payload instanceof URL) { - finalUrl = payload; + newUrl = payload; } else if (payload instanceof Request) { - finalUrl = new URL(payload.url); + newUrl = new URL(payload.url); } else { - finalUrl = new URL(payload, new URL(request.url).origin); + newUrl = new URL(payload, new URL(request.url).origin); + } + let pathname = newUrl.pathname; + if (base !== '/' && newUrl.pathname.startsWith(base)) { + pathname = shouldAppendForwardSlash(trailingSlash, buildFormat) + ? appendForwardSlash(newUrl.pathname) + : removeTrailingForwardSlash(newUrl.pathname); + pathname = pathname.slice(base.length); } let foundRoute; for (const route of routes) { - const pathname = shouldAppendForwardSlash(trailingSlash, buildFormat) - ? appendForwardSlash(finalUrl.pathname) - : base !== '/' - ? removeTrailingForwardSlash(finalUrl.pathname) - : finalUrl.pathname; if (route.pattern.test(decodeURI(pathname))) { foundRoute = route; break; @@ -43,13 +56,17 @@ export function findRouteToRewrite({ } if (foundRoute) { - return [foundRoute, finalUrl]; + return { + routeData: foundRoute, + newUrl, + pathname + }; } else { const custom404 = routes.find((route) => route.route === '/404'); if (custom404) { - return [custom404, finalUrl]; + return { routeData: custom404, newUrl, pathname }; } else { - return [DEFAULT_404_ROUTE, finalUrl]; + return { routeData: DEFAULT_404_ROUTE, newUrl, pathname }; } } } diff --git a/packages/astro/src/i18n/index.ts b/packages/astro/src/i18n/index.ts index aa38b63bbe89..d00345d55527 100644 --- a/packages/astro/src/i18n/index.ts +++ b/packages/astro/src/i18n/index.ts @@ -282,6 +282,7 @@ export type MiddlewarePayload = { defaultLocale: string; domains: Record | undefined; fallback: Record | undefined; + fallbackType: "redirect" | "rewrite"; }; // NOTE: public function exported to the users via `astro:i18n` module @@ -332,7 +333,7 @@ export function notFound({ base, locales }: MiddlewarePayload) { } // NOTE: public function exported to the users via `astro:i18n` module -export type RedirectToFallback = (context: APIContext, response: Response) => Response; +export type RedirectToFallback = (context: APIContext, response: Response) => Promise; export function redirectToFallback({ fallback, @@ -340,8 +341,9 @@ export function redirectToFallback({ defaultLocale, strategy, base, + fallbackType }: MiddlewarePayload) { - return function (context: APIContext, response: Response): Response { + return async function (context: APIContext, response: Response): Promise { if (response.status >= 300 && fallback) { const fallbackKeys = fallback ? Object.keys(fallback) : []; // we split the URL using the `/`, and then check in the returned array we have the locale @@ -375,7 +377,12 @@ export function redirectToFallback({ } else { newPathname = context.url.pathname.replace(`/${urlLocale}`, `/${pathFallbackLocale}`); } - return context.redirect(newPathname); + + if (fallbackType === "rewrite") { + return await context.rewrite(newPathname); + } else { + return context.redirect(newPathname); + } } } return response; diff --git a/packages/astro/src/i18n/utils.ts b/packages/astro/src/i18n/utils.ts index 052fe01fce65..ed256b7ab06b 100644 --- a/packages/astro/src/i18n/utils.ts +++ b/packages/astro/src/i18n/utils.ts @@ -215,3 +215,11 @@ export function toRoutingStrategy( return strategy; } + +export function toFallbackType(routing: NonNullable['routing']): "redirect" | "rewrite" { + if (routing === 'manual') { + return 'rewrite'; + } + return routing.fallbackType; + +} diff --git a/packages/astro/src/virtual-modules/i18n.ts b/packages/astro/src/virtual-modules/i18n.ts index aacdd80a96e1..114e76bb42fd 100644 --- a/packages/astro/src/virtual-modules/i18n.ts +++ b/packages/astro/src/virtual-modules/i18n.ts @@ -9,7 +9,7 @@ import { IncorrectStrategyForI18n } from '../core/errors/errors-data.js'; import { AstroError } from '../core/errors/index.js'; import * as I18nInternals from '../i18n/index.js'; import type { RedirectToFallback } from '../i18n/index.js'; -import { toRoutingStrategy } from '../i18n/utils.js'; +import {toFallbackType, toRoutingStrategy} from '../i18n/utils.js'; import type { I18nInternalConfig } from '../i18n/vite-plugin-i18n.js'; export { normalizeTheLocale, toCodes, toPaths } from '../i18n/index.js'; @@ -21,6 +21,7 @@ const { defaultLocale, locales, domains, fallback, routing } = i18n!; const base = import.meta.env.BASE_URL; let strategy = toRoutingStrategy(routing, domains); +let fallbackType = toFallbackType(routing); export type GetLocaleOptions = I18nInternals.GetLocaleOptions; @@ -267,6 +268,7 @@ if (i18n?.routing === 'manual') { strategy, domains, fallback, + fallbackType }); } else { redirectToDefaultLocale = noop('redirectToDefaultLocale'); @@ -295,6 +297,7 @@ if (i18n?.routing === 'manual') { strategy, domains, fallback, + fallbackType }); } else { notFound = noop('notFound'); @@ -317,7 +320,7 @@ if (i18n?.routing === 'manual') { * Allows to use the build-in fallback system of Astro * * @param {APIContext} context The context passed to the middleware - * @param {Response} response An optional `Response` in case you're handling a `Response` coming from the `next` function. + * @param {Promise} response An optional `Response` in case you're handling a `Response` coming from the `next` function. */ export let redirectToFallback: RedirectToFallback; @@ -331,6 +334,7 @@ if (i18n?.routing === 'manual') { strategy, domains, fallback, + fallbackType }); } else { redirectToFallback = noop('useFallback'); @@ -374,11 +378,13 @@ export let middleware: (customOptions: NewAstroRoutingConfigWithoutManual) => Mi if (i18n?.routing === 'manual') { middleware = (customOptions: NewAstroRoutingConfigWithoutManual) => { strategy = toRoutingStrategy(customOptions, {}); + fallbackType = toFallbackType(customOptions); const manifest: SSRManifest['i18n'] = { ...i18n, fallback: undefined, strategy, domainLookupTable: {}, + fallbackType }; return I18nInternals.createMiddleware(manifest, base, trailingSlash, format); }; diff --git a/packages/astro/src/vite-plugin-astro-server/pipeline.ts b/packages/astro/src/vite-plugin-astro-server/pipeline.ts index d5117a0a48a2..80073a54eb00 100644 --- a/packages/astro/src/vite-plugin-astro-server/pipeline.ts +++ b/packages/astro/src/vite-plugin-astro-server/pipeline.ts @@ -11,7 +11,7 @@ import type { SSRManifest, } from '../@types/astro.js'; import { getInfoOutput } from '../cli/info/index.js'; -import type { HeadElements } from '../core/base-pipeline.js'; +import type { HeadElements, TryRewriteResult } from '../core/base-pipeline.js'; import { ASTRO_VERSION } from '../core/constants.js'; import { enhanceViteSSRError } from '../core/errors/dev/index.js'; import { AggregateError, CSSError, MarkdownError } from '../core/errors/index.js'; @@ -203,11 +203,11 @@ export class DevPipeline extends Pipeline { payload: RewritePayload, request: Request, _sourceRoute: RouteData, - ): Promise<[RouteData, ComponentInstance, URL]> { + ): Promise { if (!this.manifestData) { throw new Error('Missing manifest data. This is an internal error, please file an issue.'); } - const [foundRoute, finalUrl] = findRouteToRewrite({ + const { routeData, pathname, newUrl } = findRouteToRewrite({ payload, request, routes: this.manifestData?.routes, @@ -216,8 +216,8 @@ export class DevPipeline extends Pipeline { base: this.config.base, }); - const componentInstance = await this.getComponentByRoute(foundRoute); - return [foundRoute, componentInstance, finalUrl]; + const componentInstance = await this.getComponentByRoute(routeData); + return { newUrl, pathname, componentInstance, routeData }; } setManifestData(manifestData: ManifestData) { diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index 2458b5b8d360..f38c84782118 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -12,7 +12,7 @@ import type { Logger } from '../core/logger/core.js'; import { createViteLoader } from '../core/module-loader/index.js'; import { injectDefaultRoutes } from '../core/routing/default.js'; import { createRouteManifest } from '../core/routing/index.js'; -import { toRoutingStrategy } from '../i18n/utils.js'; +import {toFallbackType, toRoutingStrategy} from '../i18n/utils.js'; import { baseMiddleware } from './base.js'; import { createController } from './controller.js'; import { recordServerError } from './error.js'; @@ -128,6 +128,7 @@ export function createDevelopmentManifest(settings: AstroSettings): SSRManifest defaultLocale: settings.config.i18n.defaultLocale, locales: settings.config.i18n.locales, domainLookupTable: {}, + fallbackType: toFallbackType(settings.config.i18n.routing), }; } diff --git a/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro b/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro index d468d103bd3e..bbc1a2d9e5c4 100644 --- a/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro +++ b/packages/astro/test/fixtures/rewrite-server/src/pages/[slug]/title.astro @@ -1,6 +1,5 @@ --- const { slug } = Astro.params; -console.log("is it here???", Astro.params) export const prerender = false; --- diff --git a/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro index 70ce073954aa..f7e38bbc5adb 100644 --- a/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro +++ b/packages/astro/test/fixtures/rewrite-trailing-slash-never/src/pages/foo.astro @@ -1,3 +1,3 @@ --- -return Astro.rewrite("/") +return Astro.rewrite("/base") --- diff --git a/packages/astro/test/i18n-routing.test.js b/packages/astro/test/i18n-routing.test.js index df8083b81c1a..06329aa329f1 100644 --- a/packages/astro/test/i18n-routing.test.js +++ b/packages/astro/test/i18n-routing.test.js @@ -1929,3 +1929,108 @@ describe('SSR fallback from missing locale index to default locale index', () => assert.equal(response.headers.get('location'), '/'); }); }); + + + +describe('Fallback rewrite dev server', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-fallback/', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + routing: { + prefixDefaultLocale: false, + }, + fallback: { + fr: 'en', + }, + fallbackType: "rewrite" + }, + }); + devServer = await fixture.startDevServer(); + }); + after(async () => { + devServer.stop() + }) + + it('should correctly rewrite to en', async () => { + const html = await fixture.fetch('/fr').then((res) => res.text()); + assert.match(html, /Hello/); + // assert.fail() + }); +}); + +describe('Fallback rewrite SSG', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-fallback/', + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + routing: { + prefixDefaultLocale: false, + fallbackType: "rewrite" + }, + fallback: { + fr: 'en', + }, + }, + }); + await fixture.build(); + // app = await fixture.loadTestAdapterApp(); + }); + + it('should correctly rewrite to en', async () => { + const html = await fixture.readFile('/fr/index.html'); + assert.match(html, /Hello/); + // assert.fail() + }); +}); + +describe('Fallback rewrite SSR', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + let app; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/i18n-routing-fallback/', + output: 'server', + outDir: './dist/i18n-routing-fallback', + build: { + client: './dist/i18n-routing-fallback/client', + server: './dist/i18n-routing-fallback/server', + }, + adapter: testAdapter(), + i18n: { + defaultLocale: 'en', + locales: ['en', 'fr'], + routing: { + prefixDefaultLocale: false, + fallbackType: "rewrite" + }, + fallback: { + fr: 'en', + }, + }, + }); + await fixture.build(); + app = await fixture.loadTestAdapterApp(); + }); + + it('should correctly rewrite to en', async () => { + const request = new Request('http://example.com/fr'); + const response = await app.render(request); + assert.equal(response.status, 200); + const html = await response.text(); + assert.match(html, /Hello/); + }); +}); diff --git a/packages/astro/test/rewrite.test.js b/packages/astro/test/rewrite.test.js index 7839e7d3350b..10d7c70d24fd 100644 --- a/packages/astro/test/rewrite.test.js +++ b/packages/astro/test/rewrite.test.js @@ -104,7 +104,7 @@ describe('Dev rewrite, trailing slash -> never, with base', () => { }); it('should rewrite to the homepage', async () => { - const html = await fixture.fetch('/foo').then((res) => res.text()); + const html = await fixture.fetch('/base/foo').then((res) => res.text()); const $ = cheerioLoad(html); assert.equal($('h1').text(), 'Index');