
Test packages that have not been published to a registry, without getting caught in the pitfalls of npm and yarn's built-in solutions.
Henry Bley-Vroman
Posted
A version of this post appeared on viget.com
Twig and ERB are the two front-end templating languages I use most when developing websites. Here I document the ways each write just about everything to build views: comments, conditionals, variables and undefined variables, interpolation, loops and the loop index, slicing, handling whitespace, retrieving an keyed values, and templating with blocks and partials. If you're familiar with one of Twig or ERB, use this as a cross-reference to translate your knowledge of the one language into the other. If you haven't used either, this should get you up and running quickly. Read on to learn more about ERB and Twig, or skip ahead to the reference section.
Read more
This post is geared towards developers who want to translate their Twig knowledge to ERB, or vice versa. If you're only interested in one language or the other, check out Fundamental ERB for Front-End Development or Fundamental Twig for Front-End Development.
Twig is SensioLabs' Django- / Jinja-like templating language for PHP. The recommended extension for Twig files is .twig
, .<compiled_extension>.twig
is useful, and .html
—though inaccurate— is common in front-end templating. It's used by SensioLabs' Symfony; by Drupal 8, which is built on Symfony; and by Craft.
Twig is a great language for building web front ends: it is full-featured without having more than one person could hope to learn, it reads fairly closely to English, and it has great official documentation. Twig is especially notable for its powerful support for complex inheritance across templates. Check out the use
tag, the embed
tag, and the block()
function.
Twig even has Javascript implementations, making it easy to fit into projects built on the JS ecosystem. A quick overview to help you pick the one that best suits yours needs:
embed
inheritence). If you use Gulp in your build tools, you can use gulp-nunjucks.embed
tag) but it currently comes closer than Nunjucks does and, since its goal is to duplicate Twig, it likely always will. The Twig.js Gulp plugin is gulp-twig.To learn Twig, read through the official documentation, and try things out in twigfiddle.
ERB (Embedded Ruby) is a feature of Ruby that lets you —you guessed it!— embed Ruby in other files. ERB files have the extension .<compiled_extension>.erb
. It is the language HAML and Slim are shorthand for. ERB is commonly used for templating Views in Rails apps — at Viget we use it when building large sites with custom CMSes. (If that's something you do, check out Colonel Kurtz, the block editor we often use for the client-facing admin area of Rails app sites.)
Because it can do anything Ruby can do, it's extremely powerful, has a much steeper learning curve than Twig, and can do a lot that isn't relevant to front-end templating. There's no cannonical ERB-for-front-end-developers documentation, and the Rails official documentation is immense and hard to dig through. Some resources if for learning ERB:
link...
and button_to
methods are essential.content tag
and tag
methods are useful.link_to
ERB: <%# … %>
erb
<%# comment %>Copied
Twig: {# … #}
twig
{# comment #}Copied
ERB: =begin
…=end
the opening and closing tags must be at the start of the line
erb
<%=begin %>block comment(both lines of both the begin and end tags must be at the start of their lines)<%=end %>Copied
not
erb
<%=begin %>not a comment<%=end %>Copied
Twig: {# … #}
twig
{#block comment#}Copied
or
twig
not a comment {# blockcomment #} not a commentCopied
ERB: <%= … %>
erb
<%= "print this" %> <%# output: `"print this"` %><%= 1 + 2 %> <%# output: `3` %>Copied
Twig: {{ }}
twig
{{ "print this" }} {# output: `print this` #}{{ 1 + 2 }} {# output: `3` #}Copied
ERB: <% … %>
erb
<% if … do %> … <% end %>Copied
Twig: {% … %}
twig
{% if … %} … {% endif %}Copied
ERB: if
and unless
erb
<%= 2 if true %> <%# output: `2` %><%= 2 if false %> <%# output: `nil` %><%= 2 unless true %> <%# output: `nil` %><%= 2 unless false %> <%# output: `2` %>Copied
ERB: if
…elsif
…end
erb
<%# assuming x, y, z, and n are defined %><% if x %>y<% elsif z == n %> <%# note the spelling of elsif %>0<% else %>1<% end %>Copied
Twig: if
…elseif
…endif
twig
{% if x %}y{% elseif z == n %}{# note the spelling of elseif #}0{% else %}1{% endif %}Copied
Both ERB and Twig support "condition ?
iftrue :
iffalse", and "ifselftrue ?:
otherwise".
ERB. Note that the "then" case :
must be provided
erb
<%# assuming x, y, z, and n are defined %><%# if x then y %><%# omitting the "else" will throw an error #><%= x ? y : '' %><%# if x is true, y. otherwise, if z equals n then 0. otherwise 1 %><%= x ? y : z == n ? 0 : 1 %><%# ternary operator: x if x is true, otherwise y %><%= x ?: y %>Copied
Twig
twig
{# assuming x, y, z, and n are defined and/or Twig's strict variables option is turned off #}{# if x then y #}{{ x ? y }}{# if x is true, y. otherwise, if z equals n then 0. otherwise 1 #}{{ x ? y : z == n ? 0 : 1 }}{# ternary operator: x if x is true, otherwise y #}{{ x ?: y }}Copied
ERB: 0
is True
in Boolean contexts
erb
<%= false ? 'truthy' : 'falsy' %> <%# output: `"falsy"` %><%= 0 ? 'truthy' : 'falsy' %> <%# output: `"truthy"` %>Copied
Twig: as in PHP generally, 0
is False
in Boolean contexts
twig
{{ false ? 'truthy' : 'falsy' }} {# output: `falsy` #}{{ 0 ? 'truthy' : 'falsy' }} {# output: `falsy` #}Copied
ERB: =
erb
<% var = 1 %><% anotherVar = 0 %><% falseVar = false %><%= 2 if var %> <%# output: `2` %><%= 2 if anotherVar %> <%# output: `2` %><%= 2 if falseVar %> <%# output: `` %><%= 2 unless falseVar %> <%# output: `2` %>Copied
Twig: set
twig
{% set var = 1 %}{% set anotherVar = 0 %}{% set falseVar = false %}{{ var ? 2 }} {# output: `2` #}{{ anotherVar ? 2 }} {# output: null - Twig, unlike PHP, equates 0 with falsehood #}{{ falseVar ? '' : 2 }} {# output `2` #}Copied
Twig can define multiple variables in a single call — just keep in mind that developers not used to this might overlook the multiple declarations!
twig
{% set x, y, z = 1, 2, 3 %}Copied
(A value must be explicitly provided for each variable: {% set x, y = 1 %}
will error.)
ERB: multi-line blocks of markup can stored in an identifier with content_for x do
…end
erb
<% content_for longVar do %><div>…</div><% end %><%= content_for(longVar) %>Copied
Note: content_for
is additive: each time you provide content for a given variable, that content is appeneded to what was there already. To use content_for
to overwrite a global variable, use the flush: true
option:
erb
<% content_for refreshedVar do %>a<% end %><% content_for refreshedVar, flush: true do %>b<% end %>Copied
Twig: use the set
tag's form set x
…endset
to capture chunks of text
twig
{% set longVar %}<div>…</div>{% endset %}{{ longVar }}Copied
ERB:
defined?()
erb
<%# output: the content if `var` is defined %><% if defined?(var) %>…<% end %><%# output: `var` if `var` is defined, otherwise `fallback` %><%= defined?(var) ? var : fallback %>Copied
||=
, the OR Equal operator
erb
<%# output: `var` if it is defined and not nil and not false, otherwise `fallback` %><% var ||= fallback %><%=begin %> common front-end use cases:1. output a variable only if it is defined<%=end %><% var ||= nil %><%# set a variable with a fallback %><% x = y ||= nil %>Copied
Twig:
is defined
Especially useful when Twig's strict variables
option is turned on, in which case referring to an undefined variable will throw an error.
twig
{# output: Twig_Error_Runtime: Variable "x" does not exist. #}{{ x }}{# output: the content if var is defined #}{% if var is defined %}…{% endif %}{# output: `advance` if var is defined, otherwise `fallback` #}{{ var is defined ? advance : fallback }}Copied
??
, the null coalescing operator
twig
{# output: `var` if it is defined and not null, otherwise `fallback` #}{{ var ?? fallback }}{# common use cases:1. output a variable only if it is defined #}{{ var ?? null }}{# set a variable with a fallback #}{% set x = y ?? null %}Copied
ERB: #{var}
erb
<% x = 1 %><%= "this is interpolated: #{x}" %><%# output: `this is interpolated: 1` %>Copied
Twig: #{var}
twig
{% set x = 1 %}{{ "this is interpolated #{x}" }}{# output: `this is interpolated: 1` #}Copied
ERB: +
(plus). Note that to concatenate a string and a number in Ruby, the number must be converted to a string.
erb
<% string_variable = 'world' %><% number_variable = 2 %><%= 'hello ' + string_variable %> <%# output: `"hello world"` %><%= "example #{number_variable}" %> <%# output: `"example 2"` %><%= 'example ' + 3.to_s %> <%# output: `"example 3"` %>Copied
Twig: ~
(tilde). Note that strings and numbers can be freely concatenated.
twig
{% set string_variable = 'world' %}{% set number_variable = 2 %}{{ 'hello ' ~ string_variable }} {# output: `hello world` #}{{ "example #{number_variable}" }} {# output: `example 2` #}{{ 'example ' ~ 3 }} {# output: `example 3` #}Copied
ERB: n.each do |i|
…end
erb
<% items = ['a', 'b', 'c'] %><%# output: `...` %><% [0..items.length].each do %>.<% end %><%# output: `a b c ` %><% items.each do |item| %><%= item %><% end %>Copied
Twig: for i in n
…endfor
twig
{% set items = ['a','b','c'] %}{# output: `...` #}{% for i in 0..items.length %}.{% endfor %}{# output: `a b c ` #}{% for item in items %}{{item}}{% endfor %}Copied
ERB:
n.each_with_index do |i, index|
…end
erb
<%# output: `0. a 1. b 2. c ` %><% items = ['a', 'b', 'c'] %><% items.each_with_index do |item,index| %><%= index %>. <%= item %><% end %>Copied
n.times do |i|
…end
erb
<%# output: `0 1 2 3 4 5 6 7 8 9` %><% 10.times do |i| %><%= i %> <% end %>Copied
Twig: loop.index0
twig
{% for item in items %}{{loop.index0}}. {{item}}{% endfor %}Copied
ERB:
.each_with_index
's index
is always 0-indexed, so add 1
erb
<% items.each_with_index do |item,index| %><%= index + 1 %>. <%= item %><% end %>Copied
n.times do |i|
…end
erb
<%# output: `1 2 3 4 5 6 7 8 9 10 ` %><% 10.times do |i| %><%= i %> <% end %>Copied
Twig: loop.index
twig
{% for item in items %}{{loop.index}}. {{item}}{% endfor %}Copied
ERB: n.times do |i|
…end
erb
<% n = 3 %><%# output: `...` %><% n.times do %>.<% end %><%# output: `1 2 3 ` %><% n.times do |i| %><%= i %><% end %>Copied
Twig: for i in n
…endfor
twig
{% set items = ['a','b','c'] %}{# output: `...` #}{% for i in 0..items.length %}.{% endfor %}{# output: `a b c ` #}{% for item in items %}{{item}}{% endfor %}Copied
ERB: several options for formatting an object's data, notably: simply outputting, .inspect
ing, and debug()
ing. For basic data-checking purposes in a view, the essential difference is debug()
returns YAML while inspect
and printing return strings.
erb
<%# for some object `posts` %><%= posts %><%= posts.inspect %><%= debug(posts) %>Copied
Twig:
The |json_encode()
filter formats an object's data.
twig
{# for some object `posts` #}{{ posts|json_encode }}Copied
The dump()
function outputs information about a variable.
twig
{# for some object `posts` #}{{ dump(posts) }}Copied
Note: dump
must be enabled. Some implementations make it available out of the box (for example, Craft in dev mode).
ERB: .slice(index)
, .slice(start,count)
erb
<%= [1,2,3,4].slice(1) %> <%# output: `2` %><%= [1,2,3,4].slice(1,2) %> <%# output: `[2,3]` %>Copied
Twig: |slice(start,count)
or [start:count]
twig
{{ [1,2,3,4]|slice(1) }} {# output: `Array` #}{{ [1,2,3,4]|slice(1,2) }} {# output: `Array` #}Copied
Note: The output of the above Twig examples is Array
, because in Twig the output of {{ [anArray] }}
is Array
. If you need to print an array, use |json_encode
:
twig
{{ [1,2,3,4]|slice(1)|json_encode() }} {# output: `[2,3,4]` #}{{ [1,2,3,4]|slice(1,2)|json_encode() }} {# output: `[2,3]` #}Copied
In execution, no special steps are necessary:
twig
{% set myArray = [1,2,3,4] %}…Copied
count
itemsERB: .take(count)
or .first(count)
erb
<%= [1,2,3,4].take(2) %> <%# output: `[1,2]` %><%= [1,2,3,4].first(2) %> <%# output: `[1,2]` %>Copied
Twig: [:count]
twig
{{ [1,2,3,4][:2]|json_encode() }} {# output: `[1,2]` #}Copied
start
itemTwig: [start:]
twig
{{ [1,2,3,4][2:]|json_encode() }} {# output: `[3,4]` #}Copied
ERB
If trim_mode
is set to -
, a -
in the closing erb
tag will trim trailing whitespace:
erb
<% something -%>1<%= something_else -%>2<% another_thing %>Copied
is equivalent to
erb
<% something %>1<%= something_else %>2<% another_thing %>Copied
Twig
Trim leading or trailing whitespace by adding a -
inside in an opening or close delimiter, respectively:
twig
{% something -%}1{%- something_else -%}2{%- last_thing %}Copied
is equivalent to
twig
{% something %}1{% something_else %}2{% last_thing %}Copied
Twig
Twig doesn't care what language you are compiling to, but it does provide a special spaceless
tag for use with HTML.
twig
{% spaceless %}<div>…</div><span>…</span>{% endspaceless %}Copied
is equivalent to
twig
<div>…</div><span>…</span>Copied
Note that this spaceless
has limited powers:
it isn't recursive
twig
{% spaceless %}<div><div>…</div><div><span>…</span>{% endspaceless %}Copied
is equivalent to
twig
<div><div>…</div><div><span>…</span>Copied
and content between HTML tags will disrupt it
twig
{% spaceless %}<div>…</div>sorry, spaceless<span>…</span>{% endspaceless %}Copied
is equivalent to
twig
<div>…</div>sorry, spaceless<span>…</span>Copied
ERB:
Use a Symbol :property
to look up an operation on a Hash:
erb
<% myHash = {hello: 'world'} %><%= myHash[:hello] %> <%# output: "world" %>Copied
Twig:
Use dot notation or subscript syntax to access attributes of a variable:
twig
{% set myVar = {hello: 'world'} %}{{ myVar.hello }} {# output: world #}{{ myVar['hello'] }} {# output: world #}Copied
For a layout
file that pulls in page
:
ERB: content_for
in child, yield
in parent
layouts/layout.html.erb
erb
<%= yield :myBlock %>Copied
views/page.html.erb
erb
<% content_for :myBlock do %>the content<% end %>Copied
Twig: block
+ extends
in child, block
in parent.
layout.html.twig
twig
{% block myBlock '' %}{# or #}{% block myBlock %}{% endblock %}{# or #}{% block myBlock %}{% endblock myBlock %}Copied
page.html.twig
twig
{% extends 'layout.html.twig' %}{% block myBlock %}the content{% endblock %}Copied
or if all the content is a variable x, page.html.twig
twig
{% extends 'layout.html.twig' %}{% block myBlock x %}Copied
or if all the content is a single string, page.html.twig
twig
{% extends 'layout.html.twig' %}{% block myBlock "#{x} content" %}{# or #}{% extends 'layout.html.twig' %}{% block myBlock x ~ "content" %}Copied
or if all the content is a single literal string, page.html.twig
twig
{% extends 'layout.html.twig' %}{% block myBlock 'the content' %}{# or #}{% block myBlock "the content" %}Copied
ERB
layouts/layout.html.erb
erb
<% if content_for?(:my_content) %><%= yield :my_content %><% else %>default content<% end %>Copied
views/page.html.erb
erb
<% content_for :my_content do %>the content<% end %>Copied
Twig
main.html.twig
twig
{% block content %}default content{% block sub_content '' %}{% endblock %}Copied
override-content.html.twig
twig
{% extends 'main.html.twig' %}{% block content %}the content{% endblock %}Copied
Result of override-content.html.twig:
default contentCopied
override-subcontent.html.twig
twig
{% extends 'main.html.twig' %}{% block subcontent %}the sub-content{% endblock %}Copied
Result of override-subcontent.html.twig:
default content the sub-contentCopied
ERB:
render
will output the contents of another file
erb
<%= render 'path/to/x' %>Copied
To pass values to the rendered file, define them:
erb
<% a = 1 %><% b = 2 %><%= render 'path/to/x', a:a, b:b %> <%# in path/to/x a=1 and b=2 %>Copied
If the rendered file expects different variable names, use those:
erb
<% a = 1 %><% b = 2 %><%= render 'path/to/x', y:a, z:b %> <%# in path/to/x y=1 and z=2 %>Copied
Twig:
include
tag
twig
{% include 'path/to/x' %}Copied
include
function
twig
{{ include('path/to/x') }}Copied
The include
tag passes the entire parent context to the included file by default:
twig
{% set a = 1 %}{% set b = 2 %}{% include 'path/to/x' %} {# in path/to/x a=1 and b=2 #}Copied
To pass only certain data, use include with only
:
twig
{% set a = 1 %}{% set b = 2 %}{% include 'path/to/x' with {a:a} only %}{# in path/to/x a=1 and b does not exist #}Copied
Rename variables in the with
(can be combined with only
):
twig
{% set a = 1 %}{% include 'path/to/x' with {y:a} %} {# in path/to/x a=1 and y=1 #}{% include 'path/to/z' with {y:a} only %}{# in path/to/z y=1 and a does not exist #}Copied
Test packages that have not been published to a registry, without getting caught in the pitfalls of npm and yarn's built-in solutions.
Full interactive iOS Mobile Safari browser testing is possible right on your Mac, no additional services necessary. We'll set up Apple's Simulator and configure it for testing Safari on a wide range of iOS versions and devices.