-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
astro.ts
130 lines (111 loc) · 3.79 KB
/
astro.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import type { SSRResult } from '../../../@types/astro';
import type { AstroComponentFactory } from './index';
import type { RenderInstruction } from './types';
import { HTMLBytes, markHTMLString } from '../escape.js';
import { HydrationDirectiveProps } from '../hydration.js';
import { renderChild } from './any.js';
import { HTMLParts } from './common.js';
// In dev mode, check props and make sure they are valid for an Astro component
function validateComponentProps(props: any, displayName: string) {
if (import.meta.env?.DEV && props != null) {
for (const prop of Object.keys(props)) {
if (HydrationDirectiveProps.has(prop)) {
// eslint-disable-next-line
console.warn(
`You are attempting to render <${displayName} ${prop} />, but ${displayName} is an Astro component. Astro components do not render in the client and should not have a hydration directive. Please use a framework component for client rendering.`
);
}
}
}
}
// The return value when rendering a component.
// This is the result of calling render(), should this be named to RenderResult or...?
export class AstroComponent {
private htmlParts: TemplateStringsArray;
private expressions: any[];
constructor(htmlParts: TemplateStringsArray, expressions: any[]) {
this.htmlParts = htmlParts;
this.expressions = expressions;
}
get [Symbol.toStringTag]() {
return 'AstroComponent';
}
async *[Symbol.asyncIterator]() {
const { htmlParts, expressions } = this;
for (let i = 0; i < htmlParts.length; i++) {
const html = htmlParts[i];
const expression = expressions[i];
yield markHTMLString(html);
yield* renderChild(expression);
}
}
}
// Determines if a component is an .astro component
export function isAstroComponent(obj: any): obj is AstroComponent {
return (
typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object AstroComponent]'
);
}
export function isAstroComponentFactory(obj: any): obj is AstroComponentFactory {
return obj == null ? false : obj.isAstroComponentFactory === true;
}
export async function* renderAstroComponent(
component: InstanceType<typeof AstroComponent>
): AsyncIterable<string | HTMLBytes | RenderInstruction> {
for await (const value of component) {
if (value || value === 0) {
for await (const chunk of renderChild(value)) {
switch (chunk.type) {
case 'directive': {
yield chunk;
break;
}
default: {
yield markHTMLString(chunk);
break;
}
}
}
}
}
}
// Calls a component and renders it into a string of HTML
export async function renderToString(
result: SSRResult,
componentFactory: AstroComponentFactory,
props: any,
children: any
): Promise<string> {
const Component = await componentFactory(result, props, children);
if (!isAstroComponent(Component)) {
const response: Response = Component;
throw response;
}
let parts = new HTMLParts();
for await (const chunk of renderAstroComponent(Component)) {
parts.append(chunk, result);
}
return parts.toString();
}
export async function renderToIterable(
result: SSRResult,
componentFactory: AstroComponentFactory,
displayName: string,
props: any,
children: any
): Promise<AsyncIterable<string | HTMLBytes | RenderInstruction>> {
validateComponentProps(props, displayName);
const Component = await componentFactory(result, props, children);
if (!isAstroComponent(Component)) {
// eslint-disable-next-line no-console
console.warn(
`Returning a Response is only supported inside of page components. Consider refactoring this logic into something like a function that can be used in the page.`
);
const response = Component;
throw response;
}
return renderAstroComponent(Component);
}
export async function renderTemplate(htmlParts: TemplateStringsArray, ...expressions: any[]) {
return new AstroComponent(htmlParts, expressions);
}