uhtml

repository·main·Indexed 22 days ago

https://github.com/webreflection/uhtml

A minimalistic, high-performance library for creating fast and reactive web pages. uhtml provides an HTML/SVG parser that converts template strings into a lightweight JavaScript literal tree, utilizing a 'holes' mechanism for efficient DOM updates. It includes a reactive state management API with signals, computed values, and effects powered by @webreflection/alien-signals, as well as an ISH DOM facade for manipulating virtual DOM nodes.

Tokens
6.3K
Snippets
30
Records
34
Agent score
77%

What's inside uhtml

  1. How template holes and updates work

    main

    In uhtml, template holes are not just text interpolations; they are treated as specific nodes in the tree that can be updated.

    When the parser encounters a hole (represented internally by a NUL character), it uses the update function to create a record. Depending on the context, holes can represent:

    • TEXT: Interpolation within text content.
    • ATTRIBUTE: Interpolation within an attribute (e.g., <div class="${val}">). If the attribute name ends with a NUL character, it is treated as a dynamic attribute update.
    • COMMENT: A placeholder comment node used to track the position of the hole.
    • COMPONENT: A placeholder for a custom component.

    The update function provides the path (an array of indices) to the specific location in the tree where the hole resides, allowing efficient targeted updates later.

  2. Initialize the uhtml parser with custom DOM implementations

    main

    The default export of the parser module is a factory function that allows you to inject custom implementations for standard DOM nodes and components. This is useful when working in environments where the standard DOM is not available or when using a custom virtual DOM/ISH implementation.

    You can provide custom constructors for Comment, DocumentType, Text, Fragment, Element, and Component, as well as a custom update function to control how nodes are patched during template updates.

    import parserFactory from 'uhtml/parser';
    
    // Example of initializing the parser with custom implementations
    const parser = parserFactory({
        Element: MyCustomElement,
        Component: MyCustomComponent,
        update: (node, type, path, name, hint) => {
            // Custom update logic
        }
    });
    
    // The returned function parses templates into [Node, holes]
    const [node, holes] = parser(templateStrings, holesArray, true);
  3. Manage reactive state with signals

    main

    uhtml provides a reactive state management API through signals. It exports core primitives for creating reactive values, deriving state, and handling side effects. These primitives are powered by @webreflection/alien-signals.

    • signal(initialValue): Creates a new reactive signal with an initial value.
    • computed(fn): Creates a derived signal that automatically updates when its dependencies change.
    • effect(fn): Runs a side effect that automatically tracks any signals read during its execution.
    • untracked(fn): Runs a function without tracking any signals read inside it.
    • batch(fn): Executes a function within a batch, preventing multiple intermediate updates and ensuring all signal changes are processed together at the end of the batch.
    import { signal, computed, effect, batch } from './signals.js';
    
    const count = signal(0);
    const double = computed(() => count.value * 2);
    
    effect(() => {
      console.log(`Count is: ${count.value}, Double is: ${double.value}`);
    });
    
    batch(() => {
      count.value = 1;
      count.value = 2;
    });
  4. Render DOM nodes with dom()

    main

    The dom() function is the primary entrypoint for converting a Hole instance into a real DOM Node. It handles the initial rendering or the efficient updating of an existing node based on the changes within the Hole.

    import { dom } from './rabbit.js';
    
    // Assuming 'hole' is an instance of Hole
    const node = dom(hole);
  5. Use Hole and unsafe for advanced template manipulation

    main

    The Hole abstraction and unsafe utility are exported for advanced usage. Hole represents a reactive placeholder within a template that can be updated. unsafe allows for bypassing certain parsing constraints when necessary.

    import { Hole, unsafe } from 'uhtml';
    
    // Note: Specific usage patterns for Hole and unsafe 
    // depend on the internal rabbit.js and utils.js implementations.
  6. Parse a template string into a node tree and updates

    main

    Use the parser function to transform a template (either a TemplateStringsArray from a tagged template literal or an array of strings) into a traversable JS literal tree and a list of update instructions for the holes.

    Parameters:

    • template: A TemplateStringsArray or string[] representing the HTML structure. Holes are represented by the NUL character (\x00) in the internal processing.
    • holes: An array of values to be interpolated into the template holes.
    • xml: A boolean indicating if the parser should operate in XML mode (important for SVG and self-closing tags).

    Returns: An array containing [Node, unknown[]]:

    1. Node: The root Fragment containing the parsed tree.
    2. unknown[]: A list of update records generated by the update function for every hole found in the template.
    import createParser from './src/parser/index.js';
    const parser = createParser();
    
    // Using a tagged template literal style (simulated via array)
    const template = ["<div>Hello, ", "!</div>"];
    const holes = ["World"];
    
    const [rootNode, updates] = parser(template, holes, false);
    
    console.log(rootNode);
    console.log(updates); // Contains metadata for the "World" hole
  7. Create SVG templates with svg()

    main

    The svg() function is used to create SVG elements using tagged template literals. It behaves similarly to html() but is configured to parse the template as XML/SVG, ensuring correct handling of SVG namespaces and syntax.

    import { svg } from 'uhtml';
    
    const icon = svg`<svg viewBox="0 0 100 100">
      <circle cx="50" cy="50" r="40" fill="red" />
    </svg>`;
  8. Use the parser function to transform templates into nodes

    main

    The factory function returns a parser function with the following signature:

    (template: TemplateStringsArray | string[], holes: unknown[], xml: boolean) => [Node, unknown[]]

    • template: An array of strings (from a tagged template literal) or a standard string[].
    • holes: An array of values to be interpolated into the template.
    • xml: A boolean flag indicating if the template should be parsed as XML.

    Returns a tuple containing the root Node and the array of holes (the interpolated values).

    // Assuming 'parser' was created via the factory function
    const template = [`<div>Hello, ${'World'}</div>` as const];
    const holes = ['World'];
    const [node, parsedHoles] = parser(template, holes, false);
  9. Initialize the HTML/SVG parser

    main

    The parser is created by calling the default export of src/parser/index.js with an optional configuration object. This factory function returns the actual parsing function. You can provide custom implementations for DOM node types and an update function to customize how template holes (interpolations) are processed.

    Available configuration options:

    • Comment: Custom Comment class (defaults to DOMComment).
    • DocumentType: Custom DocumentType class (defaults to DOMDocumentType).
    • Text: Custom Text class (defaults to DOMText).
    • Fragment: Custom Fragment class (defaults to DOMFragment).
    • Element: Custom Element class (defaults to DOMElement).
    • Component: Custom Component class (defaults to DOMComponent).
    • update: A function used to generate update metadata for template holes. It receives (node, type, path, name, hint) and should return an unknown value (typically an array describing the update).
    import createParser from './src/parser/index.js';
    
    const parser = createParser({
      // Optional: override default DOM implementations
      Element: MyCustomElement,
      update: (node, type, path, name, hint) => [
        { type, path, name, hint }
      ]
    });
  10. Create HTML comments

    main

    The createComment function is a wrapper around the native document.createComment() API. It is used to create HTML comment nodes containing the provided value.

    Example usage:

    import { createComment } from './src/utils.js';
    
    const comment = createComment(' end of section ');
    document.body.appendChild(comment);
    // Result: <!-- end of section -->
  11. Manipulate nodes with append() and prop()

    main

    To build a node tree manually, use append(node, child) to add a child to a node's children array and set the child's parent reference. Use prop(node, name, value) to set attributes on a node's props object.

    import { Element, Text, append, prop } from './src/dom/ish.js';
    
    const div = new Element('div');
    const span = new Element('span');
    const text = new Text('Hello');
    
    prop(span, 'class', 'highlight');
    append(span, text);
    append(div, span);
    
    console.log(div.toString()); // <div><span class="highlight">Hello</span></div>
  12. Use batching to optimize reactive updates

    main

    The batch function allows you to group multiple signal updates together. This prevents the system from triggering effects or re-computations for every single intermediate change, which is essential for performance when updating multiple related signals at once.

    import { signal, batch } from './signals.js';
    
    const x = signal(0);
    const y = signal(0);
    
    // Without batch, effects tracking x and y would run twice
    // With batch, the effect runs only once after both updates
    batch(() => {
      x.value = 10;
      y.value = 20;
    });