An open API service indexing awesome lists of open source software.

https://github.com/srdjan/hsx

SSR-only JSX/TSX renderer for Deno that hides HTMX away :)
https://github.com/srdjan/hsx

deno htmx ssr typescript ui

Last synced: 5 months ago
JSON representation

SSR-only JSX/TSX renderer for Deno that hides HTMX away :)

Awesome Lists containing this project

README

          

# HSX is love, HSX is life

First things, first... What the hack does HSX stand for? I'll say it's '**H**TML
for **S**erver-Side e**X**tensions'. :0

But, honestly, I prefer: **H**TMX **S**laps **X**tremely :)

SSR-only JSX/TSX renderer for Deno that hides HTMX-style attributes away during the rendering process, and compiles them to `hx-*` attributes.

> Disclaimer: this was a quick hack in my free time, held together by vibe
> coding and espresso. I like it a lot, but consider it an early release. I feel it is getting better (a lot)

## TL;DR: Like JSX, but for SSR HTML + HTMX.

## Features

- **SSR-only** - No client runtime. Outputs plain HTML.
- **HTMX as HTML** - Write `get`, `post`, `target`, `swap` as native attributes
- **Type-safe routes** - Branded `Route` types with automatic parameter
inference
- **Co-located components** - `hsxComponent()` bundles route + handler + render
- **Page guardrails** - `hsxPage()` enforces semantic, style-free layouts
- **Branded IDs** - `id("name")` returns `Id<"name">` typed as `"#name"`
- **Auto HTMX injection** - `` injected when
needed
- **No manual hx-\*** - Throws at render time if you write `hx-get` directly
- **Widgets** - Define once, serve via SSR or embed as iframes with
Declarative Shadow DOM

## Installation

### From JSR

```bash
deno add jsr:@srdjan/hsx
```

Or import directly:

```ts
import { id, render, route } from "jsr:@srdjan/hsx";
```

### Separate Packages

HSX is a monorepo with three packages:

```ts
// Core - JSX rendering, type-safe routes, hsxComponent, hsxPage
import { Fragment, hsxComponent, hsxPage, id, render, route } from "jsr:@srdjan/hsx";

// Styles - ready-to-use CSS with theming support
import { HSX_STYLES_PATH, hsxStyles } from "jsr:@srdjan/hsx-styles";

// Widgets - embeddable widget protocol + SSR/embed adapters
import { widgetToHsxComponent } from "jsr:@srdjan/hsx-widgets/ssr";
```

Install individually:

```bash
deno add jsr:@srdjan/hsx
deno add jsr:@srdjan/hsx-styles
deno add jsr:@srdjan/hsx-widgets
```

### Selective Imports (Tree-Shaking)

For smaller bundles, import only what you need:

```ts
// Core only - render, route, id, Fragment (smaller bundle)
import { render, route, id, Fragment } from "jsr:@srdjan/hsx/core";

// Components only - hsxComponent, hsxPage
import { hsxComponent, hsxPage } from "jsr:@srdjan/hsx/components";

// Everything (default)
import { render, route, hsxComponent, hsxPage } from "jsr:@srdjan/hsx";
```

### From Source

Clone and import using workspace package names:

```ts
import { hsxComponent, hsxPage, id, render, route } from "@srdjan/hsx";
```

## Quick Start (Low-Level API)

> **Note:** This shows the low-level API using `route()`. For most projects, use
> the [hsxComponent pattern](#hsx-component-pattern-recommended) below instead.

```tsx
import { id, render, route } from "@srdjan/hsx";

const routes = {
todos: route("/todos", () => "/todos"),
};

const ids = {
list: id("todo-list"),
};

function Page() {
return (
<html>
<head>
<title>HSX Demo</title>
</head>
<body>
<form post={routes.todos} target={ids.list} swap="outerHTML">
<input name="text" required />
<button type="submit">Add</button>
</form>
<ul id="todo-list">{/* items */}</ul>
</body>
</html>
);
}

Deno.serve(() => render(<Page />));
```

**Output HTML:**

```html
<html>
<head><title>HSX Demo</title></head>
<body>
<form hx-post="/todos" hx-target="#todo-list" hx-swap="outerHTML">
<input name="text" required>
<button type="submit">Add</button>
</form>
<ul id="todo-list"></ul>
<script src="/static/htmx.js">
`
- Semantic tags (header/main/section/article/h1-h6/p/ul/ol/li/etc.) have **no**
`class` or inline `style`
- `` tags live in `<head>`; CSS belongs there, not inline
- Composition stays within semantic HTML + HSX components

```tsx
import { hsxComponent, hsxPage } from "jsr:@srdjan/hsx";

const Widget = hsxComponent("/data", {
handler: () => ({ message: "Hi" }),
render: ({ message }) => <p>{message}</p>,
});

const Page = hsxPage(() => (
<html lang="en">
<head>
<title>Guarded Page</title>
<style>{"body { font-family: system-ui; }"}



Welcome