Liquid Template Engine

repository·main·Indexed 11 days ago

https://github.com/shopify/liquid

A stateless, secure, and non-evaling template engine for Ruby designed to allow users to safely edit application appearance without executing arbitrary code on the server. Features include the Liquid::Template API, Liquid::Environment for scoped configurations, Liquid::Context for variable management, and Liquid::Drop for lazy-loaded data.

Tokens
6.9K
Snippets
28
Records
37
Agent score
95%

What's inside Liquid

  1. How Liquid Environments work

    main

    An Environment is a scoped container that encapsulates custom tags, filters, and configurations. Instead of using global overrides, which can cause conflicts, you should use Environments to isolate functionality for different contexts (e.g., one for user-facing templates and one for email templates).

    To use a custom environment, pass it as the environment: option to Liquid::Template.parse.

    user_environment = Liquid::Environment.build do |environment|
      environment.register_tag("renderobj", RenderObjTag)
    end
    
    Liquid::Template.parse(<<~LIQUID, environment: user_environment)
      {% renderobj src: "path/to/model.obj" %}
    LIQUID
  2. How Liquid Environments and Strainers work together

    main

    A Liquid::Environment manages the definitions of filters, while a Liquid::Strainer is the object actually used during the rendering process to execute those filters within a specific Liquid::Context.

    When you call create_strainer(context, filters), the environment either:

    1. Uses its primary strainer_template if no additional filters are provided.
    2. Creates and caches a new specialized class if specific additional filters are requested. This caching mechanism ensures that repeated requests for the same set of filters are performant.
    # Creating a strainer for a specific context
    strainer = env.create_strainer(my_context)
  3. Configure Liquid error modes

    main

    Liquid's parser is naturally lax. You can adjust the strictness of template interpretation using error_mode. This can be set globally on the default environment or specifically for a single template during parsing.

    Available modes:

    • :lax: The default mode; accepts almost anything.
    • :warn: Adds strict errors to template.errors but continues rendering.
    • :strict: Raises a SyntaxError when invalid syntax is used in some tags.
    • :strict2: Raises a SyntaxError when invalid syntax is used in all tags.
    # Set globally
    Liquid::Environment.default.error_mode = :strict2
    
    # Set per template
    Liquid::Template.parse(source, error_mode: :strict)
  4. Use Liquid Drops for lazy-loaded data

    main

    A Liquid::Drop is a class used to safely export data to Liquid templates. The primary use case for Drops is implementing lazy loading: you can make complex data available to template designers without actually loading it from your database or API until the specific property is accessed within the template.

    When a template accesses a property on a Drop, Liquid calls the corresponding method on the Drop instance. If the method is not explicitly defined, Liquid uses liquid_method_missing to handle the request, which can be configured to raise an error if strict variable mode is enabled.

    class ProductDrop < Liquid::Drop
      def top_sales
        # This expensive query only runs if the template actually calls 'product.top_sales'
        Shop.current.products.find(:all, :order => 'sales', :limit => 10)
      end
    end
    
    # Usage in rendering
    tmpl = Liquid::Template.parse('{% for product in product.top_sales %} {{ product.name }} {% endfor %}')
    tmpl.render('product' => ProductDrop.new)
  5. Parse and render Liquid templates

    main

    Interpreting Liquid templates is a two-step process: first, you compile the source code into a Template object, and then you render it. Compiling performs error checking and may raise SyntaxErrors. Once compiled, a Template can be reused and cached for multiple renders.

    To enable profiling, pass profile: true as an option during parsing. Profiling information is then accessible via the Template#profiler method.

    template = Liquid::Template.parse(source)
    template.render('user_name' => 'bob')
  6. Initialize and render a Liquid template

    main

    To use Liquid, you primarily interact with the Liquid::Template class. You can parse a string containing Liquid syntax and then render it by providing a Liquid::Context containing the variables used in the template.

    Note: While this file is the main entrypoint that requires all necessary components, the high-level API for end-users is centered around Liquid::Template.parse and Liquid::Context.

    # Example of the standard Liquid workflow
    template = Liquid::Template.parse("Hello {{ name }}!")
    context = Liquid::Context.new("name" => "World")
    puts template.render(context)
    # => "Hello World!"
  7. Enable usage tracking

    main
    Liquid provides an opt-in mechanism for usage tracking to help developers monitor feature usage in production. You can implement this by customizing the Liquid::Usage.increment method (e.g., by integrating it with StatsD).
  8. Handle undefined variables and filters

    main

    By default, Liquid renders undefined variables or filters as nil without notification. To capture these issues, use the strict_variables and strict_filters options within the render method. Errors will be collected in the template.errors array.

    If you prefer the application to raise an exception immediately upon encountering an undefined variable or filter, use the render! method instead of render.

    # Collecting errors in the errors array
    template = Liquid::Template.parse("{{x}} {{y}} {{z.a}}")
    template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
    #=> '1  2 '
    template.errors
    #=> [#<Liquid::UndefinedVariable: ...>, ...]
    
    # Raising an exception immediately
    template.render!({ 'x' => 1}, { strict_variables: true })
    #=> raises Liquid::UndefinedVariable
  9. Render templates with Liquid::Template

    main

    The core API revolves around the Liquid::Template class. You first parse and compile a template string, then render it by passing a hash of local variables.

    @template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
    @template.render('name' => 'tobi')                # => "hi tobi"
  10. Configure the default error mode

    main

    The error_mode attribute on the Liquid::Environment determines how the engine handles errors during template rendering. This setting applies to all templates rendered by that environment unless overridden on a per-template basis.

    Supported error modes include:

    • :lax (default)
    • :warn
    • :strict
    • :strict2
    env = Liquid::Environment.build(error_mode: :strict)
  11. Pass variables and attributes to an include tag

    main

    When using {% include %}, you can pass specific variables or iterate over a collection using with or for syntax, and assign an alias using as.

    Syntax Patterns

    Basic include: {% include 'filename' %}

    Include with a variable: {% include 'filename' with variable_name %}

    Include with a loop: {% include 'filename' for collection %}

    Include with an alias: {% include 'filename' as alias_name %}

    Include with attributes (key-value pairs): {% include 'filename', key1: value1, key2: value2 %}

    {% include 'filename', key1: value1, key2: value2 %}