Adding Responsive CSS to Shopify Blog Articles

Parsing HTML with the Liquid templating language

Posted by Nate Eckerson on October 09, 2019 · 5 mins read

If you’re looking for an awesome Shopify developer, get in touch!

Liquid! An open source templating language created by Shopify and written in Ruby. Sharing many similarities with the aforementioned language, it presents a unique challenge when attempting to do anything more than insert object values into the contents of a page. With no regex, a convoluted syntax for assigning variables, and unconventional iteration abilities (cycle tag anyone?), you are typically sent to the documentation to browse until the Liquid Way becomes clear and you can solve the problem at hand.

Working on the Slate Bootstrap theme, I ran into an issue with the contents of blog articles. While the Shopify blog editor gives you a simple WYSIWYG interface for composing the contents of posts, and the ability to add simple HTML markup, I was unsatisfied with the necessity of wrapping images and iframe elements (YouTube embeds) in Bootstrap’s responsive classes. It seemed like this was something the theme itself should do. Additionally, I had a storefront with many pages of posts, and I wasn’t about to retroactively wrap every post element in HTML for responsive display.

My initial research found little information on the topic. Some suggested adding the classes with Javascript after the page loaded, but I wasn’t interested in increasing page load time with additional scripts. So, it was time to see what could be done with Liquid.

Given the flexibility of another language, I would have used some regex to identify the </img> and </iframe> tags in the article content, then replaced portions of the tag with strings containing the responsive classes. However this was not possible in my environment.

Then I started looking at the Split filter. Liquid Filters are simple methods that modify the output of numbers, strings, variables and objects. The returned value can either be assigned to a variable, or output on the page immediately with double curly brackets, like so {{ }}.

split is used to create an array from a string, breaking the string into array items where a specified delimiter is found. Commonly used to convert comma-separated items from a string to an array, it stuck out to me because there was no limit to the length the specified separator could be. split worked with ", " and "banana" equally well. And since the separator was removed from the string in the array that was created, if the array was joined back together in a single string, the separator was effectively scrubbed out.

It looked like I had found something that worked like a simple find-and-remove. Now I had to build find-and-replace.

Enter the Join filter. join combines the element of an array together into a single string, using a specified argument as a separator. This was the missing piece. After I had stripped text out with split, I could take the resulting array and fill in the holes with join.

All of these concepts came together in the code below. While the img element requires only one pass to insert the responsive class, iframe requires two passes, since the element must be enclosed in a surrounding div.

In addition to the split & join filters, I tested the results of chaining multiple [replace](https://shopify.github.io/liquid/filters/replace/) filters together to achieve the same result. This did not improve performance, and while the code is more compact, it’s less readable.

The code below could be improved to conditionally wrap iframe elements in 16by9 or 4by3, depending on width & height specified in the iframe, but this was more than the blog required.

Browser Profiling

I created a blog article with 80 iframe element and 80 images. The iframes were empty, and the images were identical 616 x 411 jpgs displayed at their original resolution. To measure load times, I used the Chrome DevTools Network tab and refreshed the page. The page was hosted locally in the Slate environment.

To make sure the page was not being cached, I removed article.content from the page between tests, made sure the page was clear, then added in the filter code and hit refresh. I did this three times for each technique.

Outputting article.content on the page with no processing:

  • Load times: 3.68s, 3.78s, 3.23s
  • Average 3.56s

Split-join filters:

  • Load times: 3.34s, 3.57s, 3.36s
  • Average 3.42s

Replace filter chain:

  • Load times: 3.07s, 3.81s, 4.16s
  • Average: 3.68s