Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement reroute in dev #10818

Merged
merged 10 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ export interface AstroGlobal<
* [Astro reference](https://docs.astro.build/en/guides/server-side-rendering/)
*/
redirect: AstroSharedContext['redirect'];
/**
* TODO add documentation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all the TODOs here intentional for this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they are all intentional and I plan to resolve them in a later PR

*/
reroute: AstroSharedContext['reroute'];
/**
* The <Astro.self /> element allows a component to reference itself recursively.
*
Expand Down Expand Up @@ -1918,6 +1922,18 @@ export interface AstroUserConfig {
origin?: boolean;
};
};

/**
* @docs
* @name experimental.rerouting
* @type {boolean}
* @default `false`
* @version 4.6.0
* @description
*
* TODO
*/
rerouting: boolean;
};
}

Expand Down Expand Up @@ -2479,6 +2495,11 @@ interface AstroSharedContext<
*/
redirect(path: string, status?: ValidRedirectStatus): Response;

/**
* TODO: add documentation
*/
reroute(reroutePayload: ReroutePayload): Promise<Response>;

/**
* Object accessed via Astro middleware
*/
Expand Down Expand Up @@ -2784,7 +2805,9 @@ export interface AstroIntegration {
};
}

export type MiddlewareNext = () => Promise<Response>;
export type ReroutePayload = string | URL | Request;

export type MiddlewareNext = (reroutePayload?: ReroutePayload) => Promise<Response>;
export type MiddlewareHandler = (
context: APIContext,
next: MiddlewareNext
Expand Down
15 changes: 14 additions & 1 deletion packages/astro/src/core/app/pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import type { RouteData, SSRElement, SSRResult } from '../../@types/astro.js';
import type {
ComponentInstance,
ReroutePayload,
RouteData,
SSRElement,
SSRResult,
} from '../../@types/astro.js';
import { Pipeline } from '../base-pipeline.js';
import { createModuleScriptElement, createStylesheetElementSet } from '../render/ssr-element.js';

Expand Down Expand Up @@ -41,4 +47,11 @@ export class AppPipeline extends Pipeline {
}

componentMetadata() {}
getComponentByRoute(_routeData: RouteData): Promise<ComponentInstance> {
throw new Error('unimplemented');
}

tryReroute(_reroutePayload: ReroutePayload): Promise<[RouteData, ComponentInstance]> {
throw new Error('unimplemented');
}
}
2 changes: 2 additions & 0 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export type SSRManifest = {
i18n: SSRManifestI18n | undefined;
middleware: MiddlewareHandler;
checkOrigin: boolean;
// TODO: remove once the experimental flag is removed
reroutingEnabled: boolean;
};

export type SSRManifestI18n = {
Expand Down
19 changes: 19 additions & 0 deletions packages/astro/src/core/base-pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type {
ComponentInstance,
MiddlewareHandler,
ReroutePayload,
RouteData,
RuntimeMode,
SSRLoadedRenderer,
Expand Down Expand Up @@ -59,6 +61,23 @@ export abstract class Pipeline {

abstract headElements(routeData: RouteData): Promise<HeadElements> | HeadElements;
abstract componentMetadata(routeData: RouteData): Promise<SSRResult['componentMetadata']> | void;

/**
* It attempts to retrieve the `RouteData` that matches the input `url`, and the component that belongs to the `RouteData`.
*
* ## Errors
*
* - if not `RouteData` is found
*
* @param {ReroutePayload} reroutePayload
*/
abstract tryReroute(reroutePayload: ReroutePayload): Promise<[RouteData, ComponentInstance]>;

/**
* Tells the pipeline how to retrieve a component give a `RouteData`
* @param routeData
*/
abstract getComponentByRoute(routeData: RouteData): Promise<ComponentInstance>;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ function createBuildManifest(
i18n: i18nManifest,
buildFormat: settings.config.build.format,
middleware,
reroutingEnabled: settings.config.experimental.rerouting,
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
};
}
19 changes: 18 additions & 1 deletion packages/astro/src/core/build/pipeline.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { RouteData, SSRLoadedRenderer, SSRResult } from '../../@types/astro.js';
import type {
RouteData,
SSRLoadedRenderer,
SSRResult,
MiddlewareHandler,
ReroutePayload,
ComponentInstance,
} from '../../@types/astro.js';
import { getOutputDirectory, isServerLikeOutput } from '../../prerender/utils.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import type { SSRManifest } from '../app/types.js';
Expand All @@ -21,6 +28,8 @@ import { getVirtualModulePageNameFromPath } from './plugins/util.js';
import { ASTRO_PAGE_EXTENSION_POST_PATTERN } from './plugins/util.js';
import type { PageBuildData, StaticBuildOptions } from './types.js';
import { i18nHasFallback } from './util.js';
import { defineMiddleware } from '../middleware/index.js';
import { undefined } from 'zod';

/**
* The build pipeline is responsible to gather the files emitted by the SSR build and generate the pages by executing these files.
Expand Down Expand Up @@ -225,4 +234,12 @@ export class BuildPipeline extends Pipeline {

return pages;
}

getComponentByRoute(_routeData: RouteData): Promise<ComponentInstance> {
throw new Error('unimplemented');
}

tryReroute(_reroutePayload: ReroutePayload): Promise<[RouteData, ComponentInstance]> {
throw new Error('unimplemented');
}
}
1 change: 1 addition & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,6 @@ function buildManifest(
i18n: i18nManifest,
buildFormat: settings.config.build.format,
checkOrigin: settings.config.experimental.security?.csrfProtection?.origin ?? false,
reroutingEnabled: settings.config.experimental.rerouting,
};
}
2 changes: 2 additions & 0 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const ASTRO_CONFIG_DEFAULTS = {
globalRoutePriority: false,
i18nDomains: false,
security: {},
rerouting: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };

Expand Down Expand Up @@ -525,6 +526,7 @@ export const AstroConfigSchema = z.object({
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.security),
i18nDomains: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.i18nDomains),
rerouting: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.rerouting),
})
.strict(
`Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/configuration-reference/#experimental-flags for a list of all current experiments.`
Expand Down
13 changes: 9 additions & 4 deletions packages/astro/src/core/middleware/callMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { APIContext, MiddlewareHandler, MiddlewareNext } from '../../@types/astro.js';
import type {
APIContext,
MiddlewareHandler,
MiddlewareNext,
ReroutePayload,
} from '../../@types/astro.js';
import { AstroError, AstroErrorData } from '../errors/index.js';

/**
Expand Down Expand Up @@ -38,13 +43,13 @@ import { AstroError, AstroErrorData } from '../errors/index.js';
export async function callMiddleware(
onRequest: MiddlewareHandler,
apiContext: APIContext,
responseFunction: () => Promise<Response> | Response
responseFunction: (reroutePayload?: ReroutePayload) => Promise<Response> | Response
): Promise<Response> {
let nextCalled = false;
let responseFunctionPromise: Promise<Response> | Response | undefined = undefined;
const next: MiddlewareNext = async () => {
const next: MiddlewareNext = async (payload) => {
nextCalled = true;
responseFunctionPromise = responseFunction();
responseFunctionPromise = responseFunction(payload);
return responseFunctionPromise;
};

Expand Down
14 changes: 9 additions & 5 deletions packages/astro/src/core/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import type { APIContext, MiddlewareHandler, Params } from '../../@types/astro.js';
import type { APIContext, MiddlewareHandler, Params, ReroutePayload } from '../../@types/astro.js';
import {
computeCurrentLocale,
computePreferredLocale,
computePreferredLocaleList,
} from '../../i18n/utils.js';
import { ASTRO_VERSION } from '../constants.js';
import { ASTRO_VERSION, clientLocalsSymbol, clientAddressSymbol } from '../constants.js';
import { AstroCookies } from '../cookies/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { sequence } from './sequence.js';

const clientAddressSymbol = Symbol.for('astro.clientAddress');
const clientLocalsSymbol = Symbol.for('astro.locals');

function defineMiddleware(fn: MiddlewareHandler) {
return fn;
}
Expand Down Expand Up @@ -49,13 +46,20 @@ function createContext({
const url = new URL(request.url);
const route = url.pathname;

// TODO verify that this function works in an edge middleware environment
const reroute = (_reroutePayload: ReroutePayload) => {
// return dummy response
return Promise.resolve(new Response(null));
};

return {
cookies: new AstroCookies(request),
request,
params,
site: undefined,
generator: `Astro v${ASTRO_VERSION}`,
props: {},
reroute,
redirect(path, status) {
return new Response(null, {
status: status || 302,
Expand Down
9 changes: 4 additions & 5 deletions packages/astro/src/core/middleware/sequence.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { APIContext, MiddlewareHandler } from '../../@types/astro.js';
import type { APIContext, MiddlewareHandler, ReroutePayload } from '../../@types/astro.js';
import { defineMiddleware } from './index.js';

// From SvelteKit: https://github.com/sveltejs/kit/blob/master/packages/kit/src/exports/hooks/sequence.js
Expand All @@ -10,10 +10,9 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
const filtered = handlers.filter((h) => !!h);
const length = filtered.length;
if (!length) {
const handler: MiddlewareHandler = defineMiddleware((context, next) => {
return defineMiddleware((context, next) => {
return next();
});
return handler;
}

return defineMiddleware((context, next) => {
Expand All @@ -24,11 +23,11 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
// @ts-expect-error
// SAFETY: Usually `next` always returns something in user land, but in `sequence` we are actually
// doing a loop over all the `next` functions, and eventually we call the last `next` that returns the `Response`.
const result = handle(handleContext, async () => {
const result = handle(handleContext, async (payload: ReroutePayload) => {
if (i < length - 1) {
return applyHandle(i + 1, handleContext);
} else {
return next();
return next(payload);
}
});
return result;
Expand Down
Loading
Loading