Advanced Liquid Recipes
- Introduction
- Format
- The Recipes: How to…
- Comment out some broken code
- Make a literal string list
- Filter a list to elements containing a substring
- Filter a list to elements containing a variable substring
- Write test cases using
capture
- Using Liquid to generate non-html files
- Strip your includes
- Make a map
- Return a value from an include
- Recurse in an include
- Conclusion
Introduction
After using Jekyll for a number of years, I’ve had to discover or invent a number of Liquid “recipes” (cocktails?) for doing common tasks that should be easy in a sane language, but which are not obvious how to do in Liquid.
Since these (anti-?)patterns are under-documented elsewhere, I thought I’d collect them here in case this list is useful for others, especially those who are starting to use Jekyll / Liquid for serious development.
Format
Each entry below will show off some Liquid code and what that code outputs:
world
It assumes you’re already familliar with the fundamentals of writing Liquid in Jekyll.
The Recipes: How to…
Comment out some broken code
The comment
tag in Liquid unfortunately will parse and process the inner content
block 😮💨
In order to temporarily comment out broken Liquid code, you need to double wrap it first in raw
and then in comment
to ensure Liquid doesn’t try to parse the code inside the comment:
{% comment %}{% raw %}
{% broked
{% endraw %}{% endcomment %}
Just beware of any {% endraw %}
s inside the comment!
Make a literal string list
You cannot directly instantiate a list in liquid. To make a list of literal strings, you’ll have to use “split”:
hello
Filter a list to elements containing a substring
Don’t be discouraged! contains
works as you’d expect inside a where_exp
:
great!
Filter a list to elements containing a variable substring
The above is all well and good, but what if the substring you’re looking for is contained in a variable?
In that case, use {% capture %}
to build the filter expression dynamically!1
world
Write test cases using capture
Yes, the capture
tag is super powerful!
One of my favorite uses is to write test cases!
You can simply capture
the output of an include
and then make sure it contains
the expected string:
My test: Passes!
Using Liquid to generate non-html files
I usually will make a null layout file for this case:
_layouts/nil.html
:
Then, in whatever file you want Jekyll to process, just add the null layout:
create-dynamic-bash-file.html
:
Note that the template ends in .html
so that Jekyll knows not to markdownify
it.
It will be renamed to my-script.bash
in the output directory.
This will always work, even to generate a markdown file, and it makes it clear that the source file is not (yet) a real e.g. bash script.
Strip your includes
When you call {% include something.md %}
, newlines will be automatically added—even if they aren’t in the original file!
Since this breaks certain markdown patterns, you may want to have a strip
ped include.
You could could capture
the output, then call {{ captured_output | strip }}
but this is cumbersome and ugly for the caller.
To make the include self-stripping, just make sure to put a whitespace-erasing {%- -%}
s at the ends of the include:
_includes/inline_bold.md
:
_somewhere/else.md
:
- my
- amazing
- list
Make a map
Sometimes you need a more advanced data structure than just a list.
To implement a Map, I usually use two parallel lists of keys and values.
new key => new value
See the next recipe for how to access my_map[some_key]
Return a value from an include
Sometimes you find yourself doing some messy calculation in multiple places, and (being a good programmer) you want to DRY out your code.
But Liquid _includes
don’t have return
statements… so, can you actually write an _include
which just performs a calculation?
Of course!
Your first thought might be to use {% capture returnvalue %}{% include my_func.liquid param="foo" %}{% endcapture %}
and to just output your return value inside the include.
And that will work!
But that’s limited to returning strings, and usually it’ll come with lots of added whitespace.
To return arbitrary variables, leverage the fact that your local variables aren’t actually local!!
_includes/ArrayDict_lookup.liquid
:
_somewhere/else.markdown
:
Hello World
Note how in the above, value
was set in the _include
but then used by the calling template!
This means (among other things) that you should never {% assign content =
in an _include
as this will nuke the parent page’s content
!
Recurse in an include
You might think that local variables always leaking their scope would make recursive includes impossible. It certainly makes them tricky!
While general, “local” variables are a no-go in a recursive include, you can use:
include.parameter
variables (as the include objects are indeed put on a stack)for
loop variables (as the loop tracks its own state)- Temporary variables (who are assigned and used on the same side of a recursive call)
For example, this _include
takes a list of lists and outputs them as nested markdown:
_includes/recursive_lists.md
:
_somewhere/else.md
:
Outputs:
- a
- b
- c
- Sublist:
- 1
- 2
- 3
- Sublist:
- Z
- Y
- X
- 5
- e
Check your understanding: What do you think would happen if that {%- endfor -%}
at the end of the above _include
was instead {% endfor %}
?
Conclusion
Liquid can be a frustrating language at times, but you can usually convince it to do what you want.
And if you can’t, you can always write a _plugin
!
Footnotes:
-
Yes, this is basically a hidden
eval
function. With great power… ↩