One of the mailing lists I subscribe to is I Only Speak Liquid, where they regularly publish emails from their stable of developers with some great insights into working on Shopify themes and more generally working as a freelancer/consultant in the Shopify ecosystem. If you’re looking for good content on those subjects, I definitely recommend subscribing.
One tip mentioned in an email by Billy Noyes was a way to use Liquid to minify CSS. The technique was provided by a community member in this forum post. It’s a great way to remove unnecessary characters (like whitespace and comments) from a CSS file without the need for an additional build tool, so it’s a low effort way to increase a site’s performance by reducing its page weight.
Original #
Here is the initial implementation (rewritten slightly to make comparison easier, and added some comments to explain what’s happening):
{% capture bloated %}
/* CSS ... */
{% endcapture %}
{%- liquid
assign original = ''
# remove line breaks,
# multiple whitespace characters and
# split on closing comment tags
assign chunks = bloated | strip_newlines | split: ' ' | join: ' ' | split: '*/'
# iterate over each chunk of CSS
for chunk in chunks
# remove comments and whitespace around syntax characters
assign mini = chunk | split: '/*' | first | strip | replace: '; ', ';' | replace: '} ', '}' | replace: '{ ', '{' | replace: ' {', '{'
assign original = original | append: mini
endfor
# calculate characters saved
assign change = original.size | times: 1.0 | divided_by: bloated.size | times: 100.0 | round: 1
%}
This alone was providing me with a 10% reduction across most of the CSS I tried it on. However, having looked at the code, I thought I could see an opportunity to improve on it very slightly, and wanted to see if there were any meaningful gains to be made. I noticed that the final semicolon in a ruleset was being preserved, but it can safely be removed. If you have only 100 rulesets that could still save up to 100 characters. That extra bit of code was replace: ';}', '}'
.
Test #
I wanted to find out how much of a difference this made on a real codebase, and also test that nothing would break as a result, so I set up the following test. I used a 5,000+ lines CSS file from one of my projects to test the results.
{% capture bloated %}
// 5000+ lines of CSS
{% endcapture %}
{%- liquid
assign original = ''
assign chunks = bloated | strip_newlines | split: ' ' | join: ' ' | split: '*/'
for chunk in chunks
assign mini = chunk | split: '/*' | first | strip | replace: '; ', ';' | replace: '} ', '}' | replace: '{ ', '{' | replace: ' {', '{'
assign original = original | append: mini
endfor
assign change = original.size | times: 1.0 | divided_by: bloated.size | times: 100.0 | round: 1
%}
{%- liquid
assign optimised = ''
assign chunks = bloated | strip_newlines | split: ' ' | join: ' ' | split: '*/'
for chunk in chunks
assign mini = chunk | split: '/*' | first | strip | replace: ': ', ':' | replace: '; ', ';' | replace: '} ', '}' | replace: '{ ', '{' | replace: ' {', '{' | replace: ';}', '}'
assign optimised = optimised | append: mini
endfor
assign change = optimised.size | times: 1.0 | divided_by: bloated.size | times: 100.0 | round: 1
%}
Results #
Here are the results:
Nice! An extra 300+ characters removed equating to over 2.5% more of a reduction. The output CSS worked perfectly still. For very little effort or intervention, a 10%+ reduction is a great result, and will benefit almost any project.
Finally to wrap it up I created a snippet called minify-css.liquid
so that I can easily add this feature to any large CSS files as well as to snippets and sections that may also contain large chunks of CSS that could benefit from minification.
{%- liquid
assign chunks = input | strip_newlines | split: ' ' | join: ' ' | split: '*/'
for chunk in chunks
assign mini = chunk | split: '/*' | first | strip | replace: ': ', ':' | replace: '; ', ';' | replace: '} ', '}' | replace: '{ ', '{' | replace: ' {', '{' | replace: ';}', '}'
echo mini
endfor
%}
So it can even be used like this:
<style>
{%- capture bloated %}
// Unminified CSS
{%- endcapture %}
{%- render 'minify-css', input: bloated -%}
</style>
Now I can maintain beautiful, well documented, human-readable CSS code across an entire theme and still have it optimised for performance without any additional build tools.