# ovr
Progressive HTML Rendering
## 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 the following component:
```tsx
function Component() {
return
hello world
;
}
```
ovr generates three `Chunk`s 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). This includes live reload and options for Tailwind, deployment adapters, and more. domco will create a server entry point in `src/server/+app.tsx` with ovr pre-configured.
```bash
npx create-domco@latest --framework=ovr
```
## JSX
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" } }
```
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 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
- [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.get("/", () =>
Hello World
);
serve({ fetch: app.fetch });
```
Then can compile `tsx` into `js` with TypeScript, and run the server with Node.
```bash
tsc && node dist/index.js
```
# Components
Async generator JSX
While components in ovr are authored like components in frameworks like [React](https://react.dev/), there two main distinctions between ovr and other frameworks:
1. ovr only runs on the _server_, there is no client runtime.
2. JSX evaluates to an `AsyncGenerator` that yields escaped `Chunk`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.
```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.
```tsx
function Page() {
return (
Hello world
Children
);
}
```
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.
```tsx
async function Data() {
const res = await fetch("...");
const data = await res.text();
return
{data}
;
}
```
## Generators
Components can also be generators for more fine grained control and [memory optimization](/demo/memory). When utilizing generators `yield` values instead of `return`.
```tsx
async function* Generator() {
yield
start
; // streamed immediately
await promise;
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.
## Return Types
You can `return` or `yield` most data types from a component, they will be rendered as you might expect:
```tsx
function* DataTypes() {
yield null; // ""
yield undefined; // ""
yield false; // ""
yield true; // ""
yield "string"; // "string"
yield 0; // "0";
yield BigInt(9007199254740991); // "9007199254740991"
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/rossrobino/ovr/blob/main/packages/ovr/src/jsx/index.ts) 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`.
```tsx
import { Chunk } from "ovr";
const html = "
Safe to render
";
function Component() {
return
{new Chunk(html, true)}
;
}
```
## 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` with `toGenerator`.
```tsx
import { toGenerator } from "ovr";
const Component = () =>
element
;
const gen = toGenerator(Component);
for await (const chunk of gen) {
// ...
}
```
### toStream
Turn a `JSX.Element` into a `ReadableStream`, this pipes the result of `toGenerator` into a `ReadableStream`.
```tsx
import { toStream } from "ovr";
const Component = () =>
element
;
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.
```tsx
import { toString } from "ovr";
const Component = () =>
element
;
const str = await toString(Component);
```
# App
Creating an application with ovr.
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 [Hono](https://hono.dev/) and [Express](https://expressjs.com/).
## Configuration
The following values can be customized after creating the `App`. You can also configure most of these per route within middleware by modifying the value on the `Context`.
### Trailing Slash
ovr handles [trailing slash](https://bjornlu.com/blog/trailing-slash-for-frameworks) redirects automatically, you can customize the redirect preference.
```ts
app.trailingSlash = "never";
```
### Not Found
Customize the not found response handler.
```ts
app.notFound = (c) => c.html("Not found", 404);
```
### Error Handler
Add an error handler, by default errors are thrown.
```ts
app.error = (c, error) => {
console.error(error);
c.html("An error occurred", 500);
};
```
### Base HTML
Change the base HTML to inject elements into with the [`Context.head` and `Context.page`](/05-context#page-builders) methods, this is the default.
```ts
app.base =
'';
```
## Response
At the most basic level, you can create a route and return a `Response` from the middleware to handle a request.
```ts
app.get("/", () => new Response("Hello world"));
```
You can also return a `ReadableStream` to use as the `Response.body`.
## JSX
Returning JSX from middleware will generate an HTML streamed response.
```tsx
app.get("/", () =>
Hello world
);
```
The element will be injected into the `` element of the [`base`](/03-app#base-html) HTML.
## HTTP methods
`app.get` and `app.post` create handlers for the HTTP methods respectively. You can add other or custom methods with `app.on`.
```ts
// Other or custom methods
app.on("METHOD", "/pattern", () => {
// ...
});
```
## Multiple patterns
Add the same middleware to multiple patterns.
```ts
app.get(["/multi/:param", "/pattern/:another"], (c) => {
c.params; // { param: string } | { another: string }
});
```
## Middleware
When multiple middleware handlers are added to a route, the first middleware will be called, and the `next` middleware can be dispatched within the first by using `await next()`. Middleware is based on [koa-compose](https://github.com/koajs/compose).
```ts
app.get(
"/multi",
async (c, next) => {
console.log("1");
await next(); // dispatches the next middleware below
console.log("3");
},
(c) => {
console.log("2");
},
);
```
The same [`Context`](/04-context) is passed into each middleware. After all the middleware have been run, the `Context` will `build` and return the final `Response`.
### Global Middleware
Add global middleware that runs in front of every request with `app.use`.
```ts
app.use(async (c, next) => {
// ...
await next();
// ...
});
```
## Fetch
Use the `fetch` method to create a `Response`, this is the `Request` handler for your application.
```ts
const response = await app.fetch(new Request("https://example.com/"));
```
# Helpers
ovr route helpers.
ovr provides helpers to encapsulate a route, allowing you to easily create a route in a separate module from `App`. Helpers are the best way to create pages, API endpoints, links, and forms in an ovr application.
## Get
`Get` creates 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 page use the `Anchor` component from the `Get` helper.
```tsx
import { Get } from "ovr";
const page = new Get("/", () => {
return (
{/* */}
Home
{/*
);
});
```
## Post
There is also a `Post` helper that will create a [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST) handler and corresponding `Form` and `Button` elements. Anytime you need to handle a form submission, use the generated `Form` component from the `Post` helper.
For `Post`, ovr will automatically generate a unique pathname for the route based on a hash of the middleware provided.
```tsx
import { Get, Post } from "ovr";
const login = new Post(async (c) => {
const data = await c.req.formData();
// ...
c.redirect("/", 303);
});
const page = new Get("/", () => {
return (
{/*
);
});
```
You can set the pattern manually if you need a stable pattern or if you are using parameters.
```tsx
const custom = new 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.
- If `true` the current request's `url.search` will be used (this option can only be used in the context of a request).
- Other values are 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
import { Get } from "ovr";
const page = new Get("/hello/:name", () => {
return (
//