Skip to content

Commit

Permalink
Use pipeline hooks to bind data to the request
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Nov 15, 2023
1 parent 069415d commit eb2789d
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 38 deletions.
6 changes: 3 additions & 3 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
SSRElement,
SSRManifest,
} from '../../@types/astro.js';
import { createI18nMiddleware } from '../../i18n/middleware.js';
import { createI18nMiddleware, i18nPipelineHook } from '../../i18n/middleware.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import { getSetCookiesFromResponse } from '../cookies/index.js';
import { consoleLogDestination } from '../logger/console.js';
Expand Down Expand Up @@ -169,8 +169,7 @@ export class App {
let i18nMiddleware = createI18nMiddleware(
this.#manifest.i18n,
this.#manifest.base,
this.#manifest.trailingSlash,
this.#manifest.routes.map((route) => route.routeData)
this.#manifest.trailingSlash
);
if (i18nMiddleware) {
if (mod.onRequest) {
Expand All @@ -180,6 +179,7 @@ export class App {
} else {
this.#pipeline.setMiddlewareFunction(i18nMiddleware);
}
this.#pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
removeLeadingForwardSlash,
removeTrailingForwardSlash,
} from '../../core/path.js';
import { createI18nMiddleware } from '../../i18n/middleware.js';
import { createI18nMiddleware, i18nPipelineHook } from '../../i18n/middleware.js';
import { runHookBuildGenerated } from '../../integrations/index.js';
import { getOutputDirectory, isServerLikeOutput } from '../../prerender/utils.js';
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
Expand Down Expand Up @@ -279,8 +279,7 @@ async function generatePage(
const i18nMiddleware = createI18nMiddleware(
pipeline.getManifest().i18n,
pipeline.getManifest().base,
pipeline.getManifest().trailingSlash,
pipeline.getManifest().routes.map((route) => route.routeData)
pipeline.getManifest().trailingSlash
);
if (config.experimental.i18n && i18nMiddleware) {
if (onRequest) {
Expand All @@ -290,6 +289,7 @@ async function generatePage(
} else {
pipeline.setMiddlewareFunction(i18nMiddleware);
}
pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else if (onRequest) {
pipeline.setMiddlewareFunction(onRequest as MiddlewareEndpointHandler);
}
Expand Down
20 changes: 20 additions & 0 deletions packages/astro/src/core/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ type EndpointResultHandler = (
result: Response
) => Promise<Response> | Response;

type PipelineHooks = {
before: PipelineHookFunction[];
};

export type PipelineHookFunction = (ctx: RenderContext, mod: ComponentInstance | undefined) => void;

/**
* This is the basic class of a pipeline.
*
Expand All @@ -23,6 +29,9 @@ type EndpointResultHandler = (
export class Pipeline {
env: Environment;
#onRequest?: MiddlewareEndpointHandler;
#hooks: PipelineHooks = {
before: [],
};
/**
* The handler accepts the *original* `Request` and result returned by the endpoint.
* It must return a `Response`.
Expand Down Expand Up @@ -75,6 +84,9 @@ export class Pipeline {
renderContext: RenderContext,
componentInstance: ComponentInstance | undefined
): Promise<Response> {
for (const hook of this.#hooks.before) {
hook(renderContext, componentInstance);
}
const result = await this.#tryRenderRoute(
renderContext,
this.env,
Expand Down Expand Up @@ -158,4 +170,12 @@ export class Pipeline {
throw new Error(`Couldn't find route of type [${renderContext.route.type}]`);
}
}

/**
* Store a function that will be called before starting the rendering phase.
* @param fn
*/
onBeforeRenderRoute(fn: PipelineHookFunction) {
this.#hooks.before.push(fn);
}
}
45 changes: 16 additions & 29 deletions packages/astro/src/i18n/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path';
import type { MiddlewareEndpointHandler, RouteData, SSRManifest } from '../@types/astro.js';
import type { RouteInfo } from '../core/app/types.js';
import type { PipelineHookFunction } from '../core/pipeline.js';

const routeDataSymbol = Symbol.for('astro.routeData');

// Checks if the pathname doesn't have any locale, exception for the defaultLocale, which is ignored on purpose
function checkIsLocaleFree(pathname: string, locales: string[]): boolean {
Expand All @@ -16,23 +19,25 @@ function checkIsLocaleFree(pathname: string, locales: string[]): boolean {
export function createI18nMiddleware(
i18n: SSRManifest['i18n'],
base: SSRManifest['base'],
trailingSlash: SSRManifest['trailingSlash'],
routes: RouteData[]
trailingSlash: SSRManifest['trailingSlash']
): MiddlewareEndpointHandler | undefined {
if (!i18n) {
return undefined;
}

// we get all the routes that aren't pages
const nonPagesRoutes = routes.filter((route) => {
return route.type !== 'page';
});

return async (context, next) => {
if (!i18n) {
return await next();
}

const routeData = Reflect.get(context.request, routeDataSymbol);
if (routeData) {
// If the route we're processing is not a page, then we ignore it
if ((routeData as RouteData).type !== 'page') {
return await next();
}
}

const url = context.url;
// We get the pathname
// Internally, Astro removes the `base` from the manifest data of the routes.
Expand All @@ -58,11 +63,6 @@ export function createI18nMiddleware(
headers: response.headers,
});
} else if (i18n.routingStrategy === 'prefix-always') {
// We want to do this check only here, because `prefix-other-locales` assumes that non localized folder are valid
if (shouldSkipRoute(astroPathname, nonPagesRoutes, locales)) {
return await next();
}

if (url.pathname === base + '/' || url.pathname === base) {
if (trailingSlash === 'always') {
return context.redirect(`${appendForwardSlash(joinPaths(base, i18n.defaultLocale))}`);
Expand Down Expand Up @@ -105,21 +105,8 @@ export function createI18nMiddleware(
}

/**
* Checks whether a route should be skipped from the middleware logic. A route should be not be skipped when:
* - it's the home
* - it contains any locale
* - the pathname belongs to a route that is not a page
* This pipeline hook attaches a `RouteData` object to the `Request`
*/
function shouldSkipRoute(pathname: string, pageRoutes: RouteData[], locales: string[]) {
if (!pathname.length || pathname === '/') {
return false;
}

if (locales.some((locale) => pathname.includes(`/${locale}`))) {
return false;
}

return pageRoutes.some((route) => {
return !route.pattern.test(pathname);
});
}
export const i18nPipelineHook: PipelineHookFunction = (ctx) => {
Reflect.set(ctx.request, routeDataSymbol, ctx.route);
};
6 changes: 3 additions & 3 deletions packages/astro/src/vite-plugin-astro-server/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { createRequest } from '../core/request.js';
import { matchAllRoutes } from '../core/routing/index.js';
import { isPage, resolveIdToUrl } from '../core/util.js';
import { createI18nMiddleware } from '../i18n/middleware.js';
import { createI18nMiddleware, i18nPipelineHook } from '../i18n/middleware.js';
import { getSortedPreloadedMatches } from '../prerender/routing.js';
import { isServerLikeOutput } from '../prerender/utils.js';
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
Expand Down Expand Up @@ -280,8 +280,7 @@ export async function handleRoute({
const i18Middleware = createI18nMiddleware(
config.experimental.i18n,
config.base,
config.trailingSlash,
manifestData.routes
config.trailingSlash
);

if (i18Middleware) {
Expand All @@ -290,6 +289,7 @@ export async function handleRoute({
} else {
pipeline.setMiddlewareFunction(i18Middleware);
}
pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else if (onRequest) {
pipeline.setMiddlewareFunction(onRequest);
}
Expand Down

0 comments on commit eb2789d

Please sign in to comment.