Refactor SSRResult and RenderContext (#7575)

This commit is contained in:
Bjorn Lu 2023-07-06 00:25:24 +08:00 committed by GitHub
parent 44e56bb3bc
commit 30d04db981
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 59 additions and 77 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/markdoc': patch
---
Handle internal access change

View file

@ -1957,16 +1957,6 @@ export interface SSRElement {
children: string;
}
export interface SSRMetadata {
renderers: SSRLoadedRenderer[];
pathname: string;
hasHydrationScript: boolean;
hasDirectives: Set<string>;
hasRenderedHead: boolean;
headInTree: boolean;
clientDirectives: Map<string, string>;
}
/**
* A hint on whether the Astro runtime needs to wait on a component to render head
* content. The meanings:
@ -1989,9 +1979,6 @@ export interface SSRResult {
scripts: Set<SSRElement>;
links: Set<SSRElement>;
componentMetadata: Map<string, SSRComponentMetadata>;
propagators: Map<AstroComponentFactory, AstroComponentInstance>;
extraHead: Array<string>;
cookies: AstroCookies | undefined;
createAstro(
Astro: AstroGlobalPartial,
props: Record<string, any>,
@ -1999,13 +1986,32 @@ export interface SSRResult {
): AstroGlobal;
resolve: (s: string) => Promise<string>;
response: ResponseInit;
// Bits 1 = astro, 2 = jsx, 4 = slot
// As rendering occurs these bits are manipulated to determine where content
// is within a slot. This is used for head injection.
scope: number;
renderers: SSRLoadedRenderer[];
/**
* Map of directive name (e.g. `load`) to the directive script code
*/
clientDirectives: Map<string, string>;
/**
* Only used for logging
*/
pathname: string;
cookies: AstroCookies | undefined;
_metadata: SSRMetadata;
}
/**
* Ephemeral and mutable state during rendering that doesn't rely
* on external configuration
*/
export interface SSRMetadata {
hasHydrationScript: boolean;
hasDirectives: Set<string>;
hasRenderedHead: boolean;
headInTree: boolean;
extraHead: string[];
propagators: Map<AstroComponentFactory, AstroComponentInstance>;
}
/* Preview server stuff */
export interface PreviewServer {
host?: string;

View file

@ -227,7 +227,6 @@ export class App {
const mod = (await page.page()) as any;
const renderContext = await createRenderContext({
request,
origin: url.origin,
pathname,
componentMetadata: this.#manifest.componentMetadata,
scripts,
@ -295,7 +294,6 @@ export class App {
const ctx = await createRenderContext({
request,
origin: url.origin,
pathname,
route: routeData,
status,

View file

@ -539,7 +539,6 @@ async function generatePath(
});
const renderContext = await createRenderContext({
origin,
pathname,
request: createRequest({ url, headers: new Headers(), logging, ssr }),
componentMetadata: manifest.componentMetadata,

View file

@ -10,7 +10,6 @@ export async function call(options: SSROptions, logging: LogOptions) {
const ctx = await createRenderContext({
request: options.request,
origin: options.origin,
pathname: options.pathname,
route: options.route,
env,

View file

@ -1,5 +1,4 @@
import type {
AstroCookies,
ComponentInstance,
Params,
Props,
@ -18,23 +17,21 @@ const clientLocalsSymbol = Symbol.for('astro.locals');
*/
export interface RenderContext {
request: Request;
origin: string;
pathname: string;
url: URL;
scripts?: Set<SSRElement>;
links?: Set<SSRElement>;
styles?: Set<SSRElement>;
componentMetadata?: SSRResult['componentMetadata'];
route?: RouteData;
status?: number;
cookies?: AstroCookies;
params: Params;
props: Props;
locals?: object;
}
export type CreateRenderContextArgs = Partial<RenderContext> & {
origin?: string;
export type CreateRenderContextArgs = Partial<
Omit<RenderContext, 'params' | 'props' | 'locals'>
> & {
request: RenderContext['request'];
mod: ComponentInstance;
env: Environment;
@ -44,9 +41,7 @@ export async function createRenderContext(
options: CreateRenderContextArgs
): Promise<RenderContext> {
const request = options.request;
const url = new URL(request.url);
const origin = options.origin ?? url.origin;
const pathname = options.pathname ?? url.pathname;
const pathname = options.pathname ?? new URL(request.url).pathname;
const [params, props] = await getParamsAndProps({
mod: options.mod as any,
route: options.route,
@ -56,11 +51,9 @@ export async function createRenderContext(
ssr: options.env.ssr,
});
let context = {
const context: RenderContext = {
...options,
origin,
pathname,
url,
params,
props,
};

View file

@ -41,10 +41,7 @@ export async function renderPage({
styles: renderContext.styles,
logging: env.logging,
markdown: env.markdown,
mode: env.mode,
origin: renderContext.origin,
params: renderContext.params,
props: renderContext.props,
pathname: renderContext.pathname,
componentMetadata: renderContext.componentMetadata,
resolve: env.resolve,

View file

@ -24,8 +24,6 @@ export interface SSROptions {
env: DevelopmentEnvironment;
/** location of file on disk */
filePath: URL;
/** production website */
origin: string;
/** the web request (needed for dynamic routes) */
pathname: string;
/** The runtime component instance */
@ -157,7 +155,6 @@ export async function renderPage(options: SSROptions): Promise<Response> {
const renderContext = await createRenderContext({
request: options.request,
origin: options.origin,
pathname: options.pathname,
scripts,
links,

View file

@ -3,8 +3,6 @@ import type {
AstroGlobal,
AstroGlobalPartial,
Params,
Props,
RuntimeMode,
SSRElement,
SSRLoadedRenderer,
SSRResult,
@ -33,15 +31,12 @@ export interface CreateResultArgs {
*/
ssr: boolean;
logging: LogOptions;
origin: string;
/**
* Used to support `Astro.__renderMarkdown` for legacy `<Markdown />` component
*/
markdown: MarkdownRenderingOptions;
mode: RuntimeMode;
params: Params;
pathname: string;
props: Props;
renderers: SSRLoadedRenderer[];
clientDirectives: Map<string, string>;
resolve: (s: string) => Promise<string>;
@ -170,9 +165,9 @@ export function createResult(args: CreateResultArgs): SSRResult {
scripts: args.scripts ?? new Set<SSRElement>(),
links: args.links ?? new Set<SSRElement>(),
componentMetadata,
propagators: new Map(),
extraHead: [],
scope: 0,
renderers,
clientDirectives,
pathname,
cookies,
/** This function returns the `Astro` faux-global */
createAstro(
@ -259,16 +254,15 @@ export function createResult(args: CreateResultArgs): SSRResult {
return Astro;
},
resolve,
response,
_metadata: {
renderers,
pathname,
hasHydrationScript: false,
hasRenderedHead: false,
hasDirectives: new Set(),
headInTree: false,
clientDirectives,
extraHead: [],
propagators: new Map(),
},
response,
};
return result;

View file

@ -26,7 +26,7 @@ interface ExtractedProps {
// Finds these special props and removes them from what gets passed into the component.
export function extractDirectives(
inputProps: Record<string | number | symbol, any>,
clientDirectives: SSRResult['_metadata']['clientDirectives']
clientDirectives: SSRResult['clientDirectives']
): ExtractedProps {
let extracted: ExtractedProps = {
isPage: false,

View file

@ -76,7 +76,7 @@ async function renderJSXVNode(result: SSRResult, vnode: AstroVNode, skip: Skip):
if (isVNode(vnode)) {
switch (true) {
case !vnode.type: {
throw new Error(`Unable to render ${result._metadata.pathname} because it contains an undefined Component!
throw new Error(`Unable to render ${result.pathname} because it contains an undefined Component!
Did you forget to import the component or is it possible there is a typo?`);
}
case (vnode.type as any) === Symbol.for('astro:fragment'):

View file

@ -80,8 +80,8 @@ export function createAstroComponentInstance(
) {
validateComponentProps(props, displayName);
const instance = new AstroComponentInstance(result, props, slots, factory);
if (isAPropagatingComponent(result, factory) && !result.propagators.has(factory)) {
result.propagators.set(factory, instance);
if (isAPropagatingComponent(result, factory) && !result._metadata.propagators.has(factory)) {
result._metadata.propagators.set(factory, instance);
}
return instance;
}

View file

@ -74,7 +74,7 @@ async function renderFrameworkComponent(
);
}
const { renderers, clientDirectives } = result._metadata;
const { renderers, clientDirectives } = result;
const metadata: AstroComponentMetadata = {
astroStaticSlot: true,
displayName,

View file

@ -1,6 +1,7 @@
import type { SSRResult } from '../../../@types/astro';
import { markHTMLString } from '../escape.js';
import type { MaybeRenderHeadInstruction, RenderHeadInstruction } from './types';
import { renderElement } from './util.js';
// Filter out duplicate elements in our set
@ -34,8 +35,8 @@ export function renderAllHeadContent(result: SSRResult) {
let content = links.join('\n') + styles.join('\n') + scripts.join('\n');
if (result.extraHead.length > 0) {
for (const part of result.extraHead) {
if (result._metadata.extraHead.length > 0) {
for (const part of result._metadata.extraHead) {
content += part;
}
}
@ -43,20 +44,16 @@ export function renderAllHeadContent(result: SSRResult) {
return markHTMLString(content);
}
export function* renderHead(result: SSRResult) {
yield { type: 'head', result } as const;
export function* renderHead(): Generator<RenderHeadInstruction> {
yield { type: 'head' };
}
// This function is called by Astro components that do not contain a <head> component
// This accommodates the fact that using a <head> is optional in Astro, so this
// is called before a component's first non-head HTML element. If the head was
// already injected it is a noop.
export function* maybeRenderHead(result: SSRResult) {
if (result._metadata.hasRenderedHead) {
return;
}
export function* maybeRenderHead(): Generator<MaybeRenderHeadInstruction> {
// This is an instruction informing the page rendering that head might need rendering.
// This allows the page to deduplicate head injections.
yield { type: 'maybe-head', result, scope: result.scope } as const;
yield { type: 'maybe-head' };
}

View file

@ -55,7 +55,7 @@ async function iterableToHTMLBytes(
// Recursively calls component instances that might have head content
// to be propagated up.
async function bufferHeadContent(result: SSRResult) {
const iterator = result.propagators.values();
const iterator = result._metadata.propagators.values();
while (true) {
const { value, done } = iterator.next();
if (done) {
@ -63,7 +63,7 @@ async function bufferHeadContent(result: SSRResult) {
}
const returnValue = await value.init(result);
if (isHeadAndContent(returnValue)) {
result.extraHead.push(returnValue.head);
result._metadata.extraHead.push(returnValue.head);
}
}
}
@ -86,7 +86,7 @@ export async function renderPage(
try {
if (nonAstroPageNeedsHeadInjection(componentFactory)) {
const parts = new HTMLParts();
for await (const chunk of maybeRenderHead(result)) {
for await (const chunk of maybeRenderHead()) {
parts.append(chunk, result);
}
head = parts.toString();

View file

@ -1,21 +1,16 @@
import type { SSRResult } from '../../../@types/astro';
import type { HydrationMetadata } from '../hydration.js';
export type RenderDirectiveInstruction = {
type: 'directive';
result: SSRResult;
hydration: HydrationMetadata;
};
export type RenderHeadInstruction = {
type: 'head';
result: SSRResult;
};
export type MaybeRenderHeadInstruction = {
type: 'maybe-head';
result: SSRResult;
scope: number;
};
export type RenderInstruction =

View file

@ -21,7 +21,7 @@ export function determinesIfNeedsDirectiveScript(result: SSRResult, directive: s
export type PrescriptType = null | 'both' | 'directive';
function getDirectiveScriptText(result: SSRResult, directive: string): string {
const clientDirectives = result._metadata.clientDirectives;
const clientDirectives = result.clientDirectives;
const clientDirective = clientDirectives.get(directive);
if (!clientDirective) {
throw new Error(`Unknown directive: ${directive}`);

View file

@ -165,7 +165,6 @@ export async function handleRoute(
const options: SSROptions = {
env,
filePath,
origin,
preload: preloadedComponent,
pathname,
request,

View file

@ -89,7 +89,10 @@ export const ComponentNode = createComponent({
);
// Let the runtime know that this component is being used.
result.propagators.set(
// `result.propagators` has been moved to `result._metadata.propagators`
// TODO: remove this fallback in the next markdoc integration major
const propagators = result._metadata.propagators || result.propagators;
propagators.set(
{},
{
init() {