npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@hytts/hytts

v0.2.8

Published

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.

Downloads

5

Readme

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.