Components
While components in ovr are authored like components in frameworks like React, there two main distinctions between ovr and other frameworks:
- ovr only runs on the server, there is no client runtime.
- JSX evaluates to an
AsyncGenerator
that yields escapedChunk
s of HTML.
Basic
If you aren’t familiar with components, they are functions that return JSX elements. You can use them to declaratively describe and reuse parts of your HTML.
Define a props
object as a parameter for a component to pass arguments to it.
import type { JSX } from "ovr";
function Component(props: { children?: JSX.Element; color: string }) {
return <div style={`color: ${props.color}`}>{props.children}</div>;
}
Now the component can be used as it’s own tag within other components.
function Page() {
return (
<div>
<h1>Hello world</h1>
<Component color="blue">Children</Component>
</div>
);
}
Props are passed in as attributes, while children
is a special prop that is used to reference the element(s) in between the opening and closing tags.
ovr uses aligns with the standard (all lowercase) HTML attributes—attributes will be rendered exactly as they are written.
If you’re coming from React, use class
and for
instead of className
and htmlFor
respectively. There is also no need to provide a key
attribute in when rendering lists.
Async
Components can be asynchronous, for example you can fetch
directly in a component.
async function Data() {
const res = await fetch("...");
const data = await res.text();
return <div>{data}</div>;
}
Generators
Components can also be generators for more fine grained control and memory optimization. When utilizing generators yield
values instead of return
.
async function* Generator() {
yield <p>start</p>; // streamed immediately
await promise;
yield <p>after</p>;
}
Parallelization
These three components await in parallel when this component is evaluated. Then they will stream in order as soon as they are ready.
function Page() {
return (
<main>
<Username />
<div>
<Generator />
<Data />
</div>
</main>
);
}
The order of your components does not affect when they are evaluated, but it does impact when they will display. If Username
is the slowest component, Generator
and Data
will be queued but only streamed after Username
completes.
Return Types
You can return
or yield
most data types from a component, they will be rendered as you might expect:
function* DataTypes() {
yield null; // ""
yield undefined; // ""
yield false; // ""
yield true; // ""
yield "string"; // "string"
yield 0; // "0";
yield BigInt(9007199254740991); // "9007199254740991"
yield <p>jsx</p>; // "<p>jsx</p>"
yield ["any-", "iterable", 1, null]; // "any-iterable1"
yield () => "function"; // "function"
yield async () => "async"; // "async"
}
Check out the source code for the
toGenerator
function to understand how ovr evaluates each data type.
Raw HTML
To render HTML directly without escaping, create a new Chunk
with the second argument safe
set to true
.
import { Chunk } from "ovr";
const html = "<p>Safe to render</p>";
function Component() {
return <div>{new Chunk(html, true)}</div>;
}
Running components
To evaluate components (for example, if you aren’t using App
or need to call them separately), you can use these functions.
toGenerator
Convert any JSX.Element
into AsyncGenerator<Chunk>
with toGenerator
.
import { toGenerator } from "ovr";
const Component = () => <p>element</p>;
const gen = toGenerator(Component);
for await (const chunk of gen) {
// ...
}
toStream
Turn a JSX.Element
into a ReadableStream<Uint8Array>
, this pipes the result of toGenerator
into a ReadableStream
.
import { toStream } from "ovr";
const Component = () => <p>element</p>;
const stream = toStream(Component);
const response = new Response(stream, {
"Content-Type": "text/html; charset=utf-8",
});
toString
Convert any JSX.Element
into a string
of HTML with toString
. This runs toGenerator
joins the results into a single string.
import { toString } from "ovr";
const Component = () => <p>element</p>;
const str = await toString(Component);