Refactor rendering for testing (#5071)
* Refactor rendering for testing * Correctly pass the mod for endpoints
This commit is contained in:
parent
4866ff882a
commit
df453e420f
18 changed files with 498 additions and 301 deletions
|
@ -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,
|
||||
|
@ -31,16 +31,15 @@ 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;
|
||||
|
@ -48,8 +47,32 @@ export class App {
|
|||
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);
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
|
|
@ -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';
|
||||
|
@ -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);
|
||||
|
@ -386,20 +380,27 @@ 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.`);
|
||||
|
@ -407,7 +408,7 @@ async function generatePath(
|
|||
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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
|
|
45
packages/astro/src/core/render/context.ts
Normal file
45
packages/astro/src/core/render/context.ts
Normal file
|
@ -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
|
||||
};
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
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';
|
||||
import type { RenderContext } from './context.js';
|
||||
|
||||
import { Fragment, renderPage } from '../../runtime/server/index.js';
|
||||
import { Fragment, renderPage as runtimeRenderPage } from '../../runtime/server/index.js';
|
||||
import { attachToResponse } from '../cookies/index.js';
|
||||
import { getParams } from '../routing/params.js';
|
||||
import { createResult } from './result.js';
|
||||
|
@ -67,90 +65,46 @@ export async function getParamsAndProps(
|
|||
return [params, pageProps];
|
||||
}
|
||||
|
||||
export interface RenderOptions {
|
||||
adapterName?: string;
|
||||
logging: LogOptions;
|
||||
links: Set<SSRElement>;
|
||||
styles?: Set<SSRElement>;
|
||||
markdown: MarkdownRenderingOptions;
|
||||
mod: ComponentInstance;
|
||||
mode: RuntimeMode;
|
||||
origin: string;
|
||||
pathname: string;
|
||||
scripts: Set<SSRElement>;
|
||||
resolve: (s: string) => Promise<string>;
|
||||
renderers: SSRLoadedRenderer[];
|
||||
route?: RouteData;
|
||||
routeCache: RouteCache;
|
||||
site?: string;
|
||||
ssr: boolean;
|
||||
streaming: boolean;
|
||||
request: Request;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export async function render(opts: RenderOptions): Promise<Response> {
|
||||
const {
|
||||
adapterName,
|
||||
links,
|
||||
styles,
|
||||
logging,
|
||||
origin,
|
||||
markdown,
|
||||
mod,
|
||||
mode,
|
||||
pathname,
|
||||
scripts,
|
||||
renderers,
|
||||
request,
|
||||
resolve,
|
||||
route,
|
||||
routeCache,
|
||||
site,
|
||||
ssr,
|
||||
streaming,
|
||||
status = 200,
|
||||
} = opts;
|
||||
|
||||
export async function renderPage(mod: ComponentInstance, ctx: RenderContext, env: Environment) {
|
||||
const paramsAndPropsRes = await getParamsAndProps({
|
||||
logging,
|
||||
logging: env.logging,
|
||||
mod,
|
||||
route,
|
||||
routeCache,
|
||||
pathname,
|
||||
ssr,
|
||||
route: ctx.route,
|
||||
routeCache: env.routeCache,
|
||||
pathname: ctx.pathname,
|
||||
ssr: env.ssr,
|
||||
});
|
||||
|
||||
if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) {
|
||||
throw new Error(
|
||||
`[getStaticPath] route pattern matched, but no matching static path found. (${pathname})`
|
||||
`[getStaticPath] route pattern matched, but no matching static path found. (${ctx.pathname})`
|
||||
);
|
||||
}
|
||||
const [params, pageProps] = paramsAndPropsRes;
|
||||
|
||||
// Validate the page component before rendering the page
|
||||
const Component = await mod.default;
|
||||
const Component = mod.default;
|
||||
if (!Component)
|
||||
throw new Error(`Expected an exported Astro component but received typeof ${typeof Component}`);
|
||||
|
||||
const result = createResult({
|
||||
adapterName,
|
||||
links,
|
||||
styles,
|
||||
logging,
|
||||
markdown,
|
||||
mode,
|
||||
origin,
|
||||
adapterName: env.adapterName,
|
||||
links: ctx.links,
|
||||
styles: ctx.styles,
|
||||
logging: env.logging,
|
||||
markdown: env.markdown,
|
||||
mode: env.mode,
|
||||
origin: ctx.origin,
|
||||
params,
|
||||
props: pageProps,
|
||||
pathname,
|
||||
resolve,
|
||||
renderers,
|
||||
request,
|
||||
site,
|
||||
scripts,
|
||||
ssr,
|
||||
status,
|
||||
pathname: ctx.pathname,
|
||||
resolve: env.resolve,
|
||||
renderers: env.renderers,
|
||||
request: ctx.request,
|
||||
site: env.site,
|
||||
scripts: ctx.scripts,
|
||||
ssr: env.ssr,
|
||||
status: ctx.status ?? 200,
|
||||
});
|
||||
|
||||
// Support `export const components` for `MDX` pages
|
||||
|
@ -165,7 +119,7 @@ export async function render(opts: RenderOptions): Promise<Response> {
|
|||
});
|
||||
}
|
||||
|
||||
const response = await renderPage(result, Component, pageProps, null, streaming);
|
||||
const response = await runtimeRenderPage(result, Component, pageProps, null, env.streaming);
|
||||
|
||||
// If there is an Astro.cookies instance, attach it to the response so that
|
||||
// adapters can grab the Set-Cookie headers.
|
||||
|
|
47
packages/astro/src/core/render/dev/environment.ts
Normal file
47
packages/astro/src/core/render/dev/environment.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
|
||||
import type { ViteDevServer } from 'vite';
|
||||
import type {
|
||||
AstroSettings,
|
||||
RuntimeMode,
|
||||
SSRLoadedRenderer,
|
||||
} from '../../../@types/astro';
|
||||
import type { Environment } from '../index';
|
||||
import type { LogOptions } from '../../logger/core.js';
|
||||
import { RouteCache } from '../route-cache.js';
|
||||
import { createEnvironment } from '../index.js';
|
||||
import { createResolve } from './resolve.js';
|
||||
|
||||
export type DevelopmentEnvironment = Environment & {
|
||||
settings: AstroSettings;
|
||||
viteServer: ViteDevServer;
|
||||
}
|
||||
|
||||
export function createDevelopmentEnvironment(
|
||||
settings: AstroSettings,
|
||||
logging: LogOptions,
|
||||
viteServer: ViteDevServer
|
||||
): DevelopmentEnvironment {
|
||||
const mode: RuntimeMode = 'development';
|
||||
let env = createEnvironment({
|
||||
adapterName: settings.adapter?.name,
|
||||
logging,
|
||||
markdown: {
|
||||
...settings.config.markdown,
|
||||
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
|
||||
},
|
||||
mode,
|
||||
// This will be overridden in the dev server
|
||||
renderers: [],
|
||||
resolve: createResolve(viteServer),
|
||||
routeCache: new RouteCache(logging, mode),
|
||||
site: settings.config.site,
|
||||
ssr: settings.config.output === 'server',
|
||||
streaming: true,
|
||||
});
|
||||
|
||||
return {
|
||||
...env,
|
||||
viteServer,
|
||||
settings
|
||||
};
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { fileURLToPath } from 'url';
|
||||
import type { ViteDevServer } from 'vite';
|
||||
import type {
|
||||
AstroRenderer,
|
||||
AstroSettings,
|
||||
ComponentInstance,
|
||||
RouteData,
|
||||
|
@ -9,16 +8,20 @@ import type {
|
|||
SSRElement,
|
||||
SSRLoadedRenderer,
|
||||
} from '../../../@types/astro';
|
||||
import type { DevelopmentEnvironment } from './environment';
|
||||
import { PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
|
||||
import { LogOptions } from '../../logger/core.js';
|
||||
import { isPage, resolveIdToUrl } from '../../util.js';
|
||||
import { render as coreRender } from '../core.js';
|
||||
import { renderPage as coreRenderPage, createRenderContext } from '../index.js';
|
||||
import { RouteCache } from '../route-cache.js';
|
||||
import { collectMdMetadata } from '../util.js';
|
||||
import { getStylesForURL } from './css.js';
|
||||
import { getScriptsForURL } from './scripts.js';
|
||||
import { loadRenderer, filterFoundRenderers } from '../renderer.js';
|
||||
export { createDevelopmentEnvironment } from './environment.js';
|
||||
export type { DevelopmentEnvironment };
|
||||
|
||||
export interface SSROptions {
|
||||
export interface SSROptionsOld {
|
||||
/** an instance of the AstroSettings */
|
||||
settings: AstroSettings;
|
||||
/** location of file on disk */
|
||||
|
@ -41,72 +44,81 @@ export interface SSROptions {
|
|||
request: Request;
|
||||
}
|
||||
|
||||
export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance];
|
||||
/*
|
||||
filePath: options.filePath
|
||||
});
|
||||
|
||||
const svelteStylesRE = /svelte\?svelte&type=style/;
|
||||
const ctx = createRenderContext({
|
||||
request: options.request,
|
||||
origin: options.origin,
|
||||
pathname: options.pathname,
|
||||
scripts,
|
||||
links,
|
||||
styles,
|
||||
route: options.route
|
||||
*/
|
||||
|
||||
export interface SSROptions {
|
||||
/** The environment instance */
|
||||
env: DevelopmentEnvironment;
|
||||
/** location of file on disk */
|
||||
filePath: URL;
|
||||
/** production website */
|
||||
origin: string;
|
||||
/** the web request (needed for dynamic routes) */
|
||||
pathname: string;
|
||||
/** The renderers and instance */
|
||||
preload: ComponentPreload;
|
||||
/** Request */
|
||||
request: Request;
|
||||
/** optional, in case we need to render something outside of a dev server */
|
||||
route?: RouteData;
|
||||
|
||||
async function loadRenderer(
|
||||
viteServer: ViteDevServer,
|
||||
renderer: AstroRenderer
|
||||
): Promise<SSRLoadedRenderer> {
|
||||
const mod = (await viteServer.ssrLoadModule(renderer.serverEntrypoint)) as {
|
||||
default: SSRLoadedRenderer['ssr'];
|
||||
};
|
||||
return { ...renderer, ssr: mod.default };
|
||||
}
|
||||
|
||||
export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance];
|
||||
|
||||
export async function loadRenderers(
|
||||
viteServer: ViteDevServer,
|
||||
settings: AstroSettings
|
||||
): Promise<SSRLoadedRenderer[]> {
|
||||
return Promise.all(settings.renderers.map((r) => loadRenderer(viteServer, r)));
|
||||
const loader = (entry: string) => viteServer.ssrLoadModule(entry);
|
||||
const renderers = await Promise.all(settings.renderers.map(r => loadRenderer(r, loader)));
|
||||
return filterFoundRenderers(renderers);
|
||||
}
|
||||
|
||||
export async function preload({
|
||||
settings,
|
||||
env,
|
||||
filePath,
|
||||
viteServer,
|
||||
}: Pick<SSROptions, 'settings' | 'filePath' | 'viteServer'>): Promise<ComponentPreload> {
|
||||
}: Pick<SSROptions, 'env' | 'filePath'>): Promise<ComponentPreload> {
|
||||
// Important: This needs to happen first, in case a renderer provides polyfills.
|
||||
const renderers = await loadRenderers(viteServer, settings);
|
||||
const renderers = await loadRenderers(env.viteServer, env.settings);
|
||||
// Load the module from the Vite SSR Runtime.
|
||||
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
|
||||
if (viteServer.config.mode === 'development' || !mod?.$$metadata) {
|
||||
const mod = (await env.viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
|
||||
if (env.viteServer.config.mode === 'development' || !mod?.$$metadata) {
|
||||
return [renderers, mod];
|
||||
}
|
||||
|
||||
// append all nested markdown metadata to mod.$$metadata
|
||||
const modGraph = await viteServer.moduleGraph.getModuleByUrl(fileURLToPath(filePath));
|
||||
const modGraph = await env.viteServer.moduleGraph.getModuleByUrl(fileURLToPath(filePath));
|
||||
if (modGraph) {
|
||||
await collectMdMetadata(mod.$$metadata, modGraph, viteServer);
|
||||
await collectMdMetadata(mod.$$metadata, modGraph, env.viteServer);
|
||||
}
|
||||
|
||||
return [renderers, mod];
|
||||
}
|
||||
|
||||
/** use Vite to SSR */
|
||||
export async function render(
|
||||
renderers: SSRLoadedRenderer[],
|
||||
mod: ComponentInstance,
|
||||
ssrOpts: SSROptions
|
||||
): Promise<Response> {
|
||||
const {
|
||||
settings,
|
||||
filePath,
|
||||
logging,
|
||||
mode,
|
||||
origin,
|
||||
pathname,
|
||||
request,
|
||||
route,
|
||||
routeCache,
|
||||
viteServer,
|
||||
} = ssrOpts;
|
||||
interface GetScriptsAndStylesParams {
|
||||
env: DevelopmentEnvironment;
|
||||
filePath: URL;
|
||||
}
|
||||
|
||||
async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams) {
|
||||
// Add hoisted script tags
|
||||
const scripts = await getScriptsForURL(filePath, viteServer);
|
||||
const scripts = await getScriptsForURL(filePath, env.viteServer);
|
||||
|
||||
// Inject HMR scripts
|
||||
if (isPage(filePath, settings) && mode === 'development') {
|
||||
if (isPage(filePath, env.settings) && env.mode === 'development') {
|
||||
scripts.add({
|
||||
props: { type: 'module', src: '/@vite/client' },
|
||||
children: '',
|
||||
|
@ -114,20 +126,20 @@ export async function render(
|
|||
scripts.add({
|
||||
props: {
|
||||
type: 'module',
|
||||
src: await resolveIdToUrl(viteServer, 'astro/runtime/client/hmr.js'),
|
||||
src: await resolveIdToUrl(env.viteServer, 'astro/runtime/client/hmr.js'),
|
||||
},
|
||||
children: '',
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: We should allow adding generic HTML elements to the head, not just scripts
|
||||
for (const script of settings.scripts) {
|
||||
for (const script of env.settings.scripts) {
|
||||
if (script.stage === 'head-inline') {
|
||||
scripts.add({
|
||||
props: {},
|
||||
children: script.content,
|
||||
});
|
||||
} else if (script.stage === 'page' && isPage(filePath, settings)) {
|
||||
} else if (script.stage === 'page' && isPage(filePath, env.settings)) {
|
||||
scripts.add({
|
||||
props: { type: 'module', src: `/@id/${PAGE_SCRIPT_ID}` },
|
||||
children: '',
|
||||
|
@ -136,7 +148,7 @@ export async function render(
|
|||
}
|
||||
|
||||
// Pass framework CSS in as style tags to be appended to the page.
|
||||
const { urls: styleUrls, stylesMap } = await getStylesForURL(filePath, viteServer, mode);
|
||||
const { urls: styleUrls, stylesMap } = await getStylesForURL(filePath, env.viteServer, env.mode);
|
||||
let links = new Set<SSRElement>();
|
||||
[...styleUrls].forEach((href) => {
|
||||
links.add({
|
||||
|
@ -164,54 +176,31 @@ export async function render(
|
|||
children: content,
|
||||
});
|
||||
});
|
||||
|
||||
return { scripts, styles, links };
|
||||
}
|
||||
|
||||
let response = await coreRender({
|
||||
adapterName: settings.config.adapter?.name,
|
||||
links,
|
||||
styles,
|
||||
logging,
|
||||
markdown: {
|
||||
...settings.config.markdown,
|
||||
isAstroFlavoredMd: settings.config.legacy.astroFlavoredMarkdown,
|
||||
},
|
||||
mod,
|
||||
mode,
|
||||
origin,
|
||||
pathname,
|
||||
scripts,
|
||||
// Resolves specifiers in the inline hydrated scripts, such as:
|
||||
// - @astrojs/preact/client.js
|
||||
// - @/components/Foo.vue
|
||||
// - /Users/macos/project/src/Foo.vue
|
||||
// - C:/Windows/project/src/Foo.vue (normalized slash)
|
||||
async resolve(s: string) {
|
||||
const url = await resolveIdToUrl(viteServer, s);
|
||||
// Vite does not resolve .jsx -> .tsx when coming from hydration script import,
|
||||
// clip it so Vite is able to resolve implicitly.
|
||||
if (url.startsWith('/@fs') && url.endsWith('.jsx')) {
|
||||
return url.slice(0, -4);
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
},
|
||||
renderers,
|
||||
request,
|
||||
route,
|
||||
routeCache,
|
||||
site: settings.config.site
|
||||
? new URL(settings.config.base, settings.config.site).toString()
|
||||
: undefined,
|
||||
ssr: settings.config.output === 'server',
|
||||
streaming: true,
|
||||
export async function renderPage(options: SSROptions): Promise<Response> {
|
||||
const [renderers, mod] = options.preload;
|
||||
|
||||
// Override the environment's renderers. This ensures that if renderers change (HMR)
|
||||
// The new instances are passed through.
|
||||
options.env.renderers = renderers;
|
||||
|
||||
const { scripts, links, styles } = await getScriptsAndStyles({
|
||||
env: options.env,
|
||||
filePath: options.filePath
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
const ctx = createRenderContext({
|
||||
request: options.request,
|
||||
origin: options.origin,
|
||||
pathname: options.pathname,
|
||||
scripts,
|
||||
links,
|
||||
styles,
|
||||
route: options.route
|
||||
});
|
||||
|
||||
export async function ssr(
|
||||
preloadedComponent: ComponentPreload,
|
||||
ssrOpts: SSROptions
|
||||
): Promise<Response> {
|
||||
const [renderers, mod] = preloadedComponent;
|
||||
return await render(renderers, mod, ssrOpts); // NOTE: without "await", errors won’t get caught below
|
||||
return await coreRenderPage(mod, ctx, options.env); // NOTE: without "await", errors won’t get caught below
|
||||
}
|
||||
|
|
20
packages/astro/src/core/render/dev/resolve.ts
Normal file
20
packages/astro/src/core/render/dev/resolve.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import type { ViteDevServer } from 'vite';
|
||||
import { isPage, resolveIdToUrl } from '../../util.js';
|
||||
|
||||
export function createResolve(viteServer: ViteDevServer) {
|
||||
// Resolves specifiers in the inline hydrated scripts, such as:
|
||||
// - @astrojs/preact/client.js
|
||||
// - @/components/Foo.vue
|
||||
// - /Users/macos/project/src/Foo.vue
|
||||
// - C:/Windows/project/src/Foo.vue (normalized slash)
|
||||
return async function(s: string) {
|
||||
const url = await resolveIdToUrl(viteServer, s);
|
||||
// Vite does not resolve .jsx -> .tsx when coming from hydration script import,
|
||||
// clip it so Vite is able to resolve implicitly.
|
||||
if (url.startsWith('/@fs') && url.endsWith('.jsx')) {
|
||||
return url.slice(0, -4);
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
};
|
||||
}
|
52
packages/astro/src/core/render/environment.ts
Normal file
52
packages/astro/src/core/render/environment.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import type { MarkdownRenderingOptions } from '@astrojs/markdown-remark';
|
||||
import type {
|
||||
RuntimeMode,
|
||||
SSRLoadedRenderer,
|
||||
} from '../../@types/astro';
|
||||
import type { LogOptions } from '../logger/core.js';
|
||||
import { RouteCache } from './route-cache.js';
|
||||
|
||||
/**
|
||||
* An environment represents the static parts of rendering that do not change
|
||||
* between requests. These are mostly known when the server first starts up and do not change.
|
||||
* Thus they can be created once and passed through to renderPage on each request.
|
||||
*/
|
||||
export interface Environment {
|
||||
adapterName?: string;
|
||||
/** logging options */
|
||||
logging: LogOptions;
|
||||
markdown: MarkdownRenderingOptions;
|
||||
/** "development" or "production" */
|
||||
mode: RuntimeMode;
|
||||
renderers: SSRLoadedRenderer[];
|
||||
resolve: (s: string) => Promise<string>;
|
||||
routeCache: RouteCache;
|
||||
site?: string;
|
||||
ssr: boolean;
|
||||
streaming: boolean;
|
||||
}
|
||||
|
||||
export type CreateEnvironmentArgs = Environment;
|
||||
|
||||
export function createEnvironment(options: CreateEnvironmentArgs): Environment {
|
||||
return options;
|
||||
}
|
||||
|
||||
export type CreateBasicEnvironmentArgs = Partial<Environment> & {
|
||||
logging: CreateEnvironmentArgs['logging'];
|
||||
}
|
||||
|
||||
export function createBasicEnvironment(options: CreateBasicEnvironmentArgs): Environment {
|
||||
const mode = options.mode ?? 'development';
|
||||
return createEnvironment({
|
||||
...options,
|
||||
markdown: options.markdown ?? {},
|
||||
mode,
|
||||
renderers: options.renderers ?? [],
|
||||
resolve: options.resolve ?? ((s: string) => Promise.resolve(s)),
|
||||
routeCache: new RouteCache(options.logging, mode),
|
||||
ssr: options.ssr ?? true,
|
||||
streaming: options.streaming ?? true
|
||||
});
|
||||
}
|
||||
|
22
packages/astro/src/core/render/index.ts
Normal file
22
packages/astro/src/core/render/index.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
export type {
|
||||
Environment
|
||||
} from './environment';
|
||||
export type {
|
||||
RenderContext
|
||||
} from './context';
|
||||
|
||||
export {
|
||||
createBasicEnvironment,
|
||||
createEnvironment
|
||||
} from './environment.js';
|
||||
export {
|
||||
createRenderContext
|
||||
} from './context.js';
|
||||
export {
|
||||
getParamsAndProps,
|
||||
GetParamsAndPropsError,
|
||||
renderPage,
|
||||
} from './core.js';
|
||||
export {
|
||||
loadRenderer
|
||||
} from './renderer.js';
|
28
packages/astro/src/core/render/renderer.ts
Normal file
28
packages/astro/src/core/render/renderer.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import type { AstroRenderer, SSRLoadedRenderer } from '../../@types/astro';
|
||||
|
||||
export type RendererServerEntrypointModule = {
|
||||
default: SSRLoadedRenderer['ssr'];
|
||||
};
|
||||
export type MaybeRendererServerEntrypointModule = Partial<RendererServerEntrypointModule>;
|
||||
export type RendererLoader = (entryPoint: string) => Promise<MaybeRendererServerEntrypointModule>;
|
||||
|
||||
export async function loadRenderer(renderer: AstroRenderer, loader: RendererLoader): Promise<SSRLoadedRenderer | undefined> {
|
||||
const mod = await loader(renderer.serverEntrypoint);
|
||||
if(typeof mod.default !== 'undefined') {
|
||||
return createLoadedRenderer(renderer, mod as RendererServerEntrypointModule);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function filterFoundRenderers(renderers: Array<SSRLoadedRenderer | undefined>): SSRLoadedRenderer[] {
|
||||
return renderers.filter((renderer): renderer is SSRLoadedRenderer => {
|
||||
return !!renderer;
|
||||
});
|
||||
}
|
||||
|
||||
export function createLoadedRenderer(renderer: AstroRenderer, mod: RendererServerEntrypointModule): SSRLoadedRenderer {
|
||||
return {
|
||||
...renderer,
|
||||
ssr: mod.default
|
||||
};
|
||||
}
|
|
@ -129,7 +129,7 @@ class Slots {
|
|||
let renderMarkdown: any = null;
|
||||
|
||||
export function createResult(args: CreateResultArgs): SSRResult {
|
||||
const { markdown, params, pathname, props: pageProps, renderers, request, resolve } = args;
|
||||
const { markdown, params, pathname, renderers, request, resolve } = args;
|
||||
|
||||
const url = new URL(request.url);
|
||||
const headers = new Headers();
|
||||
|
|
9
packages/astro/src/jsx/component.ts
Normal file
9
packages/astro/src/jsx/component.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import renderer from './renderer.js';
|
||||
import { __astro_tag_component__ } from '../runtime/server/index.js';
|
||||
|
||||
const ASTRO_JSX_RENDERER_NAME = renderer.name;
|
||||
|
||||
export function createAstroJSXComponent(factory: (...args: any[]) => any) {
|
||||
__astro_tag_component__(factory, ASTRO_JSX_RENDERER_NAME);
|
||||
return factory;
|
||||
}
|
6
packages/astro/src/jsx/index.ts
Normal file
6
packages/astro/src/jsx/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export {
|
||||
default as renderer
|
||||
} from './renderer.js';
|
||||
export {
|
||||
createAstroJSXComponent
|
||||
} from './component.js';
|
|
@ -21,6 +21,7 @@ export {
|
|||
stringifyChunk,
|
||||
voidElementNames,
|
||||
} from './render/index.js';
|
||||
export { renderJSX } from './jsx.js';
|
||||
export type { AstroComponentFactory, RenderInstruction } from './render/index.js';
|
||||
import type { AstroComponentFactory } from './render/index.js';
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import type http from 'http';
|
|||
import mime from 'mime';
|
||||
import type * as vite from 'vite';
|
||||
import type { AstroSettings, ManifestData } from '../@types/astro';
|
||||
import type { SSROptions } from '../core/render/dev/index';
|
||||
import { DevelopmentEnvironment, SSROptions } from '../core/render/dev/index';
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { getSetCookiesFromResponse } from '../core/cookies/index.js';
|
||||
|
@ -16,9 +16,8 @@ import {
|
|||
import { error, info, LogOptions, warn } from '../core/logger/core.js';
|
||||
import * as msg from '../core/messages.js';
|
||||
import { appendForwardSlash } from '../core/path.js';
|
||||
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/core.js';
|
||||
import { preload, ssr } from '../core/render/dev/index.js';
|
||||
import { RouteCache } from '../core/render/route-cache.js';
|
||||
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/index.js';
|
||||
import { createDevelopmentEnvironment, preload, renderPage } from '../core/render/dev/index.js';
|
||||
import { createRequest } from '../core/request.js';
|
||||
import { createRouteManifest, matchAllRoutes } from '../core/routing/index.js';
|
||||
import { resolvePages } from '../core/util.js';
|
||||
|
@ -100,7 +99,6 @@ async function writeSSRResult(webResponse: Response, res: http.ServerResponse) {
|
|||
|
||||
async function handle404Response(
|
||||
origin: string,
|
||||
settings: AstroSettings,
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
) {
|
||||
|
@ -187,17 +185,15 @@ export function baseMiddleware(
|
|||
|
||||
async function matchRoute(
|
||||
pathname: string,
|
||||
routeCache: RouteCache,
|
||||
viteServer: vite.ViteDevServer,
|
||||
logging: LogOptions,
|
||||
env: DevelopmentEnvironment,
|
||||
manifest: ManifestData,
|
||||
settings: AstroSettings
|
||||
) {
|
||||
const { logging, settings, routeCache } = env;
|
||||
const matches = matchAllRoutes(pathname, manifest);
|
||||
|
||||
for await (const maybeRoute of matches) {
|
||||
const filePath = new URL(`./${maybeRoute.component}`, settings.config.root);
|
||||
const preloadedComponent = await preload({ settings, filePath, viteServer });
|
||||
const preloadedComponent = await preload({ env, filePath });
|
||||
const [, mod] = preloadedComponent;
|
||||
// attempt to get static paths
|
||||
// if this fails, we have a bad URL match!
|
||||
|
@ -233,7 +229,7 @@ async function matchRoute(
|
|||
|
||||
if (custom404) {
|
||||
const filePath = new URL(`./${custom404.component}`, settings.config.root);
|
||||
const preloadedComponent = await preload({ settings, filePath, viteServer });
|
||||
const preloadedComponent = await preload({ env, filePath });
|
||||
const [, mod] = preloadedComponent;
|
||||
|
||||
return {
|
||||
|
@ -249,14 +245,12 @@ async function matchRoute(
|
|||
|
||||
/** The main logic to route dev server requests to pages in Astro. */
|
||||
async function handleRequest(
|
||||
routeCache: RouteCache,
|
||||
viteServer: vite.ViteDevServer,
|
||||
logging: LogOptions,
|
||||
env: DevelopmentEnvironment,
|
||||
manifest: ManifestData,
|
||||
settings: AstroSettings,
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
) {
|
||||
const { settings, viteServer } = env;
|
||||
const { config } = settings;
|
||||
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
|
||||
const buildingToSSR = config.output === 'server';
|
||||
|
@ -296,11 +290,8 @@ async function handleRequest(
|
|||
try {
|
||||
const matchedRoute = await matchRoute(
|
||||
pathname,
|
||||
routeCache,
|
||||
viteServer,
|
||||
logging,
|
||||
env,
|
||||
manifest,
|
||||
settings
|
||||
);
|
||||
filePath = matchedRoute?.filePath;
|
||||
|
||||
|
@ -310,18 +301,15 @@ async function handleRequest(
|
|||
pathname,
|
||||
body,
|
||||
origin,
|
||||
routeCache,
|
||||
viteServer,
|
||||
env,
|
||||
manifest,
|
||||
logging,
|
||||
settings,
|
||||
req,
|
||||
res
|
||||
);
|
||||
} catch (_err) {
|
||||
const err = fixViteErrorMessage(_err, viteServer, filePath);
|
||||
const errorWithMetadata = collectErrorMetadata(err);
|
||||
error(logging, null, msg.formatErrorMessage(errorWithMetadata));
|
||||
error(env.logging, null, msg.formatErrorMessage(errorWithMetadata));
|
||||
handle500Response(viteServer, origin, req, res, errorWithMetadata);
|
||||
}
|
||||
}
|
||||
|
@ -332,16 +320,14 @@ async function handleRoute(
|
|||
pathname: string,
|
||||
body: ArrayBuffer | undefined,
|
||||
origin: string,
|
||||
routeCache: RouteCache,
|
||||
viteServer: vite.ViteDevServer,
|
||||
env: DevelopmentEnvironment,
|
||||
manifest: ManifestData,
|
||||
logging: LogOptions,
|
||||
settings: AstroSettings,
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
): Promise<void> {
|
||||
const { logging, settings } = env;
|
||||
if (!matchedRoute) {
|
||||
return handle404Response(origin, settings, req, res);
|
||||
return handle404Response(origin, req, res);
|
||||
}
|
||||
|
||||
const { config } = settings;
|
||||
|
@ -365,23 +351,20 @@ async function handleRoute(
|
|||
const paramsAndPropsRes = await getParamsAndProps({
|
||||
mod,
|
||||
route,
|
||||
routeCache,
|
||||
routeCache: env.routeCache,
|
||||
pathname: pathname,
|
||||
logging,
|
||||
ssr: config.output === 'server',
|
||||
});
|
||||
|
||||
const options: SSROptions = {
|
||||
settings,
|
||||
filePath,
|
||||
logging,
|
||||
mode: 'development',
|
||||
origin,
|
||||
pathname: pathname,
|
||||
route,
|
||||
routeCache,
|
||||
viteServer,
|
||||
request,
|
||||
env,
|
||||
filePath,
|
||||
origin,
|
||||
preload: preloadedComponent,
|
||||
pathname,
|
||||
request,
|
||||
route
|
||||
};
|
||||
|
||||
// Route successfully matched! Render it.
|
||||
|
@ -391,11 +374,8 @@ async function handleRoute(
|
|||
if (result.response.headers.get('X-Astro-Response') === 'Not-Found') {
|
||||
const fourOhFourRoute = await matchRoute(
|
||||
'/404',
|
||||
routeCache,
|
||||
viteServer,
|
||||
logging,
|
||||
manifest,
|
||||
settings
|
||||
env,
|
||||
manifest
|
||||
);
|
||||
return handleRoute(
|
||||
fourOhFourRoute,
|
||||
|
@ -403,11 +383,8 @@ async function handleRoute(
|
|||
'/404',
|
||||
body,
|
||||
origin,
|
||||
routeCache,
|
||||
viteServer,
|
||||
env,
|
||||
manifest,
|
||||
logging,
|
||||
settings,
|
||||
req,
|
||||
res
|
||||
);
|
||||
|
@ -427,7 +404,7 @@ async function handleRoute(
|
|||
res.end(result.body);
|
||||
}
|
||||
} else {
|
||||
const result = await ssr(preloadedComponent, options);
|
||||
const result = await renderPage(options);
|
||||
return await writeSSRResult(result, res);
|
||||
}
|
||||
}
|
||||
|
@ -436,11 +413,12 @@ export default function createPlugin({ settings, logging }: AstroPluginOptions):
|
|||
return {
|
||||
name: 'astro:server',
|
||||
configureServer(viteServer) {
|
||||
let routeCache = new RouteCache(logging, 'development');
|
||||
let env = createDevelopmentEnvironment(settings, logging, viteServer);
|
||||
let manifest: ManifestData = createRouteManifest({ settings }, logging);
|
||||
|
||||
/** rebuild the route cache + manifest, as needed. */
|
||||
function rebuildManifest(needsManifestRebuild: boolean, file: string) {
|
||||
routeCache.clearAll();
|
||||
env.routeCache.clearAll();
|
||||
if (needsManifestRebuild) {
|
||||
manifest = createRouteManifest({ settings }, logging);
|
||||
}
|
||||
|
@ -461,7 +439,7 @@ export default function createPlugin({ settings, logging }: AstroPluginOptions):
|
|||
if (!req.url || !req.method) {
|
||||
throw new Error('Incomplete request');
|
||||
}
|
||||
handleRequest(routeCache, viteServer, logging, manifest, settings, req, res);
|
||||
handleRequest(env, manifest, req, res);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
|
48
packages/astro/test/units/render/jsx.test.js
Normal file
48
packages/astro/test/units/render/jsx.test.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { expect } from 'chai';
|
||||
|
||||
import { createComponent, render, renderSlot } from '../../../dist/runtime/server/index.js';
|
||||
import { jsx } from '../../../dist/jsx-runtime/index.js';
|
||||
import { createBasicEnvironment, createRenderContext, renderPage, loadRenderer } from '../../../dist/core/render/index.js';
|
||||
import { createAstroJSXComponent, renderer as jsxRenderer } from '../../../dist/jsx/index.js';
|
||||
import { defaultLogging as logging } from '../../test-utils.js';
|
||||
|
||||
const createAstroModule = AstroComponent => ({ default: AstroComponent });
|
||||
const loadJSXRenderer = () => loadRenderer(jsxRenderer, s => import(s));
|
||||
|
||||
describe('core/render', () => {
|
||||
describe('Astro JSX components', () => {
|
||||
let env;
|
||||
before(async () => {
|
||||
env = createBasicEnvironment({
|
||||
logging,
|
||||
renderers: [await loadJSXRenderer()]
|
||||
});
|
||||
})
|
||||
|
||||
it('Can render slots', async () => {
|
||||
const Wrapper = createComponent((result, _props, slots = {}) => {
|
||||
return render`<div>${renderSlot(result, slots['myslot'])}</div>`;
|
||||
});
|
||||
|
||||
const Page = createAstroJSXComponent(() => {
|
||||
return jsx(Wrapper, {
|
||||
children: [
|
||||
jsx('p', {
|
||||
slot: 'myslot',
|
||||
className: 'n',
|
||||
children: 'works'
|
||||
})
|
||||
]
|
||||
})
|
||||
});
|
||||
|
||||
const ctx = createRenderContext({ request: new Request('http://example.com/' )});
|
||||
const response = await renderPage(createAstroModule(Page), ctx, env);
|
||||
|
||||
expect(response.status).to.equal(200);
|
||||
|
||||
const html = await response.text();
|
||||
expect(html).to.include('<div><p class="n">works</p></div>');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue