Socket
Socket
Sign inDemoInstall

@hytts/hytts

Package Overview
Dependencies
18
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    @hytts/hytts

HyTTS (pronounced "heights") is a JSX-based full-stack framework with end-to-end type safety for server-side rendered web apps, inspired by Turbo and htmx.


Version published
Weekly downloads
5
increased by66.67%
Maintainers
1
Install size
10.1 MB
Created
Weekly downloads
 

Readme

Source

HyperText TypeScript (HyTTS)

HyTTS (pronounced "heights") is a full-stack web framework for server-side rendered web apps written in TypeScript. End-to-end type safety from server code to browser code and back is one of its major design principles. While HyTTS is heavily inspired by React and its JSX-based, component-oriented and declarative nature, it exclusively uses server-side rendering instead, with SPA-like interactivity based on concepts found in Hotwire Turbo and htmx.

HyTTS' goal is to reduce the complexity of modern-day web development while retaining the user and developer experience improvements achieved by the web development community in recent years.

Project Status

HyTTS is currently under development in my spare time, after having completed a successful experimentation and prototyping phase. Nevertheless, things will likely change considerably in an effort to enhance the feature set and to reduce the complexity of HyTTS' API and implementation.

Thus, HyTTS is not yet ready for production use and it is not yet extensively documented. Prerelease versions are already available on NPM.

HyTTS Overview

HyTTS features a hypermedia-driven application architecture. Its basic abstraction are (synchronous or asynchronous) JSX components, similar to React Server Components. Just like with React, these components are only ever executed on the server and never reach the browser. In contrast to React, however, the server always renders the components to HTML instead of serialized JSX.

HyTTS has no concept similar to client-side React components. There is no hydration, no resumability, no islands; in fact, there is no JSX-related client-side interactivity whatsoever. Interactivity is instead achieved through additional server roundtrips to update explicitly marked dynamic parts of the DOM, similar to Turbo Frames. For more fine-grained control, explicitly defined browser scripts can be used, i.e., inline TypeScript code that gets serialized to the browser without requiring a bundling step.

JSX Components

The following example of a HyTTS JSX component shows that superficially, HyTTS and React Server Components look mostly identical:

type GreetingProps = {
    readonly userId: number;
}

async function Greeting(props: GreetingProps): Promise<JsxElement> {
    const userName = await loadUserNameFromDatabase(props.userId);
    return <p>Hello, {userName}!</p>;
}

A component can be synchronous or asynchronous. It can optionally take a single props argument, and it returns a value of type JsxElement or Promise<JsxElement>. Similar to React, you can pass data deeply with the Context API:

const UserIdContext = createContext<number>();

function ParentComponent() {
    const userId = // get from request cookie, for instance
    return (
        <UserIdContext value={userId}>
            <ChildComponent />
        </UserIdContext>
    );
}

function ChildComponent() {
    const userId = useContext(UserIdContext);
    return <p>{userId}</p>;
}

The useContext function is similar to React's useContext hook, except that invocations of the function do not have to follow the rules of hooks in HyTTS. HyTTS users are expected to write their own abstractions around the useContext function, which, by convention, also start with the word use just like in React. Contexts in HyTTS typically model request-specific data, and the use prefix thus signals that something happens that is specific to the currently executing request.

Routing

HyTTS features a type-safe router that takes incoming HTTP requests, routes them to the correct JSX components, and returns the rendered HTML in the HTTP responses. The router uses Zod to validate all incoming path, search, or body parameters.

const docsRoutes = routes({
    "GET /": GreetingSelector,
    "GET /greet": route(z.object({ name: z.string().trim().min(1) }), Greeting),
});

const href = getHrefs(docsRoutes);

function GreetingSelector() {
    return (
        <>
            <A href={href("GET /greet", { name: "Axel" })}>Greet Axel</A>
            <A href={href("GET /greet", { name: "HyTTS" })}>Greet HyTTS</A>
        </>
    );
}

function Greeting(props: { name: string }) {
    return (
        <>
            <p>Hello, {props.name}!</p>
            <A href={href("GET /")}>Back To Overview</A>
        </p>
    );
}

There can also be POST routes, for instance if HTML forms are used. The href function is fully type-safe, meaning that it ensures at the type-level that the referenced URLs for the given HTTP methods actually exist and that all necessary path, search, and body parameters are provided and typed correctly.

Browser Scripts

It is sometimes necessary to execute client-side JavaScript, for instance when a page loads or when some button is clicked. Browser scripts enable these scenarios in a type-safe way. They also ensure that no potentially sensitive server data gets accidentally leaked to the browser by not allowing any closures. If you want to pass data along, you have to do so explicitly using a "double lambda pattern", where the outer lambda provides the explicit closure over the server data.

function ClientSideScripting() {
    const nameOnServer = "Axel";

    return (
        <>
            {/*
                A function that is executed once this component's HTML is added to the DOM.
                Its "explicit closure" is empty, meaning it cannot reference any server data.
            */}
            <Script
                script={createBrowserScript(
                    () => console.log("component loaded")
                )}
            />
            <button
                {/*
                    When the button is clicked, the inner lambda is executed.
                    - `e` is of type `EventArgs<HTMLAnchorElement, MouseEvent>`
                    - `nameInBrowser` is automatically deduced to be of type `string`
                      and contains the value `"Axel"` at runtime.
                */}
                browser:onclick={createEventHandler((nameInBrowser) => (e) => {
                    e.preventDefault();
                    alert(`Hello, ${nameInBrowser}!`);
                }, nameOnServer)}
            >
                Patients
            </button>
        </>
    );
}

Frames

A frame is a dynamic part of an HTML page whose contents can be replaced with HTML returned from fetch requests. The frame's HTML nodes are not simply replaced, they are merged with the new HTML nodes returned by the server using a variant of React's reconciliation algorithm. This ensures that certain browser state, such as CSS animations, focused nodes, or scroll positions of textareas don't get lost on frame updates.

const frameRoutes = routes({
    "GET /": RenderPage,
    "GET /my-frame": RenderFrame,
});

const href = getHrefs(frameRoutes);
const MyFrame = createFrame("myFrame");

function RenderPage() {
    return (
        <>
            <A href={href("GET /my-frame")} target={MyFrame}>
                Update frame
            </A>
            <RenderFrame />
        </>
    );
}

function RenderFrame() {
    return (
        <MyFrame>
            {Date.now()}
        </MyFrame>
    );
}

The frame gets rendered for the first time when RenderPage is executed on the original page load. Once the user clicks on the link, HyTTS' runtime library issues a fetch request to the server, which returns the HTML produced by RenderFrame. The frame's new contents get merged into the current DOM, in this case simply replacing the original request's date with the date the server rendered the response of the click event.

Keywords

FAQs

Last updated on 01 Apr 2024

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc