Better White Space Control in Twig Templates

Whitespace control in Twig templates allows you to control the indentation and spacing of the generated contents (usually HTML code). Controlling white spaces is very important when using Twig to generate contents like YAML, text emails or any other format where the white spaces are significant.

In contrast, when generating HTML contents, most of the times you should ignore this feature, because the HTML contents are minified and compressed before sending them to the users, so trying to generate perfectly aligned HTML code is just a waste of time.

However, there are some specific cases where whitespace can change how things are displayed. For example, when an <a> element contains white spaces after the link text and the link displays an underline, the whitespace is visible. That's why Twig provides multiple ways of controlling white spaces. In recent Twig versions, we've improved those features.

New whitespace trimming options

Contributed by
Fabien Potencier
in #2925.

Consider the following Twig snippet:

1
2
3
4
5
6
7
<ul>
    <li>
        {% if some_expression %}
            {{ some_variable }}
        {% endif %}
    </li>
</ul>

If the value of some_variable is 'Lorem Ipsum', the HTML generated when the if expression matches, would be the following:

1
2
3
4
5
<ul>
    <li>
            Lorem Ipsum
        </li>
</ul>

Twig only removes by default the first \n character after each Twig tag (the \n after the if and endif tags in the previous example). If you want to generate HTML code with better indention, you can use the - character, which removes all white spaces (including newlines) from the left or right of the tag:

1
2
3
4
5
6
7
<ul>
    <li>
        {%- if some_expression %}
            {{- some_variable -}}
        {% endif -%}
    </li>
</ul>

The output is now:

1
2
3
<ul>
    <li>Lorem Ipsum</li>
</ul>

Starting from Twig 1.39 and 2.8.0, you have another option to control whitespace: the ~ character (which can be applied to {{, {% and {#). It's similar to -, with the only difference that ~ doesn't remove newlines:

1
2
3
4
5
6
7
<ul>
    <li>
        {%~ if some_expression %}
            {{ some_variable -}}
        {% endif ~%}
    </li>
</ul>

The output now contains the newlines after/before the <li> tags, so the generated HTML is more similar to the original Twig code you wrote:

1
2
3
4
5
<ul>
    <li>
        Lorem Ipsum
    </li>
</ul>

Added a spaceless filter

Contributed by
Fabien Potencier
in #2872.

In previous Twig versions, there was a tag called {% spaceless %} which transformed the given string content to remove the white spaces between HTML tags. However, in Twig, transforming some contents before displaying them is something done by filters.

That's why, starting from Twig 1.38 and 2.7.3, the spaceless tag has been deprecated in favor of the spaceless filter, which works exactly the same:

1
{{ some_variable_with_HTML_content|spaceless }}

However, this is commonly used with the alternative way of applying some filter to some HTML contents:

1
2
3
4
5
-{% spaceless %}
+{% apply spaceless %}
    {# some HTML content here #}
-{% endspaceless %}
+{% endapply %}

In case you missed it, the apply tag was recently added to replace the previous filter tag.

In any case, even after these changes, it's still recommend to not use the spaceless filter too much. The removal of white spaces with this filter happens at runtime, so calling it repeatedly can hurt performance.

Fine-grained escaping on ternary expressions

Contributed by
Fabien Potencier
in #2934.

This new feature introduced in Twig 1.39 and 2.8 is not related to whitespace control, but it's an important new feature to consider in your templates. Consider the following example and the results rendered in Twig versions before 1.39 and 2.8:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{% set foo = '<strong>foo</strong>' %}
{% set bar = '<strong>bar</strong>' %}

{{ false ? '<strong>bar</strong>' : foo|raw }}
{# renders as '<strong>foo</strong>' #}

{{ false ? bar : foo|raw }}
{# renders as '&lt;strong&gt;foo&lt;/strong&gt;' #}

{{ (false ? bar : foo)|raw }}
{# renders as '<strong>foo</strong>' #}

The reason why this example worked in that way in previous Twig versions is that in the first ternary statement, foo is marked as being safe and Twig does not escape static values. In the second ternary statement, even if foo is marked as safe, bar remains unsafe and so is the whole expression. The third ternary statement is marked as safe and the result is not escaped.

This behavior was confusing to lots of designers and developers. That's why, starting from Twig 1.39 and 2.8, the result of this example has changed as follows:

1
2
3
4
5
6
7
{% set foo = '<strong>foo</strong>' %}
{% set bar = '<strong>bar</strong>' %}

{{ false ? '<strong>bar</strong>' : foo|raw }}
{{ false ? bar : foo|raw }}
{{ (false ? bar : foo)|raw }}
{# renders as '<strong>foo</strong>' in all cases #}

Before, the escaping strategy was the same for both sides of the ternary operator. Now, in Twig 1.39 and 2.8, Twig applies a special code path for ternary operator that is able to have different escaping strategies for the two sides.

Comments

Does that mean, that the priority of ternaries has been raised or the priority of the filter has been lowered?
It seems there is a typo in {{ some_variable -}}, it should be {{ some_variable ~}}.
@Alan it may look like a typo ... but I think it's correct. the only way to get the shown output is to use "-" instead of "~" in that case. I tested it with https://twigfiddle.com/ but there could be alternative solutions.
@Christian No, the priorities did not change, I've made the escaping algorithm aware of the ternary operator so now, it is able to have a different strategies for both side of the operator.

Comments are closed.

To ensure that comments stay relevant, they are closed for old posts.