UNPKG

8.11 kBMarkdownView Raw
1# Worker HTML
2
3[HTML templating](#html-templating) and [streaming response](#streaming-responses) library for [Worker Runtimes](https://workers.js.org) such as Cloudflare Workers.
4
5
6## HTML Templating
7
8Templating is done purely in JavaScript using tagged template strings, inspired by [hyperHTML](https://github.com/WebReflection/hyperhtml) and [lit-html](https://github.com/polymer/lit-html).
9
10This library is using tagged template strings to create _streaming response bodies_ on the fly,
11using no special template syntax and giving you the full power of JS for composition.
12
13### Examples
14String interpolation works just like regular template strings,
15but all content is sanitized by default:
16
17```ts
18const helloWorld = 'Hello World!';
19const h1El = html`<h1>${helloWorld}</h1>`;
20```
21
22What is known as "partials" in string-based templating libraries are just functions here:
23
24```ts
25const timeEl = (ts = new Date()) => html`
26 <time datetime="${ts.toISOString()}">${ts.toLocalString()}</time>
27`;
28```
29
30What is known as "layouts" are just functions as well:
31
32```ts
33const baseLayout = (title: string, content: HTMLContent) => html`
34 <!DOCTYPE html>
35 <html lang="en">
36 <head>
37 <meta charset="utf-8">
38 <title>${title}</title>
39 </head>
40 <body>${content}</body>
41 </html>
42`;
43```
44
45Layouts can "inherit" from each other, again using just functions:
46
47```ts
48const pageLayout = (title: string, content: HTMLContent) => baseLayout(title, html`
49 <main>
50 ${content}
51 <footer>Powered by @worker-tools/html</footer>
52 </main>
53`);
54```
55
56Many more features of string-based templating libraries can be replicated using functions.
57Most satisfying should be the use of `map` to replace a whole host of custom looping syntax:
58
59```ts
60html`<ul>${['Foo', 'Bar', 'Baz'].map(x => html`<li>${x}</li>`)}</ul>`;
61```
62
63Putting it all together:
64
65```ts
66function handleRequest(event: FetchEvent) {
67 const page = pageLayout(helloWorld, html`
68 ${h1El}
69 <p>The current time is ${timeEl()}.</p>
70 <ul>${['Foo', 'Bar', 'Baz'].map(x => html`<li>${x}</li>`)}</ul>
71 `));
72
73 return new HTMLResponse(page);
74}
75
76self.addEventListener('fetch', ev => ev.respondWith(handleRequest(ev)));
77```
78
79Note that this works regardless of worker runtime: Cloudflare Workers, Service Workers in the browser, and hopefully other [Worker Runtimes](https://workers.js.org) that have yet to be implemented.
80
81### Tooling
82Since the use of tagged string literals for HTML is not new (see hyperHTML, lit-html), there exists tooling for syntax highlighting, such as [`lit-html` in VSCode](https://marketplace.visualstudio.com/items?itemName=bierner.lit-html).
83
84
85## Streaming Responses
86
87As 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.
88
89In the example below, everything up to and including `<p>The current time is` will be sent immediately, with the reset sent after the API request completes:
90
91```ts
92function handleRequest(event: FetchEvent) {
93 // NOTE: No `await` here!
94 const timeElPromise = fetch('https://time.api/now')
95 .then(r => r.text())
96 .then(t => timeEl(new Date(t)));
97
98 return new HTMLResponse(pageLayout('Hello World!', html`
99 <h1>Hello World!</h1>
100 <p>The current time is ${timeElPromise}.</p>
101 `));
102}
103```
104
105While there's ways around the lack of async/await in the above example (namely IIAFEs), @worker-tools/html supports passing async functions as html content directly:
106
107```ts
108function handleRequest(event: FetchEvent) {
109 return new HTMLResponse(pageLayout('Hello World!', html`
110 <h1>Hello World!</h1>
111 ${async () => {
112 const timeStamp = new Date(
113 await fetch('https://time.api/now').then(r => r.text())
114 );
115 return html`<p>The current time is ${timeEl(timeStamp)}.</p>`
116 }}
117 `));
118}
119```
120
121Note that there are some subtle differences compared to the earlier examples.
122- The initial response will include headers and html up to and including `<h1>Hello World!</h1>`
123- 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.
124
125These follow from the way async/await works, so shouldn't be too surprising to those already familiar with common async/await pitfalls.
126
127If for any reason you don't want to use streaming response bodies, you can use the `BufferedHTMLResponse` instead, which will buffer the entire body before releasing it to the network.
128
129## See Other
130You can combine this library with tools from the [Worker Tools family](https://workers.tolls) such as `@worker-tools/response-creators`:
131
132```ts
133import { internalServerError } from '@worker-tools/response-creators';
134
135function handleRequest(event: FetchEvent) {
136 return new HTMLResponse(
137 pageLayout('Ooops', html`<h1>Something went wrong</h1>`),
138 internalServerError(),
139 );
140}
141```
142
143You can also see the [Worker News source code](https://github.com/worker-tools/worker-news) for an example of how to build an entire web app on the edge using Worker HTML.
144
145Finally, you can read [The Joys and Perils of Writing Plain Old Web Apps](https://qwtel.com/posts/software/the-joys-and-perils-of-writing-plain-old-web-apps/) for a personal account of building web apps in a Web 2.0 way.
146
147--------
148
149<p align="center"><a href="https://workers.tools"><img src="https://workers.tools/assets/img/logo.svg" width="100" height="100" /></a>
150<p align="center">This module is part of the Worker Tools collection<br/>⁕
151
152[Worker Tools](https://workers.tools) are a collection of TypeScript libraries for writing web servers in [Worker Runtimes](https://workers.js.org) such as Cloudflare Workers, Deno Deploy and Service Workers in the browser.
153
154If you liked this module, you might also like:
155
156- 🧭 [__Worker Router__][router] --- Complete routing solution that works across CF Workers, Deno and Service Workers
157- πŸ”‹ [__Worker Middleware__][middleware] --- A suite of standalone HTTP server-side middleware with TypeScript support
158- πŸ“„ [__Worker HTML__][html] --- HTML templating and streaming response library
159- πŸ“¦ [__Storage Area__][kv-storage] --- Key-value store abstraction across [Cloudflare KV][cloudflare-kv-storage], [Deno][deno-kv-storage] and browsers.
160- πŸ†— [__Response Creators__][response-creators] --- Factory functions for responses with pre-filled status and status text
161- 🎏 [__Stream Response__][stream-response] --- Use async generators to build streaming responses for SSE, etc...
162- πŸ₯ [__JSON Fetch__][json-fetch] --- Drop-in replacements for Fetch API classes with first class support for JSON.
163- πŸ¦‘ [__JSON Stream__][json-stream] --- Streaming JSON parser/stingifier with first class support for web streams.
164
165Worker Tools also includes a number of polyfills that help bridge the gap between Worker Runtimes:
166- ✏️ [__HTML Rewriter__][html-rewriter] --- Cloudflare's HTML Rewriter for use in Deno, browsers, etc...
167- πŸ“ [__Location Polyfill__][location-polyfill] --- A `Location` polyfill for Cloudflare Workers.
168- πŸ¦• [__Deno Fetch Event Adapter__][deno-fetch-event-adapter] --- Dispatches global `fetch` events using Deno’s native HTTP server.
169
170[router]: https://workers.tools/router
171[middleware]: https://workers.tools/middleware
172[html]: https://workers.tools/html
173[kv-storage]: https://workers.tools/kv-storage
174[cloudflare-kv-storage]: https://workers.tools/cloudflare-kv-storage
175[deno-kv-storage]: https://workers.tools/deno-kv-storage
176[kv-storage-polyfill]: https://workers.tools/kv-storage-polyfill
177[response-creators]: https://workers.tools/response-creators
178[stream-response]: https://workers.tools/stream-response
179[json-fetch]: https://workers.tools/json-fetch
180[json-stream]: https://workers.tools/json-stream
181[request-cookie-store]: https://workers.tools/request-cookie-store
182[extendable-promise]: https://workers.tools/extendable-promise
183[html-rewriter]: https://workers.tools/html-rewriter
184[location-polyfill]: https://workers.tools/location-polyfill
185[deno-fetch-event-adapter]: https://workers.tools/deno-fetch-event-adapter
186
187Fore more visit [workers.tools](https://workers.tools).