Jet Template Engine

repository·master·Indexed 23 days ago

https://github.com/cloudykit/jet

A fast, lightweight, and secure template engine for the Go programming language. Jet features template inheritance via extends and composition via block/yield, import, and include. It provides a C-like expression syntax, automatic HTML escaping, and support for embedding templates into Go binaries using vfsgen. The engine includes built-in functions for debugging (dump), range generation (ints), and JSON rendering, as well as a customizable Cache interface for template storage.

Tokens
9.9K
Snippets
16
Records
60
Agent score
80%

What's inside Jet

  1. Overview of Jet Template Engine

    master

    Jet is a high-performance template engine for Go designed to be easy to use, powerful, and secure. It is optimized for low memory footprint and fast execution, often outperforming pre-compiled engines.

    Key features include:

    • Simple Syntax: Familiar C-like expressions.
    • Template Logic: Supports inheritance via extends and composition via block/yield, import, and include.
    • Security: Automatic HTML escaping.
    • Developer Experience: Descriptive error messages that include filenames and line numbers.
  2. Perform arithmetic and logical operations

    master

    Jet supports standard arithmetic and logical operators:

    Arithmetic: +, -, *, /, % Logical: && (and), || (or), ! (not), == (equal), != (not equal), >, >=, <, <= Ternary: x ? y : z (evaluates to y if x is truthy, else z)

  3. Use delimiters and whitespace trimming in Jet templates

    master

    Jet uses {{ and }} as default delimiters for template expressions. You can configure alternative delimiters like [[ and ]].

    To control whitespace around delimiters, use the {{- and -}} syntax. This trims whitespace (spaces, tabs, carriage returns, and newlines) preceding and following the delimiter. Note that a space character must be adjacent to the dash.

    By default, all text outside delimiters is copied verbatim.

  4. Define and invoke reusable blocks with block and yield

    master

    Blocks are named pieces of a template that can be invoked.

    • Defining a block: Use {{ block name(args...) }} ... {{ end }}. Blocks can accept arguments with optional default values.
    • Invoking a block: Use {{ yield name(args...) }}. Parameters without defaults must be provided.
    • Context in yield: You can pass a specific context to a block using {{ yield name(args...) context }}. If no context is provided, the current context is used.
    • Content blocks: You can designate a placeholder for inner content within a block using {{ yield content }}. When invoking the block, use the content keyword at the end of the yield statement to wrap the provided content.
    {{ block inputField(type="text", label, id, value="", required=false) }}
        <div class="form-field">
            <label for="{{ id }}">{{ label }}</label>
            <input type="{{ type }}" value="{{ value }}" id="{{ id }}" {{ required ? "required" : "" }} />
        </div>
    {{ end }}
    
    {{ yield inputField(id="firstname", label="First name", required=true) }}
    
    {{ block link(target) }}
        <a href="{{ target }}">{{ yield content }}</a>
    {{ end }}
    
    {{ yield link(target="https://www.example.com") content }}
        Example Inc.
    {{ end }}
  5. Iterate over data with range

    master

    The range statement iterates over Go slices, arrays, maps, and channels.

    • Context (.): Inside a range block, the context is set to the current iteration's value.
    • Slices/Arrays: You can capture the index using range i := s or both index and value using range i, v := s. Using the two-variable syntax prevents the iteration value from becoming the context (.), keeping the parent context available.
    • Maps: You can capture the key using range k := m or both key and value using range k, v := m.
    • Channels: You can capture the value using range v := c. Note that using the two-variable syntax with channels is an error.
    • Empty Collections: You can provide an else block to range that executes if the collection is empty or the channel is closed.
    {{ range i, v := s }}
        {{i}}: {{v}}
    {{ end }}
    
    {{ range searchResults }}
        {{.}}
    {{ else }}
        No results found :(
    {{ end }}
  6. Add comments to Jet templates

    master

    Comments in Jet start with {* and end with *}. They are dropped during template parsing and can span multiple lines. Content inside comments, including other template tags, will not be executed.

    {* this is a comment *}
    
    {* 
        none of this will be executed: 
        {{ asd }}
        {{ include "./foo.jet" }}
    *}
  7. Handle errors with try and catch

    master

    Use try to attempt rendering a block of code without crashing the template engine if an error occurs.

    • Buffering: All output inside a try block is buffered. If an error occurs, no content from the try block is included in the final output.
    • Catching Errors: You can use a catch block to provide fallback content. You can assign the error to a variable (e.g., {{ catch err }}) to inspect it. Since the error is a Go error, use err.Error() to get the string message.
    • Limitations: Errors occurring inside a catch block are not caught and will abort execution. The error variable is only available within the catch block.
    {{ try }}
        {{ foo }}
    {{ catch err }}
        {{ log(err.Error()) }}
        uh oh, something went wrong: {{ err.Error() }}
    {{ end }}
  8. Access data using indexing and field notation

    master

    Jet supports several ways to access data from strings, slices, maps, and structs:

    • Indexing []: Use this for strings (returns ASCII value), slices/arrays, maps, or structs.
    • Field Access .: Use dot notation for maps and structs. If the identifier before the dot is omitted (e.g., {{ .field }}), Jet looks up the field in the current context.
    • Slicing [start:end]: Use Go-like syntax to re-slice arrays or slices. The start index is inclusive, and the end index is exclusive.
  9. Use SafeWriter to bypass default HTML escaping

    master

    Jet uses a SafeWriter function type to write directly to the render output stream. This allows you to bypass Jet's default HTML escaping when you want to output pre-escaped or raw content.

    Available built-in SafeWriter functions:

    • safeHtml: An alias for template.HTMLEscape. Escapes everything that could be interpreted as HTML.
    • safeJs: An alias for template.JSEscape. Escapes data to be safe for use in a JavaScript context.
    • raw (or unsafe): A writer that performs no escaping at all. Use with extreme caution to avoid XSS vulnerabilities.
  10. How asset packaging works with go:generate

    master

    The asset packaging workflow relies on Go's go generate tool and build tags:

    1. Trigger: When you run go generate, the tool scans your source files for //go:generate annotations.
    2. Generation: The command go run assets/generate.go is executed, which uses vfsgen to scan your template directories and generate a Go file (e.g., templates_vfsdata.go).
    3. Embedding: By using specific build tags (e.g., // +build deploy_build), the generated file containing the binary data is only included in the binary during specific build processes, preventing development files from bloating your standard builds.
    4. Loading: At runtime, the Jet template engine is configured to use the http.FileSystem interface implemented by the generated code to locate and load templates from memory.
  11. Initialize and assign variables

    master

    Variables must be initialized before use using the := operator. Once initialized, they can be updated using the = operator. Variables in Jet are dynamically typed.

    To execute a function or expression without storing its result or rendering it to the output, assign the result to the underscore _ identifier.

    {{ foo := "bar" }}
    {{ foo = "asd" }}
    {{ foo = 4711 }}
    
    {{ _ := stillRuns() }}
    {{ _ = stillRuns() }}
  12. Package templates into a Go binary using vfsgen

    master

    To achieve a "Just One Binary" deployment where templates are embedded directly into your compiled Go application, you can use vfsgen. This allows you to deploy your web app by only copying the compiled binary, as the templates are included as binary data within it.

    Step-by-Step Implementation Guide

    1. Install vfsgen: Add github.com/shurcooL/vfsgen to your project (vendoring is recommended).
    2. Setup Generation Files: Create an assets/generate.go file to configure vfsgen and an assets/templates/templates.go file to hold the generated code.
    3. Add Generate Directive: In your main.go file, add the following directive above the package main declaration: //go:generate go run assets/generate.go
    4. Configure Jet Multi-Loader: Configure the Jet template engine using a multi-loader to include the http.FileSystem provided by the generated vfsgen data.
    5. Build: Run go generate (often via a make target) to execute vfsgen. This produces a templates_vfsdata.go file containing your view files as binary data.

    Extending to other assets

    To include other directories (like locale files or public folders), add the folder to the assets directory, update the vfsgen configuration in generate.go to fetch that directory tree, and ensure the generated files are included in your build.