or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

advanced-usage.mdcore-htm.mdindex.mdpreact-integration.mdreact-integration.md
tile.json

advanced-usage.mddocs/

Advanced Usage

HTM provides advanced customization through custom hyperscript functions and integration with build tools.

Custom Hyperscript Functions

HTM's core strength is its ability to work with any hyperscript-compatible function.

HTML String Generator

import htm from "htm";

const htmlString = (tag, props, ...children) => {
  const attrs = props ? 
    Object.entries(props)
      .filter(([key, value]) => value !== false)
      .map(([key, value]) => value === true ? key : `${key}="${value}"`)
      .join(' ') : '';
  
  const attrsStr = attrs ? ` ${attrs}` : '';
  const content = children.join('');
  
  if (children.length === 0 && isSelfClosing(tag)) {
    return `<${tag}${attrsStr} />`;
  }
  
  return `<${tag}${attrsStr}>${content}</${tag}>`;
};

const isSelfClosing = (tag) => 
  ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 
   'link', 'meta', 'param', 'source', 'track', 'wbr'].includes(tag);

const html = htm.bind(htmlString);

const markup = html`
  <div class="container">
    <h1>Title</h1>
    <img src="photo.jpg" alt="Photo" />
  </div>
`;

console.log(markup);
// <div class="container"><h1>Title</h1><img src="photo.jpg" alt="Photo" /></div>

JSON Structure Generator

import htm from "htm";

const jsonBuilder = (tag, props, ...children) => ({
  type: 'element',
  tagName: tag,
  attributes: props || {},
  children: children.map(child => 
    typeof child === 'string' ? 
      { type: 'text', content: child } : 
      child
  )
});

const html = htm.bind(jsonBuilder);

const structure = html`
  <article class="post">
    <h1>Blog Title</h1>
    <p>Content here...</p>
  </article>
`;

console.log(JSON.stringify(structure, null, 2));

CSS-in-JS Integration

import htm from "htm";
import styled from "styled-components";

// Use HTM with styled-components
const styledHtml = htm.bind(styled);

const StyledButton = styledHtml`
  button\`
    background: \${primary};
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
    
    &:hover \{
      opacity: 0.8;
    \}
  \`
`;

Build-time Optimizations

Babel Plugin Integration

HTM can be compiled at build time using Babel plugins:

// babel.config.js
module.exports = {
  plugins: [
    ["babel-plugin-htm", {
      "pragma": "React.createElement",
      "pragmaFrag": "React.Fragment"
    }]
  ]
};

// Before transformation
const element = html`<div>Hello ${name}</div>`;

// After transformation  
const element = React.createElement("div", null, "Hello ", name);

Template Validation

// Validate template syntax at runtime
function validateHTMLTemplate(template, values = []) {
  try {
    const html = htm.bind((tag, props, ...children) => ({ tag, props, children }));
    const result = html(template, ...values);
    return { valid: true, result };
  } catch (error) {
    return { 
      valid: false, 
      error: error.message,
      suggestion: getSuggestion(error.message)
    };
  }
}

function getSuggestion(errorMessage) {
  if (errorMessage.includes('Unexpected')) {
    return 'Check for mismatched tags or invalid syntax';
  }
  if (errorMessage.includes('property')) {
    return 'Verify all interpolated values are properly closed';
  }
  return 'Review template syntax and ensure proper nesting';
}

// Usage
const result = validateHTMLTemplate`<div><span></div>`; // Mismatched tags
if (!result.valid) {
  console.error('Template error:', result.error);
  console.log('Suggestion:', result.suggestion);
}

Performance Optimization

Template Caching

HTM uses internal caching for performance. You can inspect and manage caches:

import htm from "htm";

// Access internal cache (for debugging/profiling)
const html = htm.bind(h);

// Templates are cached by their static strings
const template1 = html`<div>${value1}</div>`;
const template2 = html`<div>${value2}</div>`; // Same cache entry

// Different static parts create different cache entries  
const template3 = html`<span>${value1}</span>`; // New cache entry

Mini Version

Use the mini version for smaller bundle sizes:

// Regular version (~600 bytes)
import htm from "htm";

// Mini version (~400 bytes) 
import htm from "htm/mini";

// Same API, slightly different internal representation
const html = htm.bind(h);

TypeScript Integration

Advanced Type Safety

import htm from "htm";

// Define strict return types
interface VNode {
  tag: string | Function;
  props: Record<string, any> | null;
  children: any[];
}

// Type-safe hyperscript function
const h = (
  tag: string | Function, 
  props: Record<string, any> | null, 
  ...children: any[]
): VNode => ({
  tag, props, children
});

// Bind with proper typing
const html = htm.bind(h);

// TypeScript infers VNode | VNode[] return type
const element = html`<div>Content</div>`;

// Component with props validation
interface ButtonProps {
  variant: 'primary' | 'secondary';
  disabled?: boolean;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ variant, disabled, onClick, children }) => {
  return html`
    <button 
      class="btn btn-${variant}"
      disabled=${disabled}
      onClick=${onClick}
    >
      ${children}
    </button>
  `;
};

Custom Template Types

// Create typed template functions
type HTMLTemplate<T> = (strings: TemplateStringsArray, ...values: any[]) => T;

// Custom hyperscript with specific return type
interface CustomElement {
  type: string;
  attributes: Record<string, any>;
  content: string;
}

const customH = (tag: string, props: any, ...children: any[]): CustomElement => ({
  type: tag,
  attributes: props || {},
  content: children.join('')
});

const customHtml: HTMLTemplate<CustomElement> = htm.bind(customH);

// Type-safe usage
const element: CustomElement = customHtml`<div class="test">Content</div>`;

Types

// Advanced hyperscript types
type CustomHyperscript<T> = (
  type: any,
  props: Record<string, any> | null,
  ...children: any[]
) => T;

type CustomTemplate<T> = (
  strings: TemplateStringsArray,
  ...values: any[]
) => T | T[];

// Custom element structures
interface CustomVNode {
  type: string;
  attributes: Record<string, any>;
  content: string;
}

interface CustomElement {
  tag: string;
  props: Record<string, any>;
  children: any[];
}

// Validation result types
interface ValidationResult {
  valid: boolean;
  result?: any;
  error?: string;
  suggestion?: string;
}