DEV Community

Mike Fallows
Mike Fallows

Posted on • Originally published at mikefallows.com on

Optimising CSS minification in Liquid

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 %}

<!-- CSS {{ bloated.size }} chars -->
{%- 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
%}
<!-- original -{{ bloated.size | minus: original.size }} {{ 100 | minus: change }}% -->

Enter fullscreen mode Exit fullscreen mode

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 %}

<!-- CSS {{ bloated.size }} chars -->
{%- 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
%}
<!-- original -{{ bloated.size | minus: original.size }} {{ 100 | minus: change }}% -->

{%- 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
%}
<!-- optimised -{{ bloated.size | minus: optimised.size }} {{ 100 | minus: change }}% -->

Enter fullscreen mode Exit fullscreen mode

Results

Here are the results:

<!-- CSS 121066 chars -->
<!-- original -12829 10.6% -->
<!-- optimised -16091 13.3% -->

Enter fullscreen mode Exit fullscreen mode

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.

Extracting to a snippet

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
%}

Enter fullscreen mode Exit fullscreen mode

So it can even be used like this:

<style>
{%- capture bloated %}
// Unminified CSS
{%- endcapture %}

{%- render 'minify-css', input: bloated -%}
</style>

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)