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

@werker/html

v1.1.4

Published

HTML templating and streaming response library for worker environments such as Cloudflare Workers

Downloads

7

Readme

@werker/html

HTML templating and streaming response library for Service Worker-like environments such as Cloudflare Workers.

HTML Templating

Templating is done purely in JavaScript using tagged template strings, inspired by hyperHTML and lit-html.

This library is using the way tagged template strings work to create streaming response bodies on the fly, using no special template syntax and giving you the full power of JS for composition.

String interpolation works just like regular template strings, but all content is sanitized by default.

const helloWorld = 'Hello World!';
const h1El = html`<h1>${helloWorld}</h1>`;

What is known as "partials" in string-based templating libraries are just functions here:

const timeEl = (ts = new Date()) => html`
  <time datetime="${ts.toISOString()}">${ts.toLocalString()}</time>
`;

What is known as "layouts" are just functions as well:

const baseLayout = (title: string, content: HTMLContent) => html`
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>${title}</title>
    </head>
    <body>${content}</body>
  </html>
`;

Layouts can "inherit" from each other, again using just functions:

const pageLayout = (title: string, content: HTMLContent) => baseLayout(title, html`
  <main>
    ${content}
    <footer>Powered by @werker/html</footer>
  </main>
`);

Many more features of string-based templating libraries can be replicated using functions. Most satisfying should be the use of map to replace a whole host of custom looping syntax:

html`<ul>${['Foo', 'Bar', 'Baz'].map(x => html`<li>${x}</li>`)}</ul>`;

Putting it all together:

function handleRequest(event: FetchEvent) {
  const page = pageLayout(helloWorld, html`
    ${h1El}
    <p>The current time is ${timeEl()}.</p>
    <ul>${['Foo', 'Bar', 'Baz'].map(x => html`<li>${x}</li>`)}</ul>
  `));

  return new HTMLResponse(page);
}

self.addEventListener('fetch', ev => ev.respondWith(handleRequest(ev)));

Note that this works regardless of worker environment: Cloudflare Workers, Service Workers in the browser, and hopefully other worker environments that have yet to be implemented.

Since the use of tagged string literals for HTML is not new (see above), there exists tooling for syntax highlighting, such as lit-html in VSCode.

Streaming Responses

As a side effect of this approach, responses are streams by default. This means you can use async data, without delaying sending the headers and HTML content.

In this example, everything up to and including <p>The current time is will be sent immediately:

function handleRequest(event: FetchEvent) {
  // NOTE: No `await` here!
  const timeElPromise = fetch('https://time.api/now')
    .then(r => r.text())
    .then(t => timeEl(new Date(t)));

  return new HTMLResponse(pageLayout('Hello World!', html`
    <h1>Hello World!</h1>
    <p>The current time is ${timeElPromise}.</p>
  `));
}

While there's ways around the lack of async/await in the above example (namely IIAFEs), @werker/html supports passing async functions as html content directly:

function handleRequest(event: FetchEvent) {
  return new HTMLResponse(pageLayout('Hello World!', html`
    <h1>Hello World!</h1>
    ${async () => {
      const timeStamp = new Date(
        await fetch('https://time.api/now').then(r => r.text())
      );
      return html`<p>The current time is ${timeEl(timeStamp)}.</p>`
    }}
  `));
}

Note that there are some subtle differences here (these follow from the way async/await works):

  • The initial response will include headers and html up to and including <h1>Hello World!</h1>
  • The time API request will not be sent until the headers and html up to and including <h1>Hello World!</h1> have hit the wire.

If for any reason you don't want to use streaming response bodies, you can import the BufferedHTMLResponse instead, which will buffer the entire body before releasing it to the network.

See Other

You can combine this library with tools from the @werker family of tools such as @werker/response-creators:

import { internalServerError } from '@werker/response-creators';

function handleRequest(event: FetchEvent) {
  return new HTMLResponse(
    pageLayout('Ooops', html`<h1>Something went wrong</h1>`), 
    internalServerError(),
  );
}

You can also see the Clap Button Worker source code for an example of how to build an entire web app on the edge using Cloudflare Workers and @werker tools, including @werker/html.

Finally, you can read The Joys and Perils of Writing Plain Old Web Apps for a personal account of building web apps in a Web 2.0 way.