UNPKG

hytypemedia

Version:

Minimal typed HTML templating helpers for Hono/Workers/HTMX. JSX-free, type-safe HTML generation with automatic escaping.

372 lines (270 loc) 8.81 kB
# HyTypeMedia [![npm version](https://img.shields.io/npm/v/hytypemedia.svg)](https://www.npmjs.com/package/hytypemedia) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) Minimal typed HTML templating helpers specifically designed for **Hono**, **Cloudflare Workers**, and **HTMX**. A JSX-free approach to HTML templating that generates safe, escaped HTML strings with full TypeScript support. ## Features - **Type-Safe**: Full TypeScript support with IntelliSense for all HTML elements and attributes - **Secure**: Automatic HTML escaping prevents XSS attacks - **Minimal**: Zero dependencies, optimized for edge runtimes - **Complete**: Support for all HTML5 elements (67+ elements) - **Targeted**: Built specifically for Hono/Workers/HTMX workflows - **Modern**: ESM-first with tree-shaking support ## Quick Start ```bash npm install hytypemedia ``` ```typescript import { div, h1, p, button, html, head, title, body } from 'hytypemedia'; // Simple elements const greeting = h1('Hello, World!'); // → <h1>Hello, World!</h1> // Elements with attributes const styledDiv = div({ class: 'container', id: 'main' }, 'Content'); // → <div class="container" id="main">Content</div> // Complete HTML documents const page = html({ lang: 'en' }, head(title('My Page')), body( h1('Welcome'), p('This is a typed HTML template.') ) ); ``` ## Core Concepts ### Element Functions Every HTML tag has a corresponding function: ```typescript import { div, span, a, img, input } from 'hytypemedia'; div('Hello') // <div>Hello</div> span({ class: 'highlight' }, 'Text') // <span class="highlight">Text</span> a({ href: '/home' }, 'Home') // <a href="/home">Home</a> img({ src: 'logo.png', alt: 'Logo' }) // <img src="logo.png" alt="Logo"> input({ type: 'email', required: true }) // <input type="email" required> ``` ### Automatic Escaping All content is automatically escaped for security: ```typescript import { div, raw } from 'hytypemedia'; // Automatically escaped div('<script>alert("xss")</script>') // → <div>&lt;script&gt;alert("xss")&lt;/script&gt;</div> // Explicit raw HTML (use only with trusted content) div(raw('<strong>Bold</strong>')) // → <div><strong>Bold</strong></div> ``` ### Class and Style Normalization ```typescript import { div } from 'hytypemedia'; // String classes div({ class: 'btn primary' }) // → <div class="btn primary"></div> // Array classes div({ class: ['btn', 'primary'] }) // → <div class="btn primary"></div> // Object classes (conditional) div({ class: { btn: true, primary: true, disabled: false } }) // → <div class="btn primary"></div> // Object styles (camelCase → kebab-case) div({ style: { fontSize: '16px', backgroundColor: 'blue' } }) // → <div style="font-size:16px;background-color:blue"></div> ``` ### Content Types Supports multiple content types: ```typescript import { div } from 'hytypemedia'; div('string') // Strings div(42) // Numbers div(['hello', ' ', 'world']) // Arrays (flattened) div(null) // null/undefined (ignored) div(raw('<em>html</em>')) // SafeHtml objects ``` ## Advanced Usage ### Document Structure with Direct Nesting ```typescript import { html, head, meta, title, body, h1, p } from 'hytypemedia'; const document = html({ lang: 'en' }, head( meta({ charset: 'utf-8' }), meta({ name: 'viewport', content: 'width=device-width, initial-scale=1' }), title('My App') ), body( h1('Welcome to My App'), p('Built with HyTypeMedia') ) ); ``` ### Forms and Inputs ```typescript import { form, label, input, textarea, button, select, option } from 'hytypemedia'; const contactForm = form({ method: 'post', action: '/contact' }, label({ for: 'email' }, 'Email:'), input({ type: 'email', id: 'email', name: 'email', required: true }), label({ for: 'message' }, 'Message:'), textarea({ id: 'message', name: 'message', rows: 4 }), select({ name: 'country' }, option({ value: 'us' }, 'United States'), option({ value: 'uk' }, 'United Kingdom') ), button({ type: 'submit' }, 'Send Message') ); ``` ### Data Attributes and ARIA ```typescript import { button, div } from 'hytypemedia'; // Data attributes const component = div({ 'data-testid': 'user-card', 'data-user-id': 123, 'data-active': 'true' }); // ARIA attributes const accessibleButton = button({ 'aria-label': 'Close dialog', 'aria-expanded': false, 'aria-controls': 'menu' }, '×'); ``` ### Custom Elements ```typescript import { customElement } from 'hytypemedia'; const myCustomElement = customElement('my-component'); const result = myCustomElement({ prop: 'value' }, 'Content'); // → <my-component prop="value">Content</my-component> ``` ## Framework Integration ### Hono Example ```typescript import { Hono } from 'hono'; import { div, h1, p, a } from 'hytypemedia'; const app = new Hono(); app.get('/', (c) => { const html = div({ class: 'container' }, h1('Welcome to My Site'), p('Built with Hono and HyTypeMedia'), a({ href: '/about' }, 'Learn More') ); return c.html(html); }); ``` ### HTMX Integration ```typescript import { div, button, form, input } from 'hytypemedia'; const htmxForm = form({ 'hx-post': '/api/users', 'hx-target': '#user-list', 'hx-swap': 'beforeend' }, input({ type: 'text', name: 'name', placeholder: 'Enter name' }), button({ type: 'submit' }, 'Add User') ); const htmxButton = button({ 'hx-get': '/api/data', 'hx-target': '#content', 'hx-indicator': '#spinner' }, 'Load Data'); ``` ## API Reference ### Element Functions All HTML5 elements are available as functions: **Text Content**: `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `p`, `span`, `div`, `a`, `strong`, `em`, `code`, etc. **Form Elements**: `form`, `input`, `textarea`, `select`, `option`, `button`, `label`, `fieldset`, `legend` **Table Elements**: `table`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`, `caption`, `colgroup`, `col` **Media Elements**: `img`, `video`, `audio`, `source`, `track`, `canvas`, `svg` **Semantic Elements**: `main`, `section`, `article`, `aside`, `header`, `footer`, `nav`, `figure`, `figcaption` **Meta Elements**: `head`, `title`, `meta`, `link`, `style`, `script`, `base` ### Helper Functions #### `Fragment(...children)` Combines multiple elements without a wrapper: ```typescript Fragment(h1('Title'), p('Content')) // → <h1>Title</h1><p>Content</p> ``` #### `doctype()` Returns HTML5 doctype: ```typescript doctype() // → <!doctype html> ``` #### `raw(html)` Marks HTML as safe (no escaping): ```typescript raw('<strong>Bold</strong>') // → <strong>Bold</strong> ``` #### `safeText(text)` Explicitly escapes text: ```typescript safeText('<script>') // → &lt;script&gt; ``` #### `customElement(tagName)` Creates a custom element function: ```typescript const myEl = customElement('my-element'); myEl({ prop: 'value' }, 'content') // → <my-element prop="value">content</my-element> ``` ### Types ```typescript // Main function type type HtmlFunction = (...args: [GlobalAttrs, ...Content[]] | Content[]) => string; // Global attributes type GlobalAttrs = { id?: string; class?: ClassValue; style?: StyleValue; role?: string; tabindex?: number; title?: string; [k: `data-${string}`]: unknown; [k: `aria-${string}`]: unknown; } & Record<string, unknown>; // Class values type ClassValue = string | string[] | Record<string, boolean>; // Style values type StyleValue = string | Record<string, string | number>; // Content types type Content = string | number | boolean | null | undefined | SafeHtml | Content[]; ``` ## Browser Support - **Node.js**: >= 18.0.0 - **TypeScript**: >= 4.9.0 - **Runtimes**: Node.js, Cloudflare Workers, Deno, Bun - **Bundlers**: Vite, esbuild, Rollup, Webpack, Parcel ## Contributing 1. Fork the repository 2. Create your feature branch: `git checkout -b feature/amazing-feature` 3. Run tests: `npm test` 4. Commit your changes: `git commit -m 'Add amazing feature'` 5. Push to the branch: `git push origin feature/amazing-feature` 6. Open a Pull Request ## Development ```bash # Install dependencies npm install # Run tests npm test # Build npm run build # Type checking npm run typecheck # Linting npm run lint ``` ## License MIT © [Terence Stupple](https://github.com/telstupps) ## Related Projects - [Hono](https://hono.dev/) - Web framework for edge runtimes - [HTMX](https://htmx.org/) - HTML over the wire - [Cloudflare Workers](https://workers.cloudflare.com/) - Edge computing platform