Vento Template Engine

repository·main·Indexed 19 days ago

https://github.com/ventojs/vento

A minimal, ergonomic, and high-performance template engine compatible with Deno, Node, Bun, and browsers. Vento features a plugin-based architecture, built-in async support, and a pipeline operator for filters. It allows real JavaScript within templates using {{ }} tags and provides flexible configuration for data variable naming, HTML autoescaping, and strict mode debugging.

Tokens
10.8K
Snippets
65
Records
77
Agent score
64%

What's inside Vento

  1. Overview of Vento template engine

    main

    Vento is a minimal, ergonomic template engine inspired by Nunjucks, Liquid, Mustache, and EJS. It is designed to be fast, dependency-free, and compatible with various JavaScript runtimes including Deno, Node, Bun, and browsers.

    Key characteristics include:

    • Ergonomic Syntax: Uses {{ and }} for all tags and outputs.
    • JavaScript Integration: You can write real JavaScript anywhere within templates (e.g., {{ await user.getName() }}).
    • Async Support: Built-in support for asynchronous operations without requiring special tags.
    • Pipeline Operators: Uses the |> operator for filters, inspired by the F# pipeline operator proposal.
    • Plugin-based Architecture: Most features and tags are implemented as plugins, allowing for a flexible and extensible system.
  2. Define reusable content with Vento functions

    main

    Vento functions allow you to define reusable chunks of content within a template. You define a function using the {{ function name }} tag and close it with {{ /function }}. You can call the function using standard JavaScript-like syntax {{ name() }}.

    Functions support arguments with default values, similar to JavaScript. Additionally, functions have access to the scoped variables of the template where they are defined, even if those variables are not explicitly passed as arguments.

    {{ function hello(name = "world") }}
      Hello, {{ name }}!
    {{ /function }}
    
    {{ hello() }}
    {{ hello("Vento") }}
  3. Avoid using double underscore `__` prefixes for variables

    main
    Vento uses internal variables prefixed with a double underscore (e.g., __env, __exports, or __pos) during template compilation. To avoid naming conflicts with these system-generated variables, you should avoid creating your own variables that start with __.
  4. How data inheritance works in Vento

    main

    Vento implements a parent-to-child data inheritance system. When a template uses include or layout to nest another template, the nested (child) template automatically inherits access to all variables available in the parent template's scope.

    Key Rules:

    • One-way flow: Data flows from parent to child. A variable declared in a child template is not accessible by its parent.
    • Deep propagation: Inheritance is recursive. If a child template includes a grandchild template, the grandchild also has access to the original parent's variables.
    {{-- Parent template --}}
    {{ set salute = "Hello world" }}
    {{ include "child.vto" }}
    
    {{-- child.vto --}}
    {{ include "nested-child.vto" }}
    
    {{-- nested-child.vto --}}
    {{ salute }}
  5. How slots work in layouts

    main

    The {{ slot name }} tag allows you to split the content provided to a layout into multiple named variables. This is more ergonomic than passing all variables via a data object.

    Key behaviors:

    • Named Slots: Content inside {{ slot name }}...{{ /slot }} is assigned to the variable name in the layout.
    • Concatenation: If you use multiple slots with the same name, their content is concatenated together.
    • Unslotted Content: Any content not wrapped in a {{ slot }} tag is automatically assigned to the content variable. You can also explicitly use {{ slot content }} to target this section for transformations.
    • Pipes on Slots: You can apply pipes to specific slots to transform only that portion of the content.
    {{/* Defining slots */}}
    {{ layout "section.vto" }}
      {{ slot header |> toUpperCase }}
        <h1>Section title</h1>
      {{ /slot }}
      <p>Content of the section</p>
    {{ /layout }}
  6. Automatic type casting in `for` loops

    main

    Vento automatically casts non-iterable types to iterables to prevent runtime errors:

    • Numbers: An integer is converted into an array containing a sequence from 1 up to that number (e.g., 10 becomes [1, 2, ..., 10]).
    • Strings: A string is converted into an array of its individual characters.
    • null and undefined: These values are converted into an empty array [], ensuring the loop simply does nothing instead of crashing.
    {{/* Numbers to array */}}
    {{ for count of 10 }}
      {{ count }}
    {{ /for }}
    
    {{/* Strings to character array */}}
    {{ for letter of "abcd" }}
      {{ letter }}
    {{ /for }}
    
    {{/* null/undefined to empty array */}}
    {{ for item of undefined }}
      {{ item }}
    {{ /for }}
  7. Compare `set` with JavaScript variable declaration

    main

    While you can create variables using standard JavaScript syntax (e.g., {{> const name = "Óscar" }}), the set tag offers specific advantages:

    1. Global Scope: Variables created with set are global and accessible in included files. JavaScript-declared variables are not necessarily shared this way.
    2. Pipes: You can use Vento pipes directly with set (e.g., {{ set x = y |> pipe }}).
    3. Re-assignment Safety: Using set allows you to re-assign or re-initialize a variable without error. In contrast, using JavaScript const to declare the same variable name twice in the same scope will cause a compilation error.

    Example of re-assignment with set (Works):

    {{ set name = "Óscar" }}
    {{ set name = "Laura" }}

    Example of re-declaration with const (Fails):

    {{> const name = "Óscar" }}
    {{> const name = "Laura" }}
    {{# This works fine with set #}}
    {{ set name = "Óscar" }}
    {{ set name = "Laura" }}
    
    {{# This will break because of double initialization #}}
    {{> const name = "Óscar" }}
    {{> const name = "Laura" }}
  8. How pipes work in Vento

    main

    Pipes allow you to chain functions to transform values using the |> operator. This syntax is inspired by the F# pipeline operator. Pipes can be used for printing variables, saving variables, or transforming data before iterating over collections.

    When a pipe is used, Vento attempts to execute functions in the following order of priority:

    1. Filters: Custom configuration functions.
    2. Global functions: Functions available in the standard JavaScript namespace.
    3. Prototype functions: Methods available on the variable's prototype (as a fallback).

    If a filter or function requires additional arguments, they are passed within parentheses following the function name.

    {{ value |> transform }}
  9. Understand Vento's template syntax

    main
    Vento uses a hybrid syntax that combines traditional template engine tags (for common tasks like loops, conditions, and partial includes) with the ability to execute real JavaScript code at runtime. This design allows you to leverage existing JavaScript knowledge instead of learning a complex, proprietary language, making the engine highly flexible for complex logic within templates.
  10. Understand Vento's core design principles

    main

    Vento is a JavaScript-based template engine designed to reduce the cognitive load of learning engine-specific APIs by leveraging standard JavaScript logic.

    Key characteristics include:

    • JavaScript-First Logic: Instead of learning engine-specific filters or flow control, you use standard JavaScript methods (e.g., .toUpperCase()) and syntax.
    • Async-Friendly: Templates are compiled into JavaScript functions that support await natively, allowing for seamless asynchronous data fetching within templates.
    • Unified Delimiters: Vento uses {{ ... }} for both control flow tags (like if or for) and variable interpolation, reducing syntax complexity.
    • Compact Closing Tags: Closing tags use a slash prefix (e.g., {{ /if }}) which is more concise than Nunjucks/Liquid ({% endif %}) and more readable than EJS (%>).
    • Pipeline Filters: Filters are applied using the pipeline operator (|>).