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

htmxx

v5.1.5

Published

Full but minimalist responsive webapp framework with no client-side Javascript

Downloads

27

Readme

HTMXX v5

This is a total rewrite since v4 and not compatible. Going forward; HTMXX will be maintaining this Typescript approach instead of the now deprecated specialist html files from pervious versions.

Back to the future of web development

A zero config full-stack web framework in Typescript using HTMX and a routing structure. Reactive and responsive web development with proper REST endpoints (i.e. returning HTML not JSON) And no need for any client-side Javascript. Use Hyperscript for reactivity.

todomvc htmx demo gif

How to install

npm i htmxx

Then just point it at a the directory that contains your routes folder. This will contain your web-app file structure used to generate a resource of all the endpoints. This resource can then be used by your server (e.g. Lambda function or ExpressJS) to render the endpoint.

How to use

Express server

const { Htmxx } = require('htmxx');
const htmxx = new Htmxx(process.cwd());
htmxx.startServer();

Routes folder

The web-app just consists of a routes folder containing the application routing structure as Typescript files, that define the server endpoints. e.g.

  • src/routes/index.ts defines a GET endpoint at /
  • src/routes/customers.post.ts defines a POST endpoint at /customers

Special files

Any Typescript files will be treated as an endpoint unless they begin with an underscore _ (e.g. _layout.ts). *.ts is a GET, other methods (POST, PUT, PATCH, DELETE) are supported in by adding the lowercase prefix to the Typescript extension e.g. *.post.ts is a POST. Special files _layout.ts and _error.ts can be defined anywhere within our routes structure, these serve the following. purposes:

Layout files

These files allow nesting of content in your routes structure allowing layouts to be created at any level of the routing tree. They must contain a ${children} instance inside the HTML template to tell the framework where the content is to be rendered. We add the import for HTMX in the root _layout.ts file.

e.g.

import type { HtmxxRequest } from 'htmxx';

export default (request: HtmxxRequest, children: string) => {
  return /*html*/ `
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <script src="https://unpkg.com/[email protected]"></script>
        <script src="https://unpkg.com/[email protected]"></script>
        <title>HTMXX Demo</title>
      </head>
    </head>
    <body>
      ${children}
    </body>
    </html>
`;
};

If we navigate to /, the index.ts will be rendered in the body.

Error files

These files are displayed if an error occurs in the app. An _error.ts in the same directory as the error occured or in a directory above will be rendered in the case of an error.

parameterised filenames

In many routing frameworks you have the ability to define variable path names e.g. customers/:customerId so that when a user navigates to customers/56 the code can retrieve the customerId as 56 and load the correct data to display. This framework follows the same principle and it is defined by the folder and file structure. If we create a folder structure customers/[customerId] we can place an index.ts in the customers folder and another index.ts in the [customerId] folder, or we could have a file called [customerId].ts in the customers folder. The <script server> block in customers/[customerId].ts or customers/[customerId]/index.ts would have access to the params variable which will contain customerId in this case. To handle a POST request for the specific customer you could use customers/[customerId]/index.post.ts or customers/[customerId].post.ts.

component files or helpers

Any other Typescript file starting with an underscore _ will be excluded from the routing, but may be used in the app. This is helpful for creating re-usable components and fragments.

Writing TS endpoint files

The Typescript endpoint files should export a default method that has (request: HtmxxRequest, children?: string) as arguments and return a string containing the rendered HTML, usually as a string literal e.g.

// src/routes/index.ts

import type { HtmxxRequest } from 'htmxx';
import cust from './customers'; // some model data

export default async (request: HtmxxRequest) => {
  const customers = await cust.getCustomers();

  return /*html*/ `
    <ul id="list">
      ${customers.map(({ name }) => `<li>${name}</li>`)}
    </ul>
    <div>
      <input type="text" name="customerName" />
    </div>
    <button hx-post="/customer" hx-swap="innerHtml" hx-target="#list">
      Add Name
    </button>
  `;
};

The HtmxxRequest request contains the following attributes.

type HtmxxRequest = {
  params: Record<string, string>;
  query: Record<string, string>;
  body: Record<string, string | number | undefined>;
  headers: Record<string, string>;
  redirect: (status: number, location: string) => void;
  broadcast: (markup: string) => void;
};

(the children argument is only used in layout files - see below)

e.g.

Redirecting from an endpoint

In some cases in your server-side code you may decide you need to redirect to a different route. to do this, simply call redirect(statusCode, location) in your script, the redirect method is available on the HtmxxRequest. e.g.

import type { HtmxxRequest } from 'htmxx';

export default async ({ redirect }: HtmxxRequest) => {
  redirect(303, '/');
};

this will return { redirect: { status, location } } from the callEndpoint method. You can use status and location to do the redirect in your server. (the Express server included, already handles this automatically)

Websockets

HTMX allows websocket support, so we have added it to our framework. In your HTMX files you can connect a form submission to a specific websocket endpoint like this

<div hx-ws="connect:/tweet">
  <form hx-ws="send:submit">...</form>
</div>

To support this we provide an extra file type *.ws.ts. In our example above we would define a websocket file like this:

// src/routes/tweet.ws.ts
import { HtmxxRequest } from 'htmxx';
import tweets from '~/model/tweets';
import Post from './_post';

export default ({ body }: HtmxxRequest) => {
  const { message, username } = body;
  const tweet = tweets.add(message, username);

  return Post(tweet);
};

The body attribute contains the data sent by the form submission, so the code here is no different to writing a normal POST. The markup that is rendered is now broadcast on the channel instead of returned in an HTTP response, but the HTMX is handled the same way by the form.

Other endpoints can choose to also broadcast their markup response as well as respond with it by calling the broadcast method available on the HtmxxRequest argument. The following POST endpoint generates a retweet fragment and broadcasts it to all clients.

// src/routes/retweet/[id].post.ts

import { HtmxxRequest } from 'htmxx';
import tweets from '~/model/tweets';
import Retweets from './_retweets';

export default ({ params }: HtmxxRequest) => {
  const { id } = body;
  const retweets = model.retweet(id);

  return Retweets(id, retweets);
};

See the examples/twitterClone folder for the full example.

Bespoke Server (e.g. serverless)

When using a serverless solution, you will be provided with a function handler that is responding to endpoint requests. In this case express is not a good solution. So you can use the Htmxx processRoute method directly without invoking the server. You will need to manage the response in the way your severless platform dictates.

// src/index.js
const { Htmxx } = require('htmxx');

// synchronously generate the list of all endpoints
const htmxx = new Htmxx('/src');
/* the full list of HtmxxFiles is now in htmxx.files */
console.log(htmxx.files);

// when a route is called with a specific method
const req: HtmxxRequest = {
  body, // parsed request body
  params, // parsed request params
  query, // parsed queries
  headers, // parsed header fields
  redirect, // method to redirect the router
  broadcast, // method to broadcast to websockets
};
const markup: string = await htmxx.processRoute(route, method, req);

TODO & Twitter Example

See the example folder for a TodoMVC and Twitter Clone implementation in HTMXX running on ExpressJS.

Resources