Skip to content
/ htsx Public

HTSX - Minimal Deno SSR framework on vanilla HTML

License

Notifications You must be signed in to change notification settings

xlsft/htsx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation



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/[email protected]"></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/[email protected]/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/[email protected]/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:

  • get
  • head
  • patch
  • options
  • delete
  • post
  • put

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

πŸ“œ License

MIT