Skip to content

Commit

Permalink
Refactor rendering for testing (#5071)
Browse files Browse the repository at this point in the history
* Refactor rendering for testing

* Correctly pass the mod for endpoints
  • Loading branch information
matthewp committed Oct 13, 2022
1 parent 4866ff8 commit df453e4
Show file tree
Hide file tree
Showing 18 changed files with 497 additions and 300 deletions.
75 changes: 37 additions & 38 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { call as callEndpoint } from '../endpoint/index.js';
import { consoleLogDestination } from '../logger/console.js';
import { error } from '../logger/core.js';
import { joinPaths, prependForwardSlash } from '../path.js';
import { render } from '../render/core.js';
import { createEnvironment, Environment, createRenderContext, renderPage } from '../render/index.js';
import { RouteCache } from '../render/route-cache.js';
import {
createLinkStylesheetElementSet,
Expand All @@ -31,25 +31,48 @@ export interface MatchOptions {
}

export class App {
#env: Environment;
#manifest: Manifest;
#manifestData: ManifestData;
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
#routeCache: RouteCache;
#encoder = new TextEncoder();
#logging: LogOptions = {
dest: consoleLogDestination,
level: 'info',
};
#streaming: boolean;

constructor(manifest: Manifest, streaming = true) {
this.#manifest = manifest;
this.#manifestData = {
routes: manifest.routes.map((route) => route.routeData),
};
this.#routeDataToRouteInfo = new Map(manifest.routes.map((route) => [route.routeData, route]));
this.#routeCache = new RouteCache(this.#logging);
this.#streaming = streaming;
this.#env = createEnvironment({
adapterName: manifest.adapterName,
logging: this.#logging,
markdown: manifest.markdown,
mode: 'production',
renderers: manifest.renderers,
async resolve(specifier: string) {
if (!(specifier in manifest.entryModules)) {
throw new Error(`Unable to resolve [${specifier}]`);
}
const bundlePath = manifest.entryModules[specifier];
switch (true) {
case bundlePath.startsWith('data:'):
case bundlePath.length === 0: {
return bundlePath;
}
default: {
return prependForwardSlash(joinPaths(manifest.base, bundlePath));
}
}
},
routeCache: new RouteCache(this.#logging),
site: this.#manifest.site,
ssr: true,
streaming,
});
}
match(request: Request, { matchNotFound = false }: MatchOptions = {}): RouteData | undefined {
const url = new URL(request.url);
Expand Down Expand Up @@ -148,41 +171,17 @@ export class App {
}

try {
const response = await render({
adapterName: manifest.adapterName,
links,
logging: this.#logging,
markdown: manifest.markdown,
mod,
mode: 'production',
const ctx = createRenderContext({
request,
origin: url.origin,
pathname: url.pathname,
scripts,
renderers,
async resolve(specifier: string) {
if (!(specifier in manifest.entryModules)) {
throw new Error(`Unable to resolve [${specifier}]`);
}
const bundlePath = manifest.entryModules[specifier];
switch (true) {
case bundlePath.startsWith('data:'):
case bundlePath.length === 0: {
return bundlePath;
}
default: {
return prependForwardSlash(joinPaths(manifest.base, bundlePath));
}
}
},
links,
route: routeData,
routeCache: this.#routeCache,
site: this.#manifest.site,
ssr: true,
request,
streaming: this.#streaming,
status,
});

const response = await renderPage(mod, ctx, this.#env);
return response;
} catch (err: any) {
error(this.#logging, 'ssr', err.stack || err.message || String(err));
Expand All @@ -201,17 +200,17 @@ export class App {
): Promise<Response> {
const url = new URL(request.url);
const handler = mod as unknown as EndpointHandler;
const result = await callEndpoint(handler, {
logging: this.#logging,

const ctx = createRenderContext({
request,
origin: url.origin,
pathname: url.pathname,
request,
route: routeData,
routeCache: this.#routeCache,
ssr: true,
status,
});

const result = await callEndpoint(handler, this.#env, ctx);

if (result.type === 'response') {
if (result.response.headers.get('X-Astro-Response') === 'Not-Found') {
const fourOhFourRequest = new Request(new URL('/404', request.url));
Expand Down
27 changes: 14 additions & 13 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ import {
removeLeadingForwardSlash,
removeTrailingForwardSlash,
} from '../../core/path.js';
import type { RenderOptions } from '../../core/render/core';
import { runHookBuildGenerated } from '../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { call as callEndpoint } from '../endpoint/index.js';
import { debug, info } from '../logger/core.js';
import { render } from '../render/core.js';
import { createEnvironment, createRenderContext, renderPage } from '../render/index.js';
import { callGetStaticPaths } from '../render/route-cache.js';
import { createLinkStylesheetElementSet, createModuleScriptsSet } from '../render/ssr-element.js';
import { createRequest } from '../request.js';
Expand Down Expand Up @@ -360,19 +359,14 @@ async function generatePath(
opts.settings.config.build.format,
pageData.route.type
);
const options: RenderOptions = {
const env = createEnvironment({
adapterName: undefined,
links,
logging,
markdown: {
...settings.config.markdown,
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
},
mod,
mode: opts.mode,
origin,
pathname,
scripts,
renderers,
async resolve(specifier: string) {
const hashedFilePath = internals.entrySpecifierToBundleMap.get(specifier);
Expand All @@ -386,28 +380,35 @@ async function generatePath(
}
return prependForwardSlash(npath.posix.join(settings.config.base, hashedFilePath));
},
request: createRequest({ url, headers: new Headers(), logging, ssr }),
route: pageData.route,
routeCache,
site: settings.config.site
? new URL(settings.config.base, settings.config.site).toString()
: settings.config.site,
ssr,
streaming: true,
};
});
const ctx = createRenderContext({
origin,
pathname,
request: createRequest({ url, headers: new Headers(), logging, ssr }),
scripts,
links,
route: pageData.route,
});

let body: string;
let encoding: BufferEncoding | undefined;
if (pageData.route.type === 'endpoint') {
const result = await callEndpoint(mod as unknown as EndpointHandler, options);
const endpointHandler = mod as unknown as EndpointHandler;
const result = await callEndpoint(endpointHandler, env, ctx);

if (result.type === 'response') {
throw new Error(`Returning a Response from an endpoint is not supported in SSG mode.`);
}
body = result.body;
encoding = result.encoding;
} else {
const response = await render(options);
const response = await renderPage(mod, ctx, env);

// If there's a redirect or something, just do nothing.
if (response.status !== 200 || !response.body) {
Expand Down
20 changes: 12 additions & 8 deletions packages/astro/src/core/endpoint/dev/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import type { EndpointHandler } from '../../../@types/astro';
import type { SSROptions } from '../../render/dev';
import { preload } from '../../render/dev/index.js';
import { createRenderContext } from '../../render/index.js';
import { call as callEndpoint } from '../index.js';

export async function call(ssrOpts: SSROptions) {
const [, mod] = await preload(ssrOpts);
return await callEndpoint(mod as unknown as EndpointHandler, {
...ssrOpts,
ssr: ssrOpts.settings.config.output === 'server',
site: ssrOpts.settings.config.site,
adapterName: ssrOpts.settings.config.adapter?.name,
export async function call(options: SSROptions) {
const { env, preload: [,mod] } = options;
const endpointHandler = mod as unknown as EndpointHandler;

const ctx = createRenderContext({
request: options.request,
origin: options.origin,
pathname: options.pathname,
route: options.route
});

return await callEndpoint(endpointHandler, env, ctx);
}
40 changes: 17 additions & 23 deletions packages/astro/src/core/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { APIContext, EndpointHandler, Params } from '../../@types/astro';
import type { RenderOptions } from '../render/core';
import type { Environment, RenderContext } from '../render/index';

import { renderEndpoint } from '../../runtime/server/index.js';
import { ASTRO_VERSION } from '../constants.js';
Expand All @@ -8,21 +8,6 @@ import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';

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

export type EndpointOptions = Pick<
RenderOptions,
| 'logging'
| 'origin'
| 'request'
| 'route'
| 'routeCache'
| 'pathname'
| 'route'
| 'site'
| 'ssr'
| 'status'
| 'adapterName'
>;

type EndpointCallResult =
| {
type: 'simple';
Expand Down Expand Up @@ -83,25 +68,34 @@ function createAPIContext({

export async function call(
mod: EndpointHandler,
opts: EndpointOptions
env: Environment,
ctx: RenderContext
): Promise<EndpointCallResult> {
const paramsAndPropsResp = await getParamsAndProps({ ...opts, mod: mod as any });
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 Error(
`[getStaticPath] route pattern matched, but no matching static path found. (${opts.pathname})`
`[getStaticPath] route pattern matched, but no matching static path found. (${ctx.pathname})`
);
}
const [params, props] = paramsAndPropsResp;

const context = createAPIContext({
request: opts.request,
request: ctx.request,
params,
props,
site: opts.site,
adapterName: opts.adapterName,
site: env.site,
adapterName: env.adapterName,
});
const response = await renderEndpoint(mod, context, opts.ssr);

const response = await renderEndpoint(mod, context, env.ssr);

if (response instanceof Response) {
attachToResponse(response, context.cookies);
Expand Down
45 changes: 45 additions & 0 deletions packages/astro/src/core/render/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
import type {
ComponentInstance,
Params,
Props,
RouteData,
RuntimeMode,
SSRElement,
SSRLoadedRenderer,
} from '../../@types/astro';
import type { LogOptions } from '../logger/core.js';
import type { Environment } from './environment.js';

/**
* The RenderContext represents the parts of rendering that are specific to one request.
*/
export interface RenderContext {
request: Request;
origin: string;
pathname: string;
url: URL;
scripts?: Set<SSRElement>;
links?: Set<SSRElement>;
styles?: Set<SSRElement>;
route?: RouteData;
status?: number;
}

export type CreateRenderContextArgs = Partial<RenderContext> & {
origin?: string;
request: RenderContext['request'];
}

export function createRenderContext(options: CreateRenderContextArgs): RenderContext {
const request = options.request;
const url = new URL(request.url);
const origin = options.origin ?? url.origin;
const pathname = options.pathname ?? url.pathname;
return {
...options,
origin,
pathname,
url
};
}
Loading

0 comments on commit df453e4

Please sign in to comment.