HTSX - Hyper Text (on) Server eXtended
HTSX - Minimal Deno SSR framework on vanilla HTML
๐ Useful Links
๐พ Installation
import { HTSX } from 'https://deno.land/x/htsx/mod.ts'
import { Application } from 'https://deno.land/x/oak/mod.ts'
import { Router } from 'https://deno.land/x/oak/router.ts'๐ Quickstart
HTSX - basically, template engine with extra functions, like:
- Automatic handler for HTTP requests
- Filesystem-based routing
- Minifier on endpoints
- Pure HTML components
- REST API constructor
To start, replicate this filesystem
Root
โ main.ts (main file)
โ
โโโโweb
โ +root.ts (<App/> like component)
โ +root.css (global css)
โ +error.ts (error component)
โ +root.ts (error css)
โ
โโโโcomponents
โ Button.ts (pure HTML component)
โ
โโโโroutes
โ +view.ts (server component)
โ +view.js (client component)
โ +view.css (client css)
โ
โโโโapi
โโโโv1/user
+users.ts (simple users array for demonstartion)
+get.ts (GET handler)
+post.ts (POST handler)
โ
โ ...+<method>.ts (supported http method handler)For starters, letโs create basic Oak server and pass app and router in HTSX class
main.ts
import { HTSX } from 'https://deno.land/x/htsx/mod.ts'
import { Application } from 'https://deno.land/x/oak/mod.ts'
import { Router } from 'https://deno.land/x/oak/router.ts'
const app = new Application()
const router = new Router()
await new HTSX({
root: './web',
server: { app, router, init: () => { app.listen({ port: 8080 }); console.log('Server listening on 8080') } },
props: {
payload: async (ctx: Context) => {
return { user: "John" }
}
}
})Then you need to create +root.ts file on root folder that you provide to HTSX class
Itโs like </App> component in React
web/+root.ts
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";
export default (props: HTSXProps) => { return /*html*/`
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
${props.body} <!-- required -->
</body>
</html>
`}If you need to style root component, create:
web/+root.css
body {
background: lightblue;
}Basic config is over, now you can create endpoints for your server in routes folder, for example - view endpoint on / and Button component
web/components/Button.ts
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";
export default (props: HTSXProps) => { return /*html*/`
<button onclick="${props.onclick}">
${props.name}
</button>
<style>
button {
background: violet
}
</style>
`}web/routes/+view.ts
import Button from "../components/Button.ts";
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";
// In view components you can export HEAD if you want to specify title, preload something or put script from CDN exclusively on this endpoint
export const HEAD = /*html*/`
<title>Hello World</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300..900;1,300..900&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@tma.js/sdk@1.3.0"></script>
<style>
* {
font-family: "Rubik", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
}
</style>
`
export default (props: HTSXProps) => { return /*html*/`
<main>
<h1>hello, ${props.payload.name}! press da button!</h1>
${Button({ name: 'PRESS ME!!!!', onclick: `click_handler()` })}
</main>
`}web/routes/+view.js
// This is vanilla browser JS
function click_handler() {
alert('Hello World!')
}web/routes/+view.css
h1 {
color: red
}
Also you can specify error page when status !== 200
web/+error.ts
import { HTSXProps } from "../mod.ts";
export default (props: HTSXProps) => { return /*html*/`
<h1>ERROR: ${props.ctx?.response.status}</h1>
`}web/+error.css
body {
background: red;
color: white;
}
Now letโs create REST API endpoint
Create any supported +<method>.ts in any directory inside routes directory, for example:
/web/routes/api/v1/user/users.ts
export const users = [
{ id: 1, name: 'John Doe', age: 28 },
{ id: 2, name: 'Foo Bar', age: 99 },
{ id: 3, name: 'I love Deno', age: 21 },
{ id: 4, name: 'I love Bun', age: 1 }
]/web/routes/api/v1/user/+get.ts
import { Context } from "https://deno.land/x/oak@v13.0.0/mod.ts";
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";
import { users } from './users.ts'
export default (ctx: Context, props: HTSXProps) => {
const id = Number(ctx.request.url.searchParams.get('id'))
const user = users.find(user => user.id === id)
if (user === undefined)
return { error: `no user with ${id} id` };
else
return user
}/web/routes/api/v1/user/+post.ts
import { Context } from "https://deno.land/x/oak@v13.0.0/mod.ts";
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";
export default (ctx: Context, props: HTSXProps) => {
return { id: 1, name: 'John Doe', age: 28, }
}
Supported methods:
getheadpatchoptionsdeletepostput
So here is example how to quickly create basic API and HTML site with cool DX
Since itโs pure HTML, you can safely plug in any library from React to HTMX
๐ค Use case
This โframeworkโ was created for convenient work with Telegram mini applications, for the lack of need to use something like Fresh for a basic one-page interface the idea to create this library was born.
Your use case may be different, but it will still be a handy tool for achieving simple goals