# ovr
The Streaming Framework
## Introduction
Designed to optimize [Time-To-First-Byte](https://web.dev/articles/ttfb#what_is_ttfb), ovr evaluates components in parallel and streams HTML in order by producing an [`AsyncGenerator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator) of HTML that feeds directly into the streamed response.
For example, for the following component:
```tsx
function Component() {
return
hello world
;
}
```
ovr generates three chunks of HTML:
```ts
"
"; // streamed immediately
"hello world"; // next
"
"; // last
```
## Asynchronous streaming
While this streaming is trivial for a paragraph, consider when a component is asynchronous:
```tsx
async function Username() {
const user = await getUser(); // slow...
return {user.name};
}
function Component() {
return (
hello
);
}
```
Instead of waiting for `Username` to resolve before sending the entire `Component`, ovr will send what it has immediately and stream the rest as it becomes available.
```ts
"
";
"hello ";
// streamed immediately
// for await (const chunk of Username()) { ...
"";
"username";
"";
"
";
```
## Render how browsers read
Web browsers are [built for streaming](https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/How_browsers_work#parsing), they parse and paint HTML as it arrives. [Most critically, the head](https://web.dev/learn/performance/understanding-the-critical-path#what_resources_are_on_the_critical_rendering_path) of the document can be sent immediately to start the requests for linked assets (JavaScript, CSS, etc.) and start parsing before the HTML has finished streaming.
ovr's architecture gives you streaming server-side rendering out of the box. No hydration bundle, no buffering---just HTML delivered _in order_, as soon as it's ready.
# Get Started
Getting started with ovr
## Installation
Install the `ovr` package from npm using your preferred package manager.
```bash
npm i ovr
```
Alternatively, you can setup ovr with a pre-configured template using [Vite with domco](https://domco.robino.dev):
```bash
npx create-domco@latest --framework=ovr
```
## JSX configuration
To utilize JSX, add the following options to your `tsconfig.json` to enable the JSX transform. TypeScript, Vite, or esbuild will pickup the option from this file.
```json
{ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "ovr" } }
```
> While the ovr package does not depend on React, it uses the same JSX transform as React that is built into `tsc` and other build tools.
Or you can use a comment if you are using ovr in conjunction with another framework to specify the import source for a specific module where you are using ovr.
```tsx
/** @jsx jsx */
/** @jsxImportSource ovr */
```
## Compatibility
ovr uses entirely standard JavaScript APIs, so it can run anywhere.
The ovr `App` server can be used in any Fetch API compatible runtime via [`App.fetch`](/03-app#fetch). Here are a few ways to create a Fetch based HTTP server in various JavaScript runtimes.
- [domco](https://domco.robino.dev) - run `npm create domco` and select `ovr` framework
- [Nitro](https://nitro.build/) - [example repo](https://github.com/rossrobino/nitro-ovr)
- [Cloudflare Vite Plugin](https://developers.cloudflare.com/workers/vite-plugin/get-started/) + [vite-ssr-components](https://github.com/yusukebe/vite-ssr-components)
- [Node + srvx](https://srvx.h3.dev/)
- [Bun HTTP server](https://bun.sh/docs/api/http)
- [Deno HTTP server](https://docs.deno.com/runtime/fundamentals/http_server/)
For example, using `srvx` you can plug `app.fetch` into the `serve` [options](https://srvx.h3.dev/guide/handler).
```tsx
// src/index.tsx
import { App } from "ovr";
import { serve } from "srvx";
const app = new App();
app.use(() =>
Hello World
);
serve({ fetch: (req) => app.fetch(req) });
```
Then can compile `tsx` into `js` with TypeScript, and run the server with Node.
```bash
tsc && node dist/index.js
```
# Render
Easily parallelize async operations and render strings with ovr.
## JSX
ovr has a built in JSX import source that can be used to parallelize component renders and output HTML. You can also use the [`Render`](#render) class on it's own without using JSX.
Components are functions that return a `JSX.Element`. 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.
```tsx
import type { JSX } from "ovr";
function Component(props: { children?: JSX.Element; color: string }) {
return
{props.children}
;
}
```
Now the component can be used as it's own tag within other components.
> Use capital letters for components to distinguish them from HTML (or _intrinsic_) elements.
```tsx
function Page() {
return (
Hello world
Children
);
}
```
Props are passed in like an HTML attribute `name={value}`, while `children` is a special prop that is used to reference the element(s) in between the opening and closing tags.
ovr aligns with the standard (all lowercase) HTML attributes---all attributes will be rendered exactly as they are written.
> If you're coming from React, this means you must 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
Components can be asynchronous, for example you can `fetch` directly in a component.
```tsx
async function Data() {
const res = await fetch("...");
const data = await res.text();
return
{data}
;
}
```
## Generator components
Components can also be generator functions for more fine grained control and [memory optimization](/demo/memory). When utilizing generators `yield` values instead of returning them.
```tsx
async function* Generator() {
yield
start
; // streamed immediately
await promise; // async work
yield
after
;
}
```
## Parallelization
These three components await in [parallel](/demo/parallel) when this component is evaluated. Then they will stream in order as soon as they are ready.
```tsx
function Page() {
return (
);
}
```
> 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. This ensures no client-side JavaScript has to run for users to see your content.
## Data types
You can `return` or `yield` most data types from a component, they will be rendered as you might expect. Each of these data types can also be used to create a new [`Render`](#render).
```tsx
function* DataTypes() {
yield null; // ""
yield undefined; // ""
yield false; // ""
yield true; // ""
yield "string"; // "string"
yield 0; // "0";
yield
jsx
; // "
jsx
"
yield ["any-", "iterable", 1, null]; // "any-iterable1"
yield () => "function"; // "function"
yield async () => "async"; // "async"
}
```
> Check out the [source code](https://github.com/ovrjs/ovr/blob/main/packages/ovr/src/render/index.ts) for the `render` function to understand how ovr evaluates each data type.
## Render
To render any element on it's own (for example, if you aren't using returning them from `Middleware` or need to call them separately), use the `Render` class.
### Async iterable
Create a new `Render` async iterable that yields escaped `Chunk`s of HTML.
```tsx
import { Render } from "ovr";
function Component() {
return
element
;
}
const render = new Render();
for await (const chunk of render) {
console.log(chunk.toString()); // HTML string
}
```
> Any of the supported [data types](#data-types) can also be rendered directly without JSX. For example, the following array can be passed into `Render` running all async operations in parallel.
```ts
const render = new Render([
async () => "hello",
"world",
[
async function* () {
yield "!";
},
],
]);
for await (const chunk of render) {
console.log(chunk.toString());
}
```
Set `Render.Options.safe` to `true` to bypass HTML escaping to render [other types of content](/05-middleware#return-value).
```ts
new Render(element, { safe: true });
```
### Stream
Turn an element into a `ReadableStream` using `Render.stream`. This pipes a `Render` into a `ReadableStream`.
This stream is optimized for generating HTML---it ensures backpressure is properly handled for slower clients and generation stops if the client aborts the request.
```tsx
import { Render } from "ovr";
function Component() {
return
element
;
}
const stream = Render.stream();
const response = new Response(stream, {
"content-type": "text/html; charset=utf-8",
});
```
### String
Convert any element into a `string` of HTML with `Render.string`. This iterates through the `Render` and joins the results into a single string.
```tsx
import { Render } from "ovr";
function Component() {
return
element
;
}
const str = await Render.string();
```
### Raw HTML
To render safe HTML **without escaping**, use the `Render.html` method.
```tsx
import { Render } from "ovr";
const html = "
Safe to render
";
function Component() {
return
{Render.html(html)}
;
}
```
# App
Creating an application with ovr.
## Create
To create a web server with ovr, initialize a new `App` instance:
```ts
import { App } from "ovr";
const app = new App();
```
The `App` API is inspired by and works similar to frameworks such as [Express](http://expressjs.com), [Koa](https://koajs.com), and [Hono](https://hono.dev/).
## Options
The following values can be configured when creating the `App`.
### Trailing Slash
ovr handles [trailing slash](https://bjornlu.com/blog/trailing-slash-for-frameworks) redirects automatically, you can customize the redirect preference.
```ts
new App({ trailingSlash: "always" });
```
### CSRF
ovr comes with basic [cross-site request forgery](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF) protection.
```ts
new App({ csrf: false }); // disables the built-in protection
```
### Form
[Multipart form data options](/06-multipart#options).
```ts
new App({
form: {
memory: 12 * 1024 * 1024, // increase to 12MB
payload: 1024 ** 3, // increase to 1GB
parts: 4, // only accept up to 4 parts
},
});
```
## Use
_Use_ the `use` method to register [routes](/04-route) and [middleware](/05-middleware) to your application.
```tsx
app.use(page); // single
app.use(page, login, mw); // multiple
app.use({ page, login, mw }); // object
app.use([page, login, mw]); // array
// any combination of these also works
```
This makes it easy to create a module of routes and/or middleware,
```tsx
// home.tsx
import { Route } from "ovr";
export const page = Route.get("/", (c) => {
// ...
});
export const login = Route.post((c) => {
// ...
});
```
and then use them all at once:
```tsx
// app.tsx
import * as home from "./home";
app.use(home); // uses all exports
```
## Fetch
Use the `fetch` method to create a `Response`, this is the `Request` handler for your application.
```ts
const response = await app.fetch("https://example.com");
```
## Head requests
ovr automatically handles [`HEAD`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/HEAD) requests, each will be routed to the corresponding `GET` route. Middleware will execute but `Context.res.body` will cleaned up and set to `null` before building the final response.
# Route
ovr application routes.
## Create
ovr's built-in router offers efficient matching, supporting static paths, [parameters](#parameters), and [wildcards](#wildcard). Utilizing a [radix trie](https://en.wikipedia.org/wiki/Radix_tree) structure ensures performance does not degrade as more routes are added.
Create a route to a specific resource in your application with the `Route` class. Construct the route with an HTTP `method`, the route `pattern`, and the [`middleware`](/05-middleware) to handle the request.
```ts
import { Route } from "ovr";
const route = new Route("GET", "/", () => "html");
```
## Parameters
Create a parameter for a route by prefixing a segment with a colon `:`.
The pattern `/api/:id` sets `Context.params.id` to the actual path segment requested, for example `/api/123`. The name of the parameter is extracted from the pattern using TypeScript to ensure `Context.params` always has the correct type.
```ts
new Route("GET", "/api/:id", (c) => {
// matches "/api/123"
c.params.id; // "123"
});
```
## Wildcard
Use an asterisk `*` to match all remaining segments in the route.
```ts
new Route("GET", "/files/*", (c) => {
c.params["*"]; // matched wildcard path (ex: "images/logo.png")
});
```
## Prioritization
Routes are prioritized in this order:
**Static** > **Parametric** > **Wildcard**
Given three routes are added in any order:
```ts
app.use(new Route("GET", "/hello/*"));
app.use(new Route("GET", "/hello/world"));
app.use(new Route("GET", "/hello/:name"));
```
More specific matches are prioritized. The following pathnames would match the corresponding patterns:
| Pathname | Pattern |
| ------------------- | -------------- |
| `/hello/world` | `/hello/world` |
| `/hello/john` | `/hello/:name` |
| `/hello/john/smith` | `/hello/*` |
## Get
`Route.get` makes it easy to create a [GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/GET) route and corresponding `Anchor`, `Button`, and `Form` components for it. This ensures if you change the route's pattern, you don't need to update all of the links to it throughout your application. Anytime you need to generate a link to a route use the `Anchor` component.
```tsx
const page = Route.get("/", () => {
return (
{/* */}
Home
{/*
);
});
```
## Post
There is also a `Route.post` function that will create a [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST) route and corresponding `Form` and `Button` elements. Anytime you need to handle a form submission, use the generated `Form` component. These components also automatically set the `enctype=multipart/form-data` attribute.
For `Route.post`, ovr will automatically generate a unique pathname for the route based on a hash of the middleware provided.
```tsx
const login = Route.post(async (c) => {
const data = await c.req.formData();
// ...
c.redirect("/", 303);
});
const page = Route.get("/", () => {
return (
{/*
);
});
```
You can set the pattern manually if you need a stable pattern or if you are using parameters.
```tsx
const custom = Route.post("/custom/:pattern", (c) => {
// ...
});
```
## Props
Components created via helpers have the following props available:
- `params` - if the route's pattern has parameters, they must be passed as a prop to properly construct the URL.
- `search` - [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams) to append to the URL, passed into `URLSearchParams` constructor to create the query string.
- `hash` - [fragment hash](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) appended with a `#` at the end of the URL.
```tsx
const page = Route.get("/hello/:name", () => {
return (
//