https://github.com/ije/mono-jsx
`<html>` as a `Response`.
https://github.com/ije/mono-jsx
Last synced: 2 months ago
JSON representation
`<html>` as a `Response`.
- Host: GitHub
- URL: https://github.com/ije/mono-jsx
- Owner: ije
- License: mit
- Created: 2024-08-19T20:56:00.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2026-03-25T06:02:56.000Z (3 months ago)
- Last Synced: 2026-03-25T17:36:07.125Z (3 months ago)
- Language: TypeScript
- Homepage:
- Size: 1.02 MB
- Stars: 530
- Watchers: 2
- Forks: 9
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# mono-jsx

mono-jsx is a JSX runtime that renders the `` element to a `Response` object.
- π No build step needed
- π¦ Lightweight (12KB gzipped), zero dependencies
- π¦ Signals as reactive primitives
- β‘οΈ Use web components, no virtual DOM
- π‘ Complete Web API TypeScript definitions
- β³ Streaming rendering
- ποΈ Built-in router (SPA mode)
- π Session storage
- π₯· [htmx](#using-htmx) integration
- π Universal, works in Node.js, Deno, Bun, Cloudflare Workers, etc.
Playground: https://val.town/x/ije/mono-jsx
## Installation
mono-jsx supports all modern JavaScript runtimes including Node.js, Deno, Bun, and Cloudflare Workers.
You can install it via `npm`, `deno`, or `bun`:
```bash
# Node.js, Cloudflare Workers, or other node-compatible runtimes
npm i mono-jsx
# Deno
deno add npm:mono-jsx
# Bun
bun add mono-jsx
```
### Setup JSX Runtime
To use mono-jsx as your JSX runtime, add the following configuration to your `tsconfig.json` (or `deno.json` for Deno):
```jsonc
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"jsxImportSource": "mono-jsx"
}
}
```
You can also run `mono-jsx setup` to automatically add the configuration to your project:
```bash
# Node.js, Cloudflare Workers, or other node-compatible runtimes
npx mono-jsx setup
# Deno
deno run -A npm:mono-jsx setup
# Bun
bunx mono-jsx setup
```
### Zero Configuration
Alternatively, you can use the `@jsxImportSource` pragma directive without installing mono-jsx (no package.json/tsconfig/node_modules). The runtime (Deno/Bun) automatically installs mono-jsx to your computer:
```js
// Deno, Valtown
/** @jsxImportSource https://esm.sh/mono-jsx */
// Bun
/** @jsxImportSource mono-jsx */
```
## Usage
mono-jsx allows you to return an `` JSX element as a `Response` object in the `fetch` handler:
```tsx
// app.tsx
export default {
fetch: (req) => (
Welcome to mono-jsx!
)
}
```
For Deno/Bun users, you can run the `app.tsx` directly:
```bash
deno serve app.tsx
bun app.tsx
```
If you're building a web app with [Cloudflare Workers](https://developers.cloudflare.com/workers/wrangler/commands/#dev), use `wrangler dev` to start your app in development mode:
```bash
npx wrangler dev app.tsx
```
**Node.js doesn't support JSX syntax or declarative fetch servers**, we recommend using mono-jsx with [srvx](https://srvx.h3.dev) and [tsx](https://tsx.is) (as JSX loader) to start your app:
```bash
# npm i srvx tsx
npx srvx --import tsx app.tsx
```
> [!NOTE]
> Only the root `` element will be rendered as a `Response` object. You cannot return a `
` or any other element directly from the `fetch` handler. This is a limitation of mono-jsx.
## Using JSX
mono-jsx uses [**JSX**](https://react.dev/learn/describing-the-ui) to describe the user interface, similar to React but with key differences.
### Using Standard HTML Property Names
mono-jsx adopts standard HTML property names, avoiding React's custom naming conventions:
- `className` β `class`
- `htmlFor` β `for`
- `onChange` β `onInput`
### Composition with `class`
mono-jsx allows you to compose the `class` property using arrays of strings, objects, or expressions:
```tsx
;
```
### Using Pseudo Classes and Media Queries in `style`
mono-jsx supports [pseudo classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes), [pseudo elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements), [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries), and [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting) in the `style` property:
```tsx
Link
;
```
### Using View Transition
mono-jsx supports [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) to create smooth transitions between views. To use view transitions, add the `viewTransition` attribute to the following components:
- ``
- ``
- ``
- ``
- ``
You can set custom transition animations by adding [`::view-transition-group`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-group), [`::view-transition-old`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-old), and [`::view-transition-new`](https://developer.mozilla.org/en-US/docs/Web/CSS/::view-transition-new) pseudo-elements with your own CSS animations. For example:
```tsx
function App(this: FC<{ show: boolean }>) {
return (
Hello world!
this.show = !this.show}>Toggle
)
}
```
You can also set the `viewTransition` attribute a html element which contains signal children.
```tsx
function App(this: FC<{ message: string }>) {
this.message = "Hello world!";
return (
{this.message}
)
}
```
You can also set the view transition name in the style property with the `viewTransition` attribute set to `true`.
```tsx
function App(this: FC<{ message: string }>) {
this.message = "Hello world!";
return (
{this.message}
)
}
```
### Using `` Element
mono-jsx uses [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) elements to render slotted content (equivalent to React's `children` property). You can also add the `name` attribute to define named slots:
```tsx
function Container() {
return (
{/* Default slot */}
{/* Named slot */}
)
}
function App() {
return (
{/* This goes to the named slot */}
This is a description.
{/* This goes to the default slot */}
Hello world!
)
}
```
### Using `html` Tag Function
mono-jsx provides an `html` tag function to render raw HTML, which is similar to React's `dangerouslySetInnerHTML`.
```tsx
function App() {
return
{html`Hello world!
`};
}
```
Variables in the `html` template literal are escaped. To render raw HTML without escaping, call the `html` function with a string literal.
```tsx
function App() {
return
{html(`${Hello world!
}`)};
}
```
The `html` function is globally available without importing. You can also use `css` and `js` functions for CSS and JavaScript:
```tsx
function App() {
return (
{css`h1 { font-size: 3rem; }`}
{js`console.log("Hello world!")`}
)
}
```
> [!WARNING]
> The `html` tag function is **unsafe** and can cause [**XSS**](https://en.wikipedia.org/wiki/Cross-site_scripting) vulnerabilities.
### Event Handlers
mono-jsx lets you write event handlers directly in JSX, similar to React:
```tsx
function Button() {
return (
alert("BOOM!")}>
Click Me
)
}
```
Event handlers are never called on the server-side. They're serialized to strings and sent to the client. **This means you should NOT use server-side variables or functions in event handlers.**
```tsx
import { doSomething } from "some-library";
function Button(this: FC<{ count: 0 }>, props: { role: string }) {
const message = "BOOM!"; // server-side variable
this.count = 0; // initialize a signal
console.log(message); // only prints on server-side
return (
{
alert(message); // β `message` is a server-side variable
console.log(props.role); // β `props` is a server-side variable
doSomething(); // β `doSomething` is imported on the server-side
Deno.exit(0); // β `Deno` is unavailable in the browser
document.title = "BOOM!"; // β
`document` is a browser API
console.log(evt.target); // β
`evt` is the event object
this.count++; // β
update the `count` signal
}}
>
)
}
```
mono-jsx allows you to use a function as the value of the `action` attribute of the `` element. The function will be called on form submission, and the `FormData` object will contain the form data.
```tsx
function App() {
return (
console.log(data.get("name"))}>
Submit
)
}
```
## Async Components
mono-jsx supports async components that return a `Promise` or an async function. With streaming rendering, async components are rendered asynchronously, allowing you to fetch data or perform other async operations before rendering the component.
```tsx
async function Loader(props: { url: string }) {
const data = await fetch(props.url).then((res) => res.json());
return ;
}
export default {
fetch: (req) => (
Loading...} />
)
}
```
You can also use async generators to yield multiple elements over time. This is useful for streaming rendering of LLM tokens:
```tsx
async function* Chat(props: { prompt: string }) {
const stream = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
stream: true,
});
for await (const event of stream) {
const text = event.choices[0]?.delta.content;
if (text) {
yield {text};
}
}
}
export default {
fetch: (req) => (
β} />
)
}
```
You can use `pending` to display a loading state while waiting for async components to render:
```tsx
async function Sleep({ ms }) {
await new Promise((resolve) => setTimeout(resolve, ms));
return ;
}
export default {
fetch: (req) => (
Loading...}>
After 1 second
)
}
```
You can set the `rendering` attribute to `"eager"` to force synchronous rendering (the `pending` property will be ignored):
```tsx
export default {
fetch: (req) => (
After 1 second
)
}
```
You can add the `catch` attribute to handle errors in the async component. The `catch` attribute should be a function that returns a JSX element:
```tsx
async function Hello() {
throw new Error("Something went wrong!");
return
Hello world!
;
}
export default {
fetch: (req) => (
{err.message}
} />
)
}
```
## Lazy Rendering
mono-jsx renders HTML on the server side and sends no hydration JavaScript to the client. To render a component dynamically on the client, you can use the `` element to ask the server to render a component:
To render a component by name, you can use the `` element with the `name` prop, and ensure the component is registered in the `components` prop of root `` element.
```tsx
function Foo(props: { bar: string }) {
return
{props.bar}
;
}
export default {
fetch: (req) => (
Loading...} />
)
}
```
You can use the `` element with the `is` prop to render a component by function reference without registering the component in the `components` prop of root `` element.
```tsx
export default {
fetch: (req) => (
Loading...} />
)
}
```
Or you can use the `` element with the `as` prop to render a component by JSX element.
```tsx
export default {
fetch: (req) => (
} pending={
Loading...
} />
)
}
```
You can also use [signals](#using-signals) for `name` or `props` attributes of a component. Changing the signal value will trigger the component to re-render with the new name or props:
```tsx
import { Profile, Projects, Settings } from "./pages.tsx"
function Dash(this: FC<{ page: "Profile" | "Projects" | "Settings" }>) {
this.page = "Profile";
return (
<>
this.page = "Profile"}>Profile
this.page = "Projects"}>Projects
this.page = "Settings"}>Settings
Loading...} />
>
)
}
export default {
fetch: (req) => (
)
}
```
You can use the `` element to control when to render a component:
```tsx
async function Lazy(this: FC<{ show: boolean }>, props: { url: string }) {
return (
Loading...} />
this.show = true }>Load `Foo` Component
)
}
export default {
fetch: (req) => (
)
}
```
## Using Signals
mono-jsx uses signals for updating the view when a signal changes. Signals are similar to React's state, but they are more lightweight and efficient. You can use signals to manage state in your components.
### Using Component Signals
You can use the `this` keyword in your components to manage signals. Signals are bound to the component instance, can be updated directly, and the view will automatically re-render when a signal changes:
```tsx
function Counter(this: FC<{ count: number }>, props: { initialCount?: number }) {
// Initialize a signal
this.count = props.initialCount ?? 0;
// or you can use `this.init` to initialize the signals
this.init({ count: props.initialCount ?? 0 });
return (
{/* render signal */}
{this.count}
{/* Update signal to trigger re-render */}
this.count--}>-
this.count++}>+
)
}
```
### Using App Signals
You can define app signals by adding the `app` prop to the root `` element. The app signals are available in all components via `this.app.`. Changes to the app signals will trigger re-renders in all components that use them:
```tsx
interface IAppSignals {
themeColor: string;
}
function Header(this: WithAppSignals) {
return (
Welcome to mono-jsx!
)
}
function Footer(this: WithAppSignals) {
return (
(c) 2025 mono-jsx.
)
}
function Main(this: WithAppSignals) {
return (
Theme Color:
)
}
export default {
fetch: (req) => (
)
}
```
### Using Computed Signals
You can use `this.computed` to create a derived signal based on other signals:
```tsx
function App(this: FC<{ input: string }>) {
this.input = "Welcome to mono-jsx";
return (
{this.computed(() => this.input + "!")}
)
}
```
> [!TIP]
> You can use `this.$` as a shorthand for `this.computed` to create computed signals.
### Using Effects
You can use `this.effect` to create side effects based on signals. The effect will run whenever the signal changes:
```tsx
function App(this: FC<{ count: number }>) {
this.count = 0;
this.effect(() => {
console.log("Count changed:", this.count);
});
return (
{this.count}
this.count++}>+
)
}
```
The callback function of `this.effect` can return a cleanup function that gets run once the component element has been removed via ``, `` or `` conditional rendering:
```tsx
function Counter(this: FC<{ count: number }>) {
this.count = 0;
this.effect(() => {
const interval = setInterval(() => {
this.count++;
}, 1000);
return () => clearInterval(interval);
});
return (
{this.count}
)
}
function App(this: FC<{ show: boolean }>) {
return (
this.show = !this.show }>{this.computed(() => this.show ? 'Hide': 'Show')}
)
}
```
### Using `` Element with Signals
The `` element conditionally renders content based on the `when` prop. You can use signals to control the visibility of the content on the client side.
```tsx
function App(this: FC<{ show: boolean }>) {
const toggle = () => {
this.show = !this.show;
}
return (
Welcome to mono-jsx!
{this.$(() => this.show ? "Hide" : "Show")}
)
}
```
mono-jsx also provides a `` element that is similar to ``, but it conditionally hides the content based on the `when` prop.
```tsx
function App(this: FC<{ hidden: boolean }>) {
return (
Welcome to mono-jsx!
)
}
```
If you need `if-else` logic in JSX, use `` element instead:
```tsx
function App(this: FC<{ ok: boolean }>) {
return (
True
False
)
}
```
### Using `` Element with Signals
The `` element renders different content based on the `value` prop. Elements with matching `slot` attributes are displayed when their value matches, otherwise default slots are shown. Like ``, you can use signals to control the value on the client side.
```tsx
function App(this: FC<{ lang: "en" | "zh" | "π" }>) {
this.lang = "en";
return (
Hello, world!
δ½ ε₯½οΌδΈηοΌ
βπβοΈ
this.lang = "en"}>English
this.lang = "zh"}>δΈζ
this.lang = "π"}>π
)
}
```
### Form Input Two-way Binding
You can use the `$value` attribute to bind a signal to the value of a form input element. The `$value` attribute is a two-way data binding, which means that when the input value changes, the signal will be updated, and when the signal changes, the input value will be updated.
```tsx
function App(this: FC<{ value: string }>) {
this.value = "Welcome to mono-jsx";
this.effect(() => {
console.log("value changed:", this.value);
});
// return this.value = e.target.value} />;
return ;
}
```
You can also use the `$checked` attribute to bind a signal to the checked state of a checkbox or radio input element.
```tsx
function App(this: FC<{ checked: boolean }>) {
this.effect(() => {
console.log("checked changed:", this.checked);
});
// return this.checked = e.target.checked} />;
return ;
}
```
### Limitations of Signals
1\. Arrow functions are non-stateful components.
```tsx
// β Won't work - uses `this` in a non-stateful component
const App = () => {
this.count = 0;
return (
{this.count}
this.count++}>+
)
};
// β
Works correctly
function App(this: FC) {
this.count = 0;
return (
{this.count}
this.count++}>+
)
}
```
2\. Signals cannot be computed outside of the `this.computed` method.
```tsx
// β Won't work - updates of a signal won't refresh the view
function App(this: FC<{ message: string }>) {
this.message = "Welcome to mono-jsx";
return (
{this.message + "!"}
this.message = "Clicked"}>
Click Me
)
}
// β
Works correctly
function App(this: FC) {
this.message = "Welcome to mono-jsx";
return (
this.message + "!")}>{this.$(() => this.message + "!")}
this.message = "Clicked"}>
Click Me
)
}
```
3\. The callback function of `this.computed` must be a pure function. This means it should not create side effects or access any non-stateful variables. For example, you cannot use `Deno` or `document` in the callback function:
```tsx
// β Won't work - throws `Deno is not defined` when the button is clicked
function App(this: FC<{ message: string }>) {
this.message = "Welcome to mono-jsx";
return (
{this.computed(() => this.message + "! (Deno " + Deno.version.deno + ")")}
this.message = "Clicked"}>
Click Me
)
}
// β
Works correctly
function App(this: FC<{ message: string, denoVersion: string }>) {
this.denoVersion = Deno.version.deno;
this.message = "Welcome to mono-jsx";
return (
{this.computed(() => this.message + "! (Deno " + this.denoVersion + ")")}
this.message = "Clicked"}>
Click Me
)
}
```
## Using `this` in Components
mono-jsx binds a scoped signals object to `this` of your component functions. This allows you to access signals, context, and request information directly in your components.
The `this` object has the following built-in properties:
- `app`: The app global signals.
- `context`: The context object defined on the root `` element.
- `request`: The request object from the `fetch` handler.
- `session`: The session storage.
- `refs`: A map of refs defined in the component.
- `computed(fn)`: A method to create a computed signal.
- `$(fn)`: A shortcut for `computed(fn)`.
- `effect(fn)`: A method to create side effects.
```ts
type FC = {
readonly app: AppSignals & { refs: AppRefs; url: WithParams }
readonly context: Context;
readonly request: WithParams;
readonly session: Session;
readonly refs: Refs;
readonly computed: (fn: () => T) => T;
readonly $: FC["computed"]; // A shortcut for `FC.computed`.
readonly effect: (fn: () => void | (() => void)) => void;
} & Signals;
// define `AppSignals` type
function Component(this: WithAppSignals) {
this.app.title // type: 'string'
}
// define `Context` type
function Component(this: WithContext) {
this.context.secret // type: 'string'
}
```
### Using Signals
See the [Using Signals](#using-signals) section for more details on how to use signals in your components.
### Using Refs
You can use `this.refs` to access refs in your components. Refs are defined using the `ref` attribute in JSX, and they allow you to access DOM elements directly. The `refs` object is a map of ref names to DOM elements.
```tsx
function App(this: WithRefs) {
this.effect(() => {
this.refs.input?.addEventListener("input", (evt) => {
console.log("Input changed:", evt.target.value);
});
});
return (
this.refs.input?.focus()}>Focus
)
}
```
You can also use `this.app.refs` to access app-level refs:
```tsx
function Layout(this: FC) {
return (
<>
Welcome to mono-jsx!
>
)
}
```
The `` element also supports the `ref` attribute, which allows you to control the component rendering manually. The `ref` will be a `ComponentElement` that has the `name`, `props`, and `refresh` properties:
- `name`: The name of the component to render.
- `props`: The props to pass to the component.
- `refresh`: A method to re-render the component with the current name and props.
```tsx
import type { ComponentElement } from "mono-jsx";
function App(this: WithRefs) {
this.effect(() => {
// updating the component name and props will trigger a re-render of the component
this.refs.component.name = "Foo";
this.refs.component.props = {};
const timer = setInterval(() => {
// re-render the component
this.refs.component.refresh();
}, 1000);
return () => clearInterval(timer); // cleanup
});
return (
)
}
```
### Using Context
You can use the `context` property in `this` to access context values in your components. The context is defined on the root `` element:
```tsx
function Dash(this: WithContext) {
const { auth } = this.context;
return (
Welcome back, {auth.name}!
Your UUID is {auth.uuid}
)
}
export default {
fetch: async (req) => {
const auth = await doAuth(req);
return (
{!auth &&
Please Login
}
{auth && }
)
}
}
```
### Accessing Request Info
You can access request information in components via the `request` property in `this` which is set on the root `` element:
```tsx
function RequestInfo(this: FC) {
const { request } = this;
return (
Request Info
{request.method}
{request.url}
{request.headers.get("user-agent")}
)
}
export default {
fetch: (req) => (
)
}
```
## Using Router (SPA mode)
mono-jsx provides a built-in `` element that allows your app to render components based on the current URL. On the client side, it listens to all `click` events on `` elements and asynchronously fetches the route component without reloading the entire page.
To use the router, you need to define your routes as a mapping of URL patterns to components and pass it to the `` element as the `routes` prop. The `request` prop is also required to match the current URL against the defined routes.
```tsx
const routes = {
"/": Home,
"/about": About,
"/blog": Blog,
"/post/:id": Post,
}
export default {
fetch: (req) => (
Home
About
Blog
)
}
```
The mono-jsx router requires [URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) to match routes:
- β
Deno
- β
Cloudflare Workers
- β
Node.js (>= 24)
For Bun users, mono-jsx provides a `buildRoutes` function that uses Bun's built-in server routing:
```tsx
import { buildRoutes } from "mono-jsx"
const routes = {
"/": Home,
"/about": About,
"/blog": Blog,
"/post/:id": Post,
}
Bun.serve({
routes: buildRoutes((req) => (
Home
About
Blog
))
})
```
### Using Route `params`
When you define a route with a parameter (e.g., `/post/:id`), mono-jsx will automatically extract the parameter from the URL and make it available in the route component. The `params` object is available in the `request` property of the component's `this` context.
You can access the `params` object in your route components to get the values of the parameters defined in the route pattern:
```tsx
// router pattern: "/post/:id"
function Post(this: FC) {
this.request.url // "http://localhost:3000/post/123"
this.request.params?.id // "123"
}
```
### Using Route Form
mono-jsx allows you to define a `FormHandler` function for route components to handle form data from form submissions on the current route page on the client side. To submit the form data to the `FormHandler` function, you need to set the `route` attribute on the `` element.
mono-jsx provides two built-in elements to allow you to control the post-submit behavior:
- `{message}` to set custom validation state for the form elements.
- `` to redirect to a new route/URL.
```tsx
async function Login(this: FC) {
return (
Login
)
}
// `FormHandler` function will be called when the form in '/login' route is submitted.
Login.FormHandler = function(this: FC, data: FormData) {
const user = await auth(data)
if (!user) {
return Invalid Username/Password
}
this.session.set("user", user)
return
}
const routes = {
"/login": Login,
// ... other routes ...
}
```
> [!NOTE]
> You can use `:invalid` CSS selector to style the form elements with invalid state.
You can also return regular HTML elements from the route form post response. The `formslot` element is used to
mark the position where the returned HTML elements will be inserted.
- ``: Replace the `formslot` element's children with the HTML. This is the default mode.
- ``: Insert HTML after the `formslot` element.
- ``: Insert HTML before the `formslot` element.
```tsx
function MyRoute(this: FC) {
return (
{/* <- new message will be inserted here */}
Send
)
}
MyRoute.FormHandler = function(this: FC, data: FormData) {
const message = data.get("message") as string | null;
if (!message) {
return Message is required
}
return
{message}
}
```
You can also use the `name` attribute to specify the name of the formslot element. And you can use the `formslot` attribute to specify the name of the slot to insert the HTML into.
```tsx
function MyRoute(this: FC) {
return (
Send
)
}
MyRoute.FormHandler = function(this: FC, data: FormData) {
return
Hello, world!
}
```
`formslot` element accepts the `onUpdate` attribute to set a callback function that will be called when the formslot element is updated.
```tsx
function MyRoute(this: FC) {
return (
Send
console.log("message updated:", evt.target.textContent)} />
)
}
MyRoute.FormHandler = function(this: FC, data: FormData) {
return
{data.get("message")}
}
```
The `hidden` attribute can be used to hide the formslot payload from the form handler.
```tsx
console.log("message updated:", evt.target.textContent)} hidden />
```
### Using `this.app.url` Signal
`this.app.url` is an app-level signal that contains the current route URL and parameters. The `this.app.url` signal is automatically updated when the route changes, so you can use it to display the current URL in your components or control the view with ``, `` or `` elements:
```tsx
function App(this: FC) {
return (
Current Pathname: {this.$(() => this.app.url.pathname)}
)
}
```
### Navigation between Pages
To navigate between pages, you can use `` elements with `href` attributes that match the defined routes. The router will intercept the click events of these links and fetch the corresponding route component without reloading the page:
```tsx
export default {
fetch: (req) => (
Home
About
Blog
)
}
```
### Nav Links
Links under the `` element will be treated as navigation links by the router. When the `href` of a nav link matches a route, an active class will be added to the link element. By default, the active class is `active`, but you can customize it by setting the `data-active-class` attribute on the `` element. You can add styles for the active link using nested CSS selectors in the `style` attribute of the `` element.
```tsx
export default {
fetch: (req) => (
Home
About
Blog
)
}
```
### Fallback (404)
You can add fallback(404) content to the `` element as children, which will be displayed when no route matches the current URL.
```tsx
export default {
fetch: (req) => (
Page Not Found
Back to Home
)
}
```
## Using Session
mono-jsx provides a built-in session storage that allows you to manage user sessions. To use the session storage, you need to set the `session` prop on the root `` element with the `cookie.secret` option.
```tsx
function Index(this: FC) {
const user = this.session.get<{ name: string }>("user")
if (!user) {
return
Please Login
}
return Welcome, {user.name}!
}
async function Login(this: FC) {
return (
Login
)
}
Login.FormHandler = async function(this: FC, data: FormData) {
const user = await auth(data)
if (!user) {
return Invalid Username/Password
}
this.session.set("user", user)
return
}
const routes = {
"/": Index,
"/login": Login,
}
export default {
fetch: (req) => (
)
}
```
### Session Storage API
```ts
function Component(this: FC) {
// set a value in the session
this.session.set("user", { name: "John" })
// get a value from the session
this.session.get<{ name: string }>("user") // { name: "John" }
// get all entries from the session
this.session.entries() // [["user", { name: "John" }]]
// delete a value from the session
this.session.delete("user")
// destroy the session
this.session.destroy()
}
```
## Caching
mono-jsx renders HTML dynamically per request; large apps may tax your CPU resources. To improve rendering performance, mono-jsx introduces two built-in elements that can cache the rendered HTML of the children:
- `` with specified `key` and `maxAge`
- `` for elements that rarely change, such as ``
```tsx
function BlogPage() {
return (
)
}
function Icon() {
return (
...
)
}
```
## Customizing HTML Response
You can add `status` or `headers` attributes to the root `` element to customize the HTTP response:
```tsx
export default {
fetch: (req) => (
Page Not Found
)
}
```
### Using htmx
mono-jsx integrates with [htmx](https://htmx.org/) and [typed-htmx](https://github.com/Desdaemon/typed-htmx). To use htmx, add the `htmx` attribute to the root `` element:
```tsx
export default {
fetch: (req) => {
const url = new URL(req.url);
if (url.pathname === "/clicked") {
return (
Clicked!
);
}
return (
Click Me
)
}
}
```
#### Adding htmx Extensions
You can add htmx [extensions](https://htmx.org/docs/#extensions) by adding the `htmx-ext-*` attribute to the root `` element:
```tsx
export default {
fetch: (req) => (
Click Me
)
}
```
#### Specifying htmx Version
You can specify the htmx version by setting the `htmx` attribute to a specific version:
```tsx
export default {
fetch: (req) => (
Click Me
)
}
```
#### Setting Up htmx Manually
By default, mono-jsx imports htmx from the [esm.sh](https://esm.sh/) CDN when you set the `htmx` attribute. You can also set up htmx manually with your own CDN or local copy:
```tsx
export default {
fetch: (req) => (
Click Me
)
}
```
## License
[MIT](LICENSE)