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

Refactor rendering for testing #5071

Merged
merged 3 commits into from
Oct 13, 2022
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
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