Skip to content

Commit

Permalink
feat: support middlewares in SSG for endpoints and pages
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Apr 6, 2023
1 parent 298c92a commit c4acb57
Show file tree
Hide file tree
Showing 15 changed files with 231 additions and 93 deletions.
19 changes: 8 additions & 11 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1414,11 +1414,6 @@ interface AstroSharedContext<Props extends Record<string, any> = Record<string,
* TODO documentation
*/
locals: Locals;

/**
* TODO documentation
*/
setLocal: (key: string, value: any) => void;
}

export interface APIContext<Props extends Record<string, any> = Record<string, any>>
Expand Down Expand Up @@ -1585,14 +1580,16 @@ export interface AstroIntegration {
};
}

export type MiddlewareResolve = (context: APIContext) => Promise<Response>;
export type MiddlewareHandler = (
export type MiddlewareResolve<R> = (context: APIContext) => Promise<R>;
export type MiddlewareHandler<R> = (
context: Readonly<APIContext>,
response: MiddlewareResolve
) => Promise<Response>;
response: MiddlewareResolve<R>
) => Promise<R>;

export type AstroMiddlewareInstance = {
onRequest?: MiddlewareHandler;
// NOTE: when updating this file with other functions,
// remember to update `plugin-page.ts` too, to add that function as a no-op function.
export type AstroMiddlewareInstance<R> = {
onRequest?: MiddlewareHandler<R>;
};

export interface AstroPluginOptions {
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ export class App {
status,
});

const result = await callEndpoint(handler, this.#env, ctx, this.#logging);
// TODO PLT-104 add adapter middleware here
const result = await callEndpoint(handler, this.#env, ctx, this.#logging, undefined);

if (result.type === 'response') {
if (result.response.headers.get('X-Astro-Response') === 'Not-Found') {
Expand Down
70 changes: 63 additions & 7 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import type { OutputAsset, OutputChunk } from 'rollup';
import { fileURLToPath } from 'url';
import type {
AstroConfig,
AstroMiddlewareInstance,
AstroSettings,
ComponentInstance,
EndpointHandler,
ImageTransform,
MiddlewareHandler,
RouteType,
SSRError,
SSRLoadedRenderer,
EndpointOutput,
} from '../../@types/astro';
import {
generateImage as generateImageInternal,
Expand All @@ -26,10 +29,20 @@ import {
} from '../../core/path.js';
import { runHookBuildGenerated } from '../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { call as callEndpoint, throwIfRedirectNotAllowed } from '../endpoint/index.js';
import { AstroError } from '../errors/index.js';
import {
call as callEndpoint,
createAPIContext,
throwIfRedirectNotAllowed,
} from '../endpoint/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { debug, info } from '../logger/core.js';
import { createEnvironment, createRenderContext, renderPage } from '../render/index.js';
import {
createEnvironment,
createRenderContext,
getParamsAndProps,
GetParamsAndPropsError,
renderPage,
} from '../render/index.js';
import { callGetStaticPaths } from '../render/route-cache.js';
import {
createAssetLink,
Expand All @@ -48,6 +61,7 @@ import {
} from './internal.js';
import type { PageBuildData, SingleFileBuiltModule, StaticBuildOptions } from './types';
import { getTimeStat } from './util.js';
import { callMiddleware } from '../middleware/index.js';

function shouldSkipDraft(pageModule: ComponentInstance, settings: AstroSettings): boolean {
return (
Expand Down Expand Up @@ -171,6 +185,7 @@ async function generatePage(
const scripts = pageInfo?.hoistedScript ?? null;

const pageModule = ssrEntry.pageMap?.get(pageData.component);
const middleware = ssrEntry.middleware;

if (!pageModule) {
throw new Error(
Expand Down Expand Up @@ -200,7 +215,7 @@ async function generatePage(

for (let i = 0; i < paths.length; i++) {
const path = paths[i];
await generatePath(path, opts, generationOptions);
await generatePath(path, opts, generationOptions, middleware);
const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd);
const timeIncrease = `(+${timeChange})`;
Expand Down Expand Up @@ -342,7 +357,8 @@ function getUrlForPath(
async function generatePath(
pathname: string,
opts: StaticBuildOptions,
gopts: GeneratePathOptions
gopts: GeneratePathOptions,
middleware: AstroMiddlewareInstance<unknown>
) {
const { settings, logging, origin, routeCache } = opts;
const { mod, internals, linkIds, scripts: hoistedScripts, pageData, renderers } = gopts;
Expand Down Expand Up @@ -428,6 +444,7 @@ async function generatePath(
ssr,
streaming: true,
});

const ctx = createRenderContext({
origin,
pathname,
Expand All @@ -442,7 +459,14 @@ async function generatePath(
let encoding: BufferEncoding | undefined;
if (pageData.route.type === 'endpoint') {
const endpointHandler = mod as unknown as EndpointHandler;
const result = await callEndpoint(endpointHandler, env, ctx, logging);

const result = await callEndpoint(
endpointHandler,
env,
ctx,
logging,
middleware as AstroMiddlewareInstance<Response | EndpointOutput>
);

if (result.type === 'response') {
throwIfRedirectNotAllowed(result.response, opts.settings.config);
Expand All @@ -457,7 +481,39 @@ async function generatePath(
} else {
let response: Response;
try {
response = await renderPage(mod, ctx, env);
const paramsAndPropsResp = await getParamsAndProps({
mod: mod as any,
route: ctx.route,
routeCache: env.routeCache,
pathname: ctx.pathname,
logging: env.logging,
ssr: env.ssr,
});

if (paramsAndPropsResp === GetParamsAndPropsError.NoMatchingStaticPath) {
throw new AstroError({
...AstroErrorData.NoMatchingStaticPathFound,
message: AstroErrorData.NoMatchingStaticPathFound.message(ctx.pathname),
hint: ctx.route?.component
? AstroErrorData.NoMatchingStaticPathFound.hint([ctx.route?.component])
: '',
});
}
const [params, props] = paramsAndPropsResp;

const context = createAPIContext({
request: ctx.request,
params,
props,
site: env.site,
adapterName: env.adapterName,
});
// If the user doesn't configure a middleware, the rollup plugin emits a no-op function,
// so it's safe to use `callMiddleware` regardless
let onRequest = middleware.onRequest as MiddlewareHandler<Response>;
response = await callMiddleware<Response>(onRequest, context, () => {
return renderPage(mod, ctx, env, context);
});
} catch (err) {
if (!AstroError.is(err) && !(err as SSRError).id && typeof err === 'object') {
(err as SSRError).id = pageData.component;
Expand Down
12 changes: 9 additions & 3 deletions packages/astro/src/core/build/plugins/plugin-pages.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Plugin as VitePlugin } from 'vite';
import type { AstroBuildPlugin } from '../plugin';
import type { StaticBuildOptions } from '../types';

import { pagesVirtualModuleId, resolvedPagesVirtualModuleId } from '../../app/index.js';
import { addRollupInput } from '../add-rollup-input.js';
import { eachPageData, hasPrerenderedPages, type BuildInternals } from '../internal.js';
import { MIDDLEWARE_PATH_SEGMENT_NAME } from '../../constants.js';

export function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
return {
Expand All @@ -22,8 +22,10 @@ export function vitePluginPages(opts: StaticBuildOptions, internals: BuildIntern
}
},

load(id) {
async load(id) {
if (id === resolvedPagesVirtualModuleId) {
const middlewareId = await this.resolve(`./src/${MIDDLEWARE_PATH_SEGMENT_NAME}`);

let importMap = '';
let imports = [];
let i = 0;
Expand All @@ -47,8 +49,12 @@ export function vitePluginPages(opts: StaticBuildOptions, internals: BuildIntern

const def = `${imports.join('\n')}
${middlewareId ? `import * as _middleware from "${middlewareId.id}";` : ''}
export const pageMap = new Map([${importMap}]);
export const renderers = [${rendererItems}];`;
export const renderers = [${rendererItems}];
export const middleware = ${middlewareId ? '_middleware' : '{ onRequest() {} }'};
`;

return def;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/core/build/static-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '../../core/build/internal.js';
import { emptyDir, removeEmptyDirs } from '../../core/fs/index.js';
import { appendForwardSlash, prependForwardSlash } from '../../core/path.js';
import { isModeServerWithNoAdapter } from '../../core/util.js';
import { isModeServerWithNoAdapter, viteID } from '../../core/util.js';
import { runHookBuildSetup } from '../../integrations/index.js';
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { resolvedPagesVirtualModuleId } from '../app/index.js';
Expand All @@ -25,6 +25,7 @@ import { createPluginContainer, type AstroBuildPluginContainer } from './plugin.
import { registerAllPlugins } from './plugins/index.js';
import type { PageBuildData, StaticBuildOptions } from './types';
import { getTimeStat } from './util.js';
import { MIDDLEWARE_PATH_SEGMENT_NAME } from '../constants.js';

export async function viteBuild(opts: StaticBuildOptions) {
const { allPages, settings } = opts;
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/build/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { default as vite, InlineConfig } from 'vite';
import type {
AstroConfig,
AstroMiddlewareInstance,
AstroSettings,
BuildConfig,
ComponentInstance,
Expand Down Expand Up @@ -44,6 +45,7 @@ export interface StaticBuildOptions {

export interface SingleFileBuiltModule {
pageMap: Map<ComponentPath, ComponentInstance>;
middleware: AstroMiddlewareInstance<unknown>;
renderers: SSRLoadedRenderer[];
}

Expand Down
1 change: 0 additions & 1 deletion packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const ASTRO_CONFIG_DEFAULTS: AstroUserConfig & any = {
open: false,
},
integrations: [],
middlewares: [],
markdown: {
drafts: false,
...markdownConfigDefaults,
Expand Down
15 changes: 13 additions & 2 deletions packages/astro/src/core/endpoint/dev/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { EndpointHandler } from '../../../@types/astro';
import type {
AstroMiddlewareInstance,
EndpointHandler,
EndpointOutput,
} from '../../../@types/astro';
import type { LogOptions } from '../../logger/core';
import type { SSROptions } from '../../render/dev';
import { createRenderContext } from '../../render/index.js';
Expand All @@ -8,6 +12,7 @@ export async function call(options: SSROptions, logging: LogOptions) {
const {
env,
preload: [, mod],
middleware,
} = options;
const endpointHandler = mod as unknown as EndpointHandler;

Expand All @@ -18,5 +23,11 @@ export async function call(options: SSROptions, logging: LogOptions) {
route: options.route,
});

return await callEndpoint(endpointHandler, env, ctx, logging);
return await callEndpoint(
endpointHandler,
env,
ctx,
logging,
middleware as AstroMiddlewareInstance<Response | EndpointOutput>
);
}
24 changes: 21 additions & 3 deletions packages/astro/src/core/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import type { APIContext, AstroConfig, EndpointHandler, Params } from '../../@types/astro';
import type {
APIContext,
AstroConfig,
AstroMiddlewareInstance,
EndpointHandler,
EndpointOutput,
MiddlewareHandler,
Params,
} from '../../@types/astro';
import type { Environment, RenderContext } from '../render/index';

import { renderEndpoint } from '../../runtime/server/index.js';
Expand All @@ -7,6 +15,7 @@ import { AstroCookies, attachToResponse } from '../cookies/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import { warn, type LogOptions } from '../logger/core.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
import { callMiddleware } from '../middleware/index.js';

const clientAddressSymbol = Symbol.for('astro.clientAddress');
const clientLocalsSymbol = Symbol.for('astro.locals');
Expand Down Expand Up @@ -75,7 +84,8 @@ export async function call(
mod: EndpointHandler,
env: Environment,
ctx: RenderContext,
logging: LogOptions
logging: LogOptions,
middleware: AstroMiddlewareInstance<Response | EndpointOutput> | undefined
): Promise<EndpointCallResult> {
const paramsAndPropsResp = await getParamsAndProps({
mod: mod as any,
Expand Down Expand Up @@ -105,7 +115,15 @@ export async function call(
adapterName: env.adapterName,
});

const response = await renderEndpoint(mod, context, env.ssr);
let response;
if (middleware && middleware.onRequest) {
const onRequest = middleware.onRequest as MiddlewareHandler<Response | EndpointOutput>;
response = await callMiddleware<Response | EndpointOutput>(onRequest, context, () => {
return renderEndpoint(mod, context, env.ssr);
});
} else {
response = await renderEndpoint(mod, context, env.ssr);
}

if (response instanceof Response) {
attachToResponse(response, context.cookies);
Expand Down
Loading

0 comments on commit c4acb57

Please sign in to comment.