{"id":20563817,"url":"https://github.com/worker-tools/html","last_synced_at":"2025-04-14T14:52:24.514Z","repository":{"id":46808934,"uuid":"303045870","full_name":"worker-tools/html","owner":"worker-tools","description":"HTML templating and streaming response library for Service Worker-like environments such as Cloudflare Workers.","archived":false,"fork":false,"pushed_at":"2022-10-13T07:32:01.000Z","size":143,"stargazers_count":70,"open_issues_count":1,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-28T03:41:36.161Z","etag":null,"topics":["cloudflare-workers","fetch-api","html","response","service-worker","streams","templating","whatwg-streams","workers"],"latest_commit_sha":null,"homepage":"https://workers.tools/html","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/worker-tools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-11T05:01:31.000Z","updated_at":"2025-03-18T03:44:40.000Z","dependencies_parsed_at":"2023-01-19T23:47:11.636Z","dependency_job_id":null,"html_url":"https://github.com/worker-tools/html","commit_stats":null,"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/worker-tools%2Fhtml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/worker-tools%2Fhtml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/worker-tools%2Fhtml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/worker-tools%2Fhtml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/worker-tools","download_url":"https://codeload.github.com/worker-tools/html/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248900827,"owners_count":21180290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudflare-workers","fetch-api","html","response","service-worker","streams","templating","whatwg-streams","workers"],"created_at":"2024-11-16T04:21:12.533Z","updated_at":"2025-04-14T14:52:24.492Z","avatar_url":"https://github.com/worker-tools.png","language":"TypeScript","readme":"# Worker HTML\n\n[HTML templating](#html-templating) and [streaming response](#streaming-responses) library for [Worker Runtimes](https://workers.js.org) such as Cloudflare Workers.\n\n\n## HTML Templating\n\nTemplating 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). \n\nThis library is using tagged template strings to create _streaming response bodies_ on the fly,\nusing no special template syntax and giving you the full power of JS for composition. \n\n### Examples\nString interpolation works just like regular template strings,\nbut all content is sanitized by default:\n\n```ts\nconst helloWorld = 'Hello World!';\nconst h1El = html`\u003ch1\u003e${helloWorld}\u003c/h1\u003e`;\n```\n\nWhat is known as \"partials\" in string-based templating libraries are just functions here:\n\n```ts\nconst timeEl = (ts = new Date()) =\u003e html`\n  \u003ctime datetime=\"${ts.toISOString()}\"\u003e${ts.toLocalString()}\u003c/time\u003e\n`;\n```\n\nWhat is known as \"layouts\" are just functions as well:\n\n```ts\nconst baseLayout = (title: string, content: HTMLContent) =\u003e html`\n  \u003c!DOCTYPE html\u003e\n  \u003chtml lang=\"en\"\u003e\n    \u003chead\u003e\n      \u003cmeta charset=\"utf-8\"\u003e\n      \u003ctitle\u003e${title}\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e${content}\u003c/body\u003e\n  \u003c/html\u003e\n`;\n```\n\nLayouts can \"inherit\" from each other, again using just functions:\n\n```ts\nconst pageLayout = (title: string, content: HTMLContent) =\u003e baseLayout(title, html`\n  \u003cmain\u003e\n    ${content}\n    \u003cfooter\u003ePowered by @worker-tools/html\u003c/footer\u003e\n  \u003c/main\u003e\n`);\n```\n\nMany more features of string-based templating libraries can be replicated using functions.\nMost satisfying should be the use of `map` to replace a whole host of custom looping syntax:\n\n```ts\nhtml`\u003cul\u003e${['Foo', 'Bar', 'Baz'].map(x =\u003e html`\u003cli\u003e${x}\u003c/li\u003e`)}\u003c/ul\u003e`;\n```\n\nPutting it all together:\n\n```ts\nfunction handleRequest(event: FetchEvent) {\n  const page = pageLayout(helloWorld, html`\n    ${h1El}\n    \u003cp\u003eThe current time is ${timeEl()}.\u003c/p\u003e\n    \u003cul\u003e${['Foo', 'Bar', 'Baz'].map(x =\u003e html`\u003cli\u003e${x}\u003c/li\u003e`)}\u003c/ul\u003e\n  `));\n\n  return new HTMLResponse(page);\n}\n\nself.addEventListener('fetch', ev =\u003e ev.respondWith(handleRequest(ev)));\n```\n\nNote 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.\n\n### Tooling\nSince 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).\n\n\n## Streaming Responses\n\nAs 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. \n\nIn the example below, everything up to and including `\u003cp\u003eThe current time is` will be sent immediately, with the reset sent after the API request completes:\n\n```ts\nfunction handleRequest(event: FetchEvent) {\n  // NOTE: No `await` here!\n  const timeElPromise = fetch('https://time.api/now')\n    .then(r =\u003e r.text())\n    .then(t =\u003e timeEl(new Date(t)));\n\n  return new HTMLResponse(pageLayout('Hello World!', html`\n    \u003ch1\u003eHello World!\u003c/h1\u003e\n    \u003cp\u003eThe current time is ${timeElPromise}.\u003c/p\u003e\n  `));\n}\n```\n\nWhile 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:\n\n```ts\nfunction handleRequest(event: FetchEvent) {\n  return new HTMLResponse(pageLayout('Hello World!', html`\n    \u003ch1\u003eHello World!\u003c/h1\u003e\n    ${async () =\u003e {\n      const timeStamp = new Date(\n        await fetch('https://time.api/now').then(r =\u003e r.text())\n      );\n      return html`\u003cp\u003eThe current time is ${timeEl(timeStamp)}.\u003c/p\u003e`\n    }}\n  `));\n}\n```\n\nNote that there are some subtle differences compared to the earlier examples. \n- The initial response will include headers and html up to and including `\u003ch1\u003eHello World!\u003c/h1\u003e`\n- The time API request will not be sent until the headers and html up to and including `\u003ch1\u003eHello World!\u003c/h1\u003e` have hit the wire.\n\nThese follow from the way async/await works, so shouldn't be too surprising to those already familiar with common async/await pitfalls.\n\nIf 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.\n\n## See Other\nYou can combine this library with tools from the [Worker Tools family](https://workers.tools) such as `@worker-tools/response-creators`:\n\n```ts\nimport { internalServerError } from '@worker-tools/response-creators';\n\nfunction handleRequest(event: FetchEvent) {\n  return new HTMLResponse(\n    pageLayout('Ooops', html`\u003ch1\u003eSomething went wrong\u003c/h1\u003e`), \n    internalServerError(),\n  );\n}\n```\n\nYou 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.\n\nFinally, 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.\n\n\u003cbr/\u003e\n\n--------\n\n\u003cbr/\u003e\n\n\u003cp align=\"center\"\u003e\u003ca href=\"https://workers.tools\"\u003e\u003cimg src=\"https://workers.tools/assets/img/logo.svg\" width=\"100\" height=\"100\" /\u003e\u003c/a\u003e\n\u003cp align=\"center\"\u003eThis module is part of the Worker Tools collection\u003cbr/\u003e⁕\n\n[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. \n\nIf you liked this module, you might also like:\n\n- 🧭 [__Worker Router__][router] --- Complete routing solution that works across CF Workers, Deno and Service Workers\n- 🔋 [__Worker Middleware__][middleware] --- A suite of standalone HTTP server-side middleware with TypeScript support\n- 📄 [__Worker HTML__][html] --- HTML templating and streaming response library\n- 📦 [__Storage Area__][kv-storage] --- Key-value store abstraction across [Cloudflare KV][cloudflare-kv-storage], [Deno][deno-kv-storage] and browsers.\n- 🆗 [__Response Creators__][response-creators] --- Factory functions for responses with pre-filled status and status text\n- 🎏 [__Stream Response__][stream-response] --- Use async generators to build streaming responses for SSE, etc...\n- 🥏 [__JSON Fetch__][json-fetch] --- Drop-in replacements for Fetch API classes with first class support for JSON.\n- 🦑 [__JSON Stream__][json-stream] --- Streaming JSON parser/stingifier with first class support for web streams.\n\nWorker Tools also includes a number of polyfills that help bridge the gap between Worker Runtimes:\n- ✏️ [__HTML Rewriter__][html-rewriter] --- Cloudflare's HTML Rewriter for use in Deno, browsers, etc...\n- 📍 [__Location Polyfill__][location-polyfill] --- A `Location` polyfill for Cloudflare Workers.\n- 🦕 [__Deno Fetch Event Adapter__][deno-fetch-event-adapter] --- Dispatches global `fetch` events using Deno’s native HTTP server.\n\n[router]: https://workers.tools/router\n[middleware]: https://workers.tools/middleware\n[html]: https://workers.tools/html\n[kv-storage]: https://workers.tools/kv-storage\n[cloudflare-kv-storage]: https://workers.tools/cloudflare-kv-storage\n[deno-kv-storage]: https://workers.tools/deno-kv-storage\n[kv-storage-polyfill]: https://workers.tools/kv-storage-polyfill\n[response-creators]: https://workers.tools/response-creators\n[stream-response]: https://workers.tools/stream-response\n[json-fetch]: https://workers.tools/json-fetch\n[json-stream]: https://workers.tools/json-stream\n[request-cookie-store]: https://workers.tools/request-cookie-store\n[extendable-promise]: https://workers.tools/extendable-promise\n[html-rewriter]: https://workers.tools/html-rewriter\n[location-polyfill]: https://workers.tools/location-polyfill\n[deno-fetch-event-adapter]: https://workers.tools/deno-fetch-event-adapter\n\nFore more visit [workers.tools](https://workers.tools).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworker-tools%2Fhtml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fworker-tools%2Fhtml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fworker-tools%2Fhtml/lists"}