gomponents

repository·main·Indexed 23 days ago

https://github.com/maragudk/gomponents

A Go library for building HTML 5 components using a type-safe, declarative DSL. It allows developers to create HTML structures using pure Go functions, avoiding external template languages and build steps. The library provides a core Node interface, specialized packages for standard HTML elements, reusable components, and HTTP utilities, and includes built-in support for void elements and automatic HTML escaping for attribute values and text nodes.

Tokens
3.9K
Snippets
8
Records
28
Agent score
72%

What's inside gomponents

  1. Overview of gomponents

    main

    gomponents is a library for generating HTML in Go using a type-safe, programmatic approach instead of traditional template languages. It is designed for scenarios requiring high performance and Go's native type safety, such as:

    • Server-side rendered (SSR) web applications
    • API servers returning HTML
    • Static site generators
    • Email template generation
    • Any programmatic HTML generation needs
  2. How gomponents works: Core Concepts

    main

    gomponents allows you to build HTML components using pure Go functions. Instead of using template files, you write Go code that produces type-safe HTML5 output.

    The Node Interface

    The fundamental building block is the Node interface. Everything in the library—elements, attributes, text, and components—implements this interface:

    type Node interface {
        Render(w io.Writer) error
    }

    Node Types

    • ElementType: Represents regular HTML elements (like div, span) and text nodes.
    • AttributeType: Represents HTML attributes (like class, href).

    The library automatically manages the correct placement of these types during the rendering process.

  3. Understand the gomponents package structure

    main

    The library is organized into several specialized packages:

    • gomponents: The core package containing fundamental interfaces and functions like Node, El, Attr, and helpers like Map, Group, If, Text, and Raw.
    • gomponents/html: Contains standard HTML elements and attributes.
    • gomponents/components: Provides higher-level, reusable components and utilities.
    • gomponents/http: Contains utilities specifically for use with web servers.
    • gomponents/x/...: Experimental packages. Warning: These do not have the same compatibility guarantees as the core library and may introduce breaking changes.
  4. How void elements are handled

    main
    In HTML, void elements (such as <br>, <img>, or <input>) do not have closing tags. gomponents handles these automatically by checking an internal list of void elements during the rendering process. If you attempt to provide child nodes to a void element, any nodes that are not attributes will be automatically ignored to ensure the resulting HTML is valid.
  5. Understand the name-escaping contract in gomponents

    main

    In gomponents, there is a deliberate design decision regarding how HTML elements and attributes are rendered:

    • Attribute Values: These are automatically escaped using template.HTMLEscapeString to prevent injection.
    • Element and Attribute Names: These are rendered verbatim (unescaped).

    Security Warning: Because names are rendered unescaped, they are treated as trusted structural input. Never use user-controlled data as an element name or an attribute name, as this allows an attacker to inject arbitrary attributes or whitespace into the HTML structure.

    This contract applies to the following core functions and helpers:

    • El (element creation)
    • Attr (attribute creation)
    • html.Aria (ARIA attribute helper)
    • html.Data (Data attribute helper)
    • components.JoinAttrs (Attribute joining helper)
  6. Performance Optimization: Reducing Allocations in Attr calls

    main

    A significant performance optimization in gomponents involves how attributes are handled to reduce heap allocations.

    Previously, the Attr(name string, value ...string) function captured the entire variadic []string slice into its closure. Because the slice was captured, it escaped to the heap.

    To optimize this, the library now internally dispatches to specialized functions (booleanAttr or valueAttr) that extract the scalar string values from the variadic slice before creating the closure. This allows the Go compiler's escape analysis to see that the slice does not escape, significantly reducing the number of allocations per element.

    Impact: This change reduces allocations by approximately 23-25% and improves ns/op by ~7-8% in realistic workloads without changing the public API.

  7. Integrate gomponents with net/http

    main

    You can integrate gomponents into your web server by using the Adapt() function from the maragu.dev/gomponents/http package. This converts a function that returns (Node, error) into a standard http.HandlerFunc.

    import (
        "net/http"
        
        . "maragu.dev/gomponents"
        . "maragu.dev/gomponents/html"
        ghttp "maragu.dev/gomponents/http"
    )
    
    func HomeHandler(w http.ResponseWriter, r *http.Request) (Node, error) {
        return Div(Text("Welcome!")), nil
    }
    
    // In main:
    http.HandleFunc("/", ghttp.Adapt(HomeHandler))
  8. Optimize performance via realistic benchmarking

    main

    When evaluating the performance of gomponents, it is important to distinguish between synthetic benchmarks and realistic workloads:

    1. Synthetic Benchmarks: (e.g., 10K identical elements) These are often dominated by GC pressure due to massive allocation volumes. They are useful for finding large-scale allocation wins.
    2. Realistic Benchmarks: (e.g., a full dashboard page with navigation, sidebars, data tables, and conditional rendering) These better represent actual usage patterns where the library builds a fresh tree per HTTP request. These benchmarks show how the library performs when dealing with diverse elements, mixed attributes, text nodes, and mapped collections.

    For most users, the construct_and_render pattern (building the tree from scratch and then rendering it) is the most relevant metric, as it reflects the typical lifecycle of a component in a web server context.

  9. Import patterns for gomponents

    main

    Depending on your preference for code readability versus standard Go idioms, you can use two different import patterns.

    Dot imports are recommended because they allow the Go code to read like an HTML DSL (Domain Specific Language), making the component structure highly readable.

    import (
        . "maragu.dev/gomponents"
        . "maragu.dev/gomponents/html"
        . "maragu.dev/gomponents/components"
    )

    Standard imports with aliases (Alternative)

    If you prefer to avoid dot imports, use single-letter aliases to keep the code concise.

    import (
        g "maragu.dev/gomponents"
        h "maragu.dev/gomponents/html"
        c "maragu.dev/gomponents/components"
        ghttp "maragu.dev/gomponents/http"
    )
  10. How Group works as a collection of Nodes

    main

    A Group is a slice of Nodes that can be treated as a single Node.

    Behavioral Nuance: When a Group is rendered as a child of an element (via El), it only renders nodes of type ElementType. If a Group contains AttributeType nodes, those attributes will be ignored during the rendering of the group as children to prevent invalid HTML. This makes Group ideal for collecting both attributes and children in a single slice, which you can then pass to El.