<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Freelance full-stack Shopify development - Lincolnshire, UK | Tom Blanchard]]></title><description><![CDATA[Freelance full-stack Shopify development - Lincolnshire, UK | Tom Blanchard]]></description><link>https://tomblanchard.co.uk/</link><image><url>https://tomblanchard.co.uk/favicon.png</url><title>Freelance full-stack Shopify development - Lincolnshire, UK | Tom Blanchard</title><link>https://tomblanchard.co.uk/</link></image><generator>Ghost 3.37</generator><lastBuildDate>Sat, 18 Apr 2026 10:39:50 GMT</lastBuildDate><atom:link href="https://tomblanchard.co.uk/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[My simple, no-build Shopify theme workflow (Tailwind included)]]></title><description><![CDATA[<p>I've developed 100's of custom Shopify themes over the 10+ years that I've spent working as a Shopify theme development specialist. When I first started working with themes, they were in their infancy, they were very simple and there was no need for a build process. As the years went</p>]]></description><link>https://tomblanchard.co.uk/my-simple-no-build-shopify-theme/</link><guid isPermaLink="false">691f3958e4aa40041f7056ba</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Fri, 21 Nov 2025 16:27:38 GMT</pubDate><content:encoded><![CDATA[<p>I've developed 100's of custom Shopify themes over the 10+ years that I've spent working as a Shopify theme development specialist. When I first started working with themes, they were in their infancy, they were very simple and there was no need for a build process. As the years went by, things changed and as codebases started to get more complex, build processes within theme development started to become more common to handle tasks like JS transpilation, code minification etc. </p><p>Everyone has their own build process, I've contracted at many agencies and they all have a slightly different setup. Some use an old school Grunt/Gulp setup, some use NPM scripts and some use Vite/Parcel or similar. Sometimes these build processes work with no issues but I can't count the amount of hours I've seen wasted due to build process problems, usually down to dependency issues, e.g. having the wrong version of Node/NPM installed.</p><p>I remember when Shopify released <a href="https://github.com/Shopify/slate">Slate</a>, it came with a complex build process which tried to do way too many things in my opinion. There was a lot of frustration from developers having issues with it and the project eventually got deprecated. Too many moving parts = more things that can potentially break. Since then, Shopify has released <a href="https://github.com/Shopify/dawn">Dawn</a> and then <a href="https://github.com/Shopify/horizon">Horizon</a> as their flagship starter themes which both use a "no-build" approach, there's less to go wrong without a build process and no time wasted trying to debug related issues due to a package discrepancy or operating system inconsistency.</p><p>I've experimented with all kinds of build processes over the years, I settled with a very lightweight NPM scripts based approach for a while and was quite happy with it. In recent times though, I've started to favour not using a build process at all. I kicked off a new full build theme project without a build process and I wanted to see how far I got until I caved, now I never want to touch a build process ever again!</p><p>In my previous, simple NPM scripts based build process, it handled a few things for me:</p><ul><li>Javascript compilation/transpilation via Babel, this read my source JS files and outputted minified bundle JS files to serve to the browser</li><li>SVG icons, this went through a folder of SVG files and outputted an SVG icon sprite</li><li>Tailwind compilation, this read my source Tailwind CSS file and outputted a minified CSS file, using the JIT compiler so it only included classes used within the theme HTML</li></ul><h2 id="javascript">Javascript</h2><p>To cut out the JS compilation step, I adopted a similar approach to Dawn/Horizon, just use separate JS files served directly to the browser with web components - this was considered bad practice for a while as it hindered page speed performance. This is no longer the case with modern HTTP/2 as it's multiplexed, meaning it can request hundreds of resources in parallel using a single TCP connection. Shopify also automatically minified these JS files when served in the browser, this gives us the best of both worlds, great developer experience with all the performance gains of using a bundler.</p><h2 id="icons">Icons</h2><p>For SVG icons, I stopped using the sprite approach and leveraged the new(ish) <code>inline_asset_content</code> Liquid filter. Each SVG icon is a separate file within the theme "assets" directory and to output the icon, simply do:</p><pre><code>{{ 'icon.svg' | inline_asset_content }}</code></pre><h2 id="tailwind">Tailwind</h2><p>The Tailwind compilation was the trickiest part of going "no-build", Tailwind has been my go-to solution for scalable CSS for years now and is it's usage is non-negotable for me so I had to find a decent solution for this. It turns out that Tailwind has a feature they call the Play CDN, this enables you to use the compiler directly inside of the browser without a build step. It's not meant for production use, it's a Javascript-based solution which dynamically generates CSS based on the page HTML and is meant to be used to test Tailwind features and to build prototypes.</p><p>You can also provide the Tailwind Play CDN with config, I assumed it'd eventually break with a complex config setup with many colors, font sizes, variants etc. but it's very stable in my experience. To get started with this approach, I added a new file <code>/assets/tailwind.css</code> and populated it with a simple config:</p><pre><code>@import "tailwindcss";

@theme {
  --color-primary: red;
  --color-secondary: green;
  --color-tertiary: blue;
}</code></pre><p>Within my <code>/layout/theme.liquid</code> file, I added this snippet:</p><pre><code>&lt;script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"&gt;&lt;/script&gt;

&lt;style type="text/tailwindcss"&gt;
  {{ 'tailwind.css' | inline_asset_content }}
&lt;/style&gt;</code></pre><p>That's it, we now have full use of Tailwind during theme development without the need for a build process. See the below demo video of this in action, notice how new classes are automatically generated as the HTML changes, the Play CDN uses a mutation observer to detect HTML changes and the Tailwind JIT compiler dynamically generates new classes on the fly and injects the necessary CSS into the DOM, it makes for very a very smooth developer experience.</p><!--kg-card-begin: html--><iframe width="788.54" height="443" src="https://www.youtube.com/embed/LSrUn8AyT4k?si=bpYPNkxc9oIC9vY0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe><!--kg-card-end: html--><p>This is a great workflow during development but what about production? This is where it gets a bit more complex, I started off by adding this snippet to my <code>/config/settings_schema.json</code> file:</p><pre><code>{
  "name": "Mode",
  "settings": [
    {
      "type": "select",
      "id": "mode",
      "label": "Mode",
      "options": [
        {
          "value": "production",
          "label": "Production"
        },
        {
          "value": "production-css-inline",
          "label": "Production (CSS inline)"
        },
        {
          "value": "development",
          "label": "Development"
        }
      ]
    }
  ]
}</code></pre><p>Then within my <code>/layout/theme.liquid</code> file, I changed my previously added CSS snippet to this:</p><pre><code>{% case settings.mode %}
  {% when 'production' %}
    &lt;link rel="stylesheet" href="{{ 'tailwind.min.css' | asset_url }}"&gt;
  {% when 'production-css-inline' %}
    &lt;style&gt;
      {{ 'tailwind.min.css' | inline_asset_content }}
    &lt;/style&gt;
  {% when 'development' %}
    &lt;script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"&gt;&lt;/script&gt;

    &lt;style type="text/tailwindcss"&gt;
      {{ 'tailwind.css' | inline_asset_content }}
    &lt;/style&gt;
{% endcase %}</code></pre><p>This adds the following section to the global theme settings within the theme customiser:</p><figure class="kg-card kg-image-card"><img src="https://tomblanchard.co.uk/content/images/2025/11/image.png" class="kg-image" alt srcset="https://tomblanchard.co.uk/content/images/size/w600/2025/11/image.png 600w, https://tomblanchard.co.uk/content/images/2025/11/image.png 606w"></figure><p>This "Mode" setting has three options: "Production", "Production (CSS inline)" and "Development". if set to "Production", the theme will use a regular CSS file reference and look for the file <code>/assets/tailwind.min.css</code>. If set to "Production (CSS inline)", the theme will output the contents of the file <code>/assets/tailwind.min.css</code> within a <code>&lt;style&gt;&lt;/style&gt;</code> tag (this can be beneficial for performance, but only if the file is small enough, be careful with this and run performance tests if unsure). If set to "Development", the theme will use the Tailwind Play CDN as discussed above.</p><h2 id="build-process-and-chill">Build process and chill?</h2><p>The only problem we have here is how do we go about using the <code>/assets/tailwind.min.css</code> file if it doesn't exist? This part does technically need a build process unfortunately, in theory this can be automated via a Git pre-commit hook and/or GitHub Action but I haven't looked into this yet. I handle this via a simple NPM script within my `package.json` file:</p><pre><code>{
  "name": "shopify-theme-no-build",
  "scripts": {
    "build": "npx @tailwindcss/cli -i ./assets/tailwind.css -o ./assets/tailwind.min.css --minify"
  },
  "dependencies": {
    "@tailwindcss/cli": "^4.1.13",
    "tailwindcss": "^4.1.13"
  }
}</code></pre><p>I simply just run <code>npm run build</code> before I push to production and that's it. I know this sounds like it defeats the purpose of not using a build process but the main thing I wanted to avoid was needing to use a build process during active development. When it comes to making changes to a project with this setup, it's just a case of pulling down the repo, running <code>shopify theme dev</code>, changing "Mode" to "Development" within the theme settings and I'm ready to go.</p><p>I've added a very bare-bones example of this setup within a Shopify theme in a GitHub repository <a href="https://github.com/tomblanchard/shopify-theme-no-build">tomblanchard/shopify-theme-no-build</a> if you'd like to nerd-out further - enjoy and thanks for reading! ✌️</p>]]></content:encoded></item><item><title><![CDATA[Shopify Scripts to Functions Migration Guide]]></title><description><![CDATA[<p>I’ve been avoiding learning how to use Functions for a while now but as the Scripts deprecation date (August 13, 2024) looms closer, I’ve decided to invest some time to learn how to set things up.</p><p>At first glance, it looks like the only way to use Functions</p>]]></description><link>https://tomblanchard.co.uk/shopify-scripts-to-functions-migration-guide/</link><guid isPermaLink="false">651aee5ce4aa40041f7055ba</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Mon, 02 Oct 2023 16:44:45 GMT</pubDate><content:encoded><![CDATA[<p>I’ve been avoiding learning how to use Functions for a while now but as the Scripts deprecation date (August 13, 2024) looms closer, I’ve decided to invest some time to learn how to set things up.</p><p>At first glance, it looks like the only way to use Functions is inside of a full blown Shopify app. The thought of letting a client know that they’ll have to purchase hosting for an app and pay a monthly fee just to replace functionality they currently get for free with Scripts rubs me the wrong way.</p><p>Luckily, we can use an <a href="https://shopify.dev/docs/apps/app-extensions/extension-only-apps">extension-only app</a> to avoid having to create a proper app, which means we don’t have to worry about setting up all the app boilerplate, setting up a server, dealing with authorisation and developing app UI pages etc.</p><p>What I’ll be detailing in this blog post is how to get started with Functions and translating an existing Ruby-based Script into Javascript which will be used by a Function and internally compiled into WebAssembly.</p><h2 id="example-script">Example Script</h2><p>Here’s the Script I’ll be using as an example:</p><pre><code class="language-RB">BULK_DISCOUNTS = [
  {
    quantity: 10,
    discount: 20.0,
    message: "20% off 10 or more"
  },
  {
    quantity: 40,
    discount: 30.0,
    message: "30% off 40 or more"
  }
]

cart_lines_quantity_total = 0

bulk_discount_active = nil

Input.cart.line_items.each do |item|
  cart_lines_quantity_total += item.quantity
end

BULK_DISCOUNTS.each do |discount|
  if cart_lines_quantity_total &gt;= discount[:quantity]
    bulk_discount_active = discount
  end
end

unless bulk_discount_active
  Output.cart = Input.cart
  exit
end

Input.cart.line_items.each do |item|
  item.change_line_price(item.line_price - (item.line_price * (bulk_discount_active[:discount] / 100)), message: bulk_discount_active[:message])
end

Output.cart = Input.cart</code></pre><p>This Script applies a discount to all line items if the total line item quantity reaches a certain threshold based on a config object. E.g. If total cart item quantity is 1 then no discount is applied, if total cart item quantity is 10 or more then apply a 20% discount to all line items, if total cart item quantity is 40 or more then apply a 30% discount to all line items.</p><h2 id="creating-the-app">Creating the app</h2><p>To get started with migrating the Script to a Function, I’ll assume you have the following:</p><ul><li>Shopify Partners account</li><li>Shopify development store</li><li>Installed Node.js 16 or higher</li><li>Installed Shopify CLI 3.0 or higher</li></ul><p>Start off by opening up Terminal and entering the following command:</p><pre><code>npm init @shopify/app@latest</code></pre><p>This will create a new app directory on your local machine, give it a name e.g. “tom-blanchard-functions-test”:</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/onxzElDkB8xE93ygNOtxVOXN8xvIYisFuZ8LR4besF2ZZ5UMG_aaSGxcYeBNjcI7y2A3VIWQ81dkRshEsL1UuksUVjHhpuQf7zIwfRDSH8V54noJVbnkolR2DWndFUBeDhCUWmpqYEEVRCqzc-tRtM4" class="kg-image" alt></figure><p>Next, choose the “Start by adding your first extension” option:</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/VbsUoBe81S4g-hAv8bIKxNvHgOOzRu6xWBddNqOj53TYZFfy3_cpshnIz0uNd6iOC5zf8gs49ZGT8abByL-d6FkFFTDyNQC9CzcyQmgIWIkaz--tzOB_DCM-hn5TumHzkIw6td0tTgSEcdlILU9GUyA" class="kg-image" alt></figure><p>This will create the app directory for you:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/DxiR6VDah9wrVN3AwNMVqLpW4zwLak-_Wt4CPVLlv0GXeCWTFgfUawGG4oOaJTc2z4MIddLBCRhJkGAjWL-k9Z_9lqKMxhl9PWIQ1oBPvcfuxtOfGyVGl_pU9x7sxBqoL6FVvpWPTbKw8qHYbIcL_cs" class="kg-image" alt></figure><h2 id="deploying-the-app">Deploying the app</h2><p>Navigate to this directory in Terminal and run the following command:</p><pre><code>npm run deploy</code></pre><p>This will create the app on your Shopify Partners account, choose the Partners organization you want the app to be associated with:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/OU4qQ7Oo9m1vrEguhTeNvLRiLK5sB_mWcHYm3woLm11EYc0tWmSObgFsHK8GiUuuPfh8xWnTnCK2_X7vpaBPztqd_Gf2V5xnMOj6fKiyxJDJkcnUAdeXiyS0NBSLTHIKbHNMYD_vN7l-Y0u2lT8pYCs" class="kg-image" alt></figure><p>Next, choose the “Yes, create it as a new app” option:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/LDOS_q5OLMmkSui9mYsQJwZu25BUimxq0Ypy0try8mAqUvri8cgEPezbovtZkOtiR2heFXn-jir_KmerLCWxARxmDMtU0pRjnFtW6ngjSZE7SvkQnP08SjpZPtkT71LDiLVnZJvYnNqPMn_DrJvE91o" class="kg-image" alt></figure><p>Next, give it a name e.g. “tom-blanchard-functions-test”:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/WBYxOQbUdeCZE0Wk-l_v6e6g_zyYdb6OSDh4MdoBGHcWAAlJl4e4C2PJ2naOEyQLWlzrrJMt_OysSw2OVm5WEdYZl4ny0HANrJAkyUebYlD-YQkPHAYPdguE0JER0gDiojSkGdit85OdZeJqz8f_EhQ" class="kg-image" alt></figure><p>Next, choose the “Yes, release this new version” option:</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/c71h9omIfeXf0RyfH3lrQ_JQrOFCO27KIGppqE1Gr9ULxUX1pbXHsbPpV_AC0zJ2j2IQYUBPpi4e7ocjhoTChsFhmaf3duSlSpCE8y9vnWr2b4VfWWRijl9yjCujB1d4wIPVUp7bafiltPZhb3bGbjg" class="kg-image" alt></figure><p>You should now be able to see this app listed under Shopify Partners -&gt; Apps:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/1YwHdc0iikF4NXqPGw5cg3smOWdj4OEqR2P7Ghx0zE6u35mRrfgte11tpPRsT94oeRok8GJh2hWkQlNNeo5-PYJzpuecOX8stakdZh_BpRIc9-OOIIxV1kYKv1TrXS0m6RgvhyY4al17zrY7D5wqdMI" class="kg-image" alt></figure><p>Next, run the following command:</p><pre><code>npm run shopify app config link</code></pre><p>Select the Partners organization you chose before then choose “No, connect it to an existing app” and find the app you created in the above steps. This will pull down your remote app config and update your local “shopify.app.toml” file:</p><figure class="kg-card kg-image-card"><img src="https://lh5.googleusercontent.com/W_Umf_OjCYQNkAk9FMV3MvrGmy2tgAhKng6no2jC-Dzf6frMmc9_0bwORioZQyq_-A3_L1cR8EV2J4GwxnWi6BIPV_f_ZKPy2qKmmB4ajmZEP8pxUryqA-siCEwDUYEDnhjrnds2OdcJHOB9XoMvUQU" class="kg-image" alt></figure><h2 id="creating-the-app-extension-function-">Creating the app extension (Function)</h2><p>Run the following command:</p><pre><code>npm run shopify app generate extension -- --template product_discounts --name product-discount</code></pre><p>Choose the “Javascript” option:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/9igMp9LFXyDvIjUDuHWCHk1UVsHAlQRwNL88lbN45i_g9hN9igMtEzRCy0AXRP7INfirSArAHTH3CPxkK5huP5rStUaT4i2u9geqQb-mT7oXgBP3dMSWKNWsC5A_11Zv05Fz1NguZBkmoIkiAImmJlI" class="kg-image" alt></figure><p>This will create a new directory “extensions/product-discount” which will house the configuration and logic to this specific Function:</p><figure class="kg-card kg-image-card"><img src="https://lh5.googleusercontent.com/9RCkv2R8TFlfgR4a7osqb4NE-TneCIdiDGIccMXtHwA9oRdf6mpGg5XwtTNSbepafCPneqap3mg3QteyH6vlKueVKMStWsgwCdWzoDc7JUFy0rmY1O4ujAzrxk-lG9qTBXNGQsB1DGSCF0DlnUxV7mY" class="kg-image" alt></figure><p>Next, open the file “shopify.app.toml” and change the scopes value to "write_products, write_discounts":</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/wGxkye1oDJItA3KRf0C30_lZ-a11gd_UuNYKr7RJzM55RtSx42PXsQ6C7gCGRNfWzqnCn0mtG8h48h9ypF9SKHX_vaUz6EHI7G_JddN9qt_I3CnD3mZmHQ2YFdi9E4_dbJVf0DB86jWvYUe7r6hp2e0" class="kg-image" alt></figure><p>Open the file “extensions/product-discount/shopify.extension.toml” and change API version to “2023-10”:</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/pQ4pQDMf0qDGEZ6VL24R9Vj4iscQbo0ljY0ajtyX4CQHJPahyZnXkjU8l0erQJGDMUharcl_nOLwx6uyUCe4My5EhcGlrKW7fqPXy7_aF8z6AdxfNheoeR08AK511QvnVhCysCGXyeV736tTFTboA8k" class="kg-image" alt></figure><p>Open the file “extensions/product-discount/input.graphql” and replace it with this:</p><pre><code>query Input {
  cart {
    lines {
      quantity
      merchandise {
        __typename
        ...on ProductVariant {
            id
        }
      }
    }
  }
  shop {
    metafield(namespace: "tom-blanchard-functions", key: "config") {
      type
      value
    }
  }
}</code></pre><p>Open the file “extensions/product-discount/src/index.js” and replace it with this (this is a direct code translation of the original Ruby-based Script):</p><pre><code class="language-JS">import { DiscountApplicationStrategy } from "../generated/api";

var EMPTY_DISCOUNT = {
  discountApplicationStrategy: DiscountApplicationStrategy.First,
  discounts: [],
};

var BULK_DISCOUNTS = [
  {
    quantity: 10,
    discount: 20.0,
    message: "20% off 10 or more"
  },
  {
    quantity: 40,
    discount: 30.0,
    message: "30% off 40 or more"
  }
];

export default (input) =&gt; {
  var config = BULK_DISCOUNTS;

  var targets = input.cart.lines
    .filter((line) =&gt; line.merchandise.__typename == 'ProductVariant')
    .map((line) =&gt; {
      var variant = line.merchandise;

      return {
        productVariant: {
          id: variant.id
        }
      }
    });

  var cartLinesQuantityTotal = input.cart.lines.reduce((total, line) =&gt; {
     total += line.quantity;
    return total;
  }, 0);

  var bulkDiscountActive = config.find((bulkDiscount) =&gt; {
    return cartLinesQuantityTotal &gt;= bulkDiscount.quantity;
  });

  if (!bulkDiscountActive) {
    console.error('No cart lines qualify for this discount.');
    return EMPTY_DISCOUNT;
  }

  return {
    discounts: [
      {
        targets,
        value: {
          percentage: {
            value: bulkDiscountActive.discount
          }
        },
        message: bulkDiscountActive.message
      }
    ],
    discountApplicationStrategy: DiscountApplicationStrategy.First
  };
};</code></pre><p>Now run the following command:</p><pre><code>npm run shopify app function schema -- --path extensions/product-discount</code></pre><p>This uses the API type and version of your Function, as defined in your extension TOML file, to generate the latest GraphQL schema. The schema is written to the schema.graphql file.</p><p>Now run the following command:</p><pre><code>npm run shopify app function typegen -- --path extensions/product-discount</code></pre><p>This creates GraphQL types based on your input query for a Function written in JavaScript.</p><p>Now run the following command and choose “Yes, confirm changes”:</p><pre><code>npm run shopify app config push</code></pre><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/sai7Z9ywrD7sA1ma_fH3_-HgKvqCNzIYNks1oC0XnGunoDvxAGiYhLinfwn_QqHb7O0510WVSfEcRk2uyAe93rwUHyPEuQW1u1tmLZxqckSYqGAqPqLVHRaQ0sDSnHFZjVRT5MQqriWKpwOFOnLoYEI" class="kg-image" alt></figure><h2 id="deploying-the-function">Deploying the Function</h2><p>Run the command:</p><pre><code>npm run deploy</code></pre><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/Kok-bmcJVd8ZWOOkpLHTP_b7o_G_VIKtY-ZNqeaRq72NEajoUAUX5QoqRNjU8VKiremB3GGGLO6tPL1AznHOqHLbwwSBsjfe6VRnquHqlIo2nucwPfigt4SFvDEgB1DATOK5XwXjvWLM3mpb8oIEwlE" class="kg-image" alt></figure><h2 id="installing-the-app">Installing the app</h2><p>Let’s install this app on your store; head Shopify Partners dashboard -&gt; Apps and find the app you just created:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/f1YWdGmsMwn1jOgz6KmpzttG5qOEip1unamdf4xsxlcMn7QdxCHuBPXXoRlskMYDWt8jFcVZoIdXvO-CVb829VgscUXZSk8Vd3L_HqSdhUi4Cd-0qvicOAES_KMcftoKNc-9XYSSVibtqaj9S6x7vMg" class="kg-image" alt></figure><p>Click the “Choose distribution” button and select the “Custom distribution” option:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/N-0bKMLYcJOyNHCnmLLNeJM2uLO5cx_-gXZpYoIPEaRD1fsGzMVYWPJh2RlfHVV_NgF3ZPyxjsgxlBwZUhEVHK0dsRHOLDq8m717oRnOaOoRO6W97fNwu80KM7FxscearEbuKPpSaG5a4xH5FfHiRwc" class="kg-image" alt></figure><p>Next, enter in the store URL you’d like to install this app on:</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/-MO3hOYK-X8RllOedd7rgdeYGL5n-X3PzfdulObp5oIByxO5wzW2wGJOGLMxgM8gr9YWcp7Xg-83ZqKdv_eYeNLVxHvATMDeZ4jQwiZG3rv9Wvf3F2WhDBdumg2JCjFmsnVnxHB746zb8auaq4U1Fz4" class="kg-image" alt></figure><p>Next, click “Generate link”:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/ZBUs5uVZCMWI4dm25c2v86IeeVzZLvR3VFiVUtRjedTdd-CxWe7Sbr4ZvCsdyD0pHmPASMCIOdH1Y-MI9nLg8SB-cDcFVwEZg7wZdPLUZRT50Gc3H_3EStOTgXKrtdW-0N5GsC9Sgk8uq22hlEKGgCI" class="kg-image" alt></figure><p>You’ll now be able to visit this URL and install the app on your store:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/fLeaNI9_HbztX_yHY4VvneVBAQP8NUgM4rL7N5NvLne-IK2DJecgpyzp9etz9zLMi76vStLJW5VzuqIGmv8INbmbi4rE7VQRr-IZ8WzJkHFryEp9A3c0lPyP19iBmdg425LHNRWbYR1U1pbwnq9UMf8" class="kg-image" alt></figure><p>Scroll down and click “Install app”. This will have installed the app on your store but the Function we created still needs to be activated.</p><h2 id="activating-the-function">Activating the Function</h2><p>Install the Shopify GraphiQL App by visiting this URL: https://shopify-graphiql-app.shopifycloud.com</p><figure class="kg-card kg-image-card"><img src="https://lh5.googleusercontent.com/A2Fiafox2sO7jH6pT8LR86XncLiWQDzhTSApWxFs2TYRV0vfjIHBFa-2pqKxPNnLiVR7M2KCBvezVoGfG0nSaonLYf2OF30m627u-08atkBlvfW0Ps0IDnKZEPAas6S3QXKNibZT8fqGZY2kGB56jxU" class="kg-image" alt></figure><p>Enter in your store URL and make sure the scopes “discounts/read” and “discounts/write” are enabled then click the “Install” button. This will bring you to this page:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/5IbYYqZ2fXVseVfgz6ZMlk5KhgLfl1Hh_Ut3MRKujx-y0e5-BFOk37-Hwb5_7tqVTZFeRG7JfuCQj_xCmSSTO69MEsNRB18RKzHpQHBApRooV6naiD54UTpk30yCdttvuCpS0NnceJkFif1sznIVC4A" class="kg-image" alt></figure><p>This app allows you to run GraphQL queries and mutations against your store. Now, copy/paste this code into the app and click the run button:</p><pre><code>query {
  shopifyFunctions(first: 25) {
    nodes {
      app {
        title
      }
      apiType
      title
      id
    }
  }
}</code></pre><p>You should see something like this:</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/L9spiGUnB6MUQVlwz3kofVI3YTYNazv7Xu2ZiqN_6Vg0ZOaPG3Xz1eph18FQy1UCSGJ7BJuEY8EjYtgbCxmP0vxa0CXHzqgZOxCC9D_eWLjelHpMfXn_8kYaw2MjTWKNaL7XTT6V4VfbdHhQ5im9TKU" class="kg-image" alt></figure><p>The ID highlighted in the above screenshot is the ID of the Function we need to activate so make a note of it. Next, copy/paste this code into the app, replace “YOUR_FUNCTION_ID_HERE” with the Function ID and click the run button:</p><pre><code>mutation {
  discountAutomaticAppCreate(automaticAppDiscount: {
    title: "tom-blanchard-functions-test",
    functionId: "YOUR_FUNCTION_ID_HERE",
    startsAt: "2022-06-22T00:00:00"
  }) {
     automaticAppDiscount {
      discountId
     }
     userErrors {
      field
      message
     }
  }
}</code></pre><p>You should now see something like this:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/h8IA6AANztYqlJ4uxKa2c2CduKtVZ1HdXpxkCJhloqvvtDrLtKV0SfCQqI-eIncLGA4dTYlr0VbOhMDjqO-OIz_S3P8Lt99-Oq2QTXylbLAq60Mre-a-SVUP2bHr_rlrdDSoAKV2YY2wypFc2CoqOso" class="kg-image" alt></figure><p>If you head to Shopify Admin -&gt; Discounts you should also see this discount there:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/xuCw_DyNdFFJ80Q3aDw0q7NwV0VhW8rvXNnfCj-ocSrW68xngRqvCSe9i-Mh4Au9XapB8kgQ4oDdrgJHS47xvxyN52JxfN7nvzRvsZvHBdzJ7aKEJRBNUxXB-GTEV_VUTXQnkZT96a8g-s7XT3suk4M" class="kg-image" alt></figure><p>This means your Function is now active and enabled, you should be able to see it in action by heading to the store and adding over 10 products to the cart:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/4zzqU4pMCifBH0RDHwiRCfv-Z55W95XOJBdI33bIkbf6ONByAR5zIZNKwLEpdB6Xkcr-Wwow4PXcIiVma0KsuLwbGoajGG26GDWzCH6TwtHCmZV4oHzTAZqKeA_fTNPhwA8U2-HMWDND7YUXsJHhx7U" class="kg-image" alt></figure><p>You can now make any changes you want to the logic by changing the JS code in the file “extensions/product-discount/src/index.js” and run the command:</p><pre><code>npm run deploy</code></pre><p>This will automatically reflect on the storefront.</p><h2 id="client-friendly-configuration">Client-friendly configuration</h2><p>With Scripts, tech-savvy clients could navigate to the Scripts editor app and manually make code changes to tweak the logic of their Scripts. I make this easier for them by defining configuration at the top of the file inside if objects, e.g. “BULK_DISCOUNTS” and walking them through how to make simple changes. This is no longer possible with Functions in the same way because clients don’t have access to the Functions code via the Shopify admin.</p><p>I’ve figured out a way to enable clients to be able to tweak Functions config in a similar fashion, leveraging shop-level metafields (credit to <a href="https://twitter.com/SammyIsseyegh">Sammy Isseyegh</a> for letting me know about this). To enable this, head to the file “extensions/product-discount/src/index.js” and replace it with this:</p><pre><code class="language-JS">import { DiscountApplicationStrategy } from "../generated/api";

var EMPTY_DISCOUNT = {
  discountApplicationStrategy: DiscountApplicationStrategy.First,
  discounts: [],
};

export default (input) =&gt; {
  var config = input.shop.metafield &amp;&amp; JSON.parse(input.shop.metafield.value);

  if (!config) {
    console.error('No config metafield setup for this discount.');
    return EMPTY_DISCOUNT;
  }

  var targets = input.cart.lines
    .filter((line) =&gt; line.merchandise.__typename == 'ProductVariant')
    .map((line) =&gt; {
      var variant = line.merchandise;

      return {
        productVariant: {
        id: variant.id
      }
    }
  });

  var cartLinesQuantityTotal = input.cart.lines.reduce((total, line) =&gt; {
    total += line.quantity;
    return total;
  }, 0);

  var bulkDiscountActive = config.find((bulkDiscount) =&gt; {
    return cartLinesQuantityTotal &gt;= bulkDiscount.quantity;
  });

  if (!bulkDiscountActive) {
    console.error('No cart lines qualify for this discount.');
    return EMPTY_DISCOUNT;
  }

  return {
    discounts: [
      {
        targets,
        value: {
          percentage: {
            value: bulkDiscountActive.discount
          }
        },
        message: bulkDiscountActive.message
      }
    ],
    discountApplicationStrategy: DiscountApplicationStrategy.First
  };
}
</code></pre><p>This code does exactly the same thing, except the configuration is no longer hard-coded and uses the metafield defined under “shop.metafields.tom-blanchard-functions.config”.</p><p>Next run the command:</p><pre><code>npm run deploy</code></pre><p>This will cause the Function to stop working on your storefront because the config metafield hasn’t been created yet:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/fZqlT1uqWz_45KYerrvt6wbFrXXNCL3m7nnGswLVDiaQdyPMgTTnFX9FnbQhy29BNJPutEA4JsJZQLfzMMVHkTeuNbUM5Mm2vPioR_1XkYydN1nK8o161i5DsWcH89N2FDpKDNHDi2xqihizkUxEfMw" class="kg-image" alt></figure><p>To create this metafield, install your favorite metafield editor app, e.g. <a href="https://apps.shopify.com/metafields-editor-2">Metafields Guru</a>. Once installed, you should see this:</p><figure class="kg-card kg-image-card"><img src="https://lh3.googleusercontent.com/Ruo94MiUmBdGcYKApu72blDnSZ9wflYZCthrYbEzpLiHh2xXeJO2otn5m7MXwNozf7_lJ8-Wkd4q5_w7byjUVJ5SLCRmRZlCQ4UzzEqvdgVwXDIJfQA4XkpO2xLzpB3EmEDyZR7otF038gGMG5qz2yA" class="kg-image" alt></figure><p>Head to “Shop”, click the “Create metafield” button:</p><figure class="kg-card kg-image-card"><img src="https://lh6.googleusercontent.com/zF7eRA2hUsBRhSf0LrCfHw6IR3v4x9-mpf0pXGDcwLcDQTllQPb9G4HsHVDAFWr5FbvxWENHfnQljDXUqu1_BBg4-_TdEzNcQD44VIAIoJ7GpiZxKk_191T8rxsnNfBE52h4dgPanuwAiO18E8g27II" class="kg-image" alt></figure><p>Set “Type” to “JSON string”, set “Namespace” to “tom-blanchard-functions, set “Key” to “config” and set “Value” to the below code:</p><pre><code>[
  {
    "quantity": 10,
    "discount": 20.0,
    "message": "20% off 10 or more"
  },
  {
    "quantity": 40,
    "discount": 30.0,
    "message": "30% off 40 or more"
  }
]</code></pre><p>Now, click the “Save” button and the Function should now work again on the storefront:</p><figure class="kg-card kg-image-card"><img src="https://lh5.googleusercontent.com/PzonlaCVfViPTf6MCqjdXDa34nY5bFwT_RJwk7DnVwEpBfwyGS4o1Xf22l0ElFT2gYurXFKJRGclWuLssMB7C0vB4yLtnmU1b-kihyRiyQduUMioHImCF3E0gCGk-ilYrk7qr5GnLJZ4-sVll8g6PB0" class="kg-image" alt></figure><p>Now, the client will be able to change this metafield whenever they please via the Shopify admin to update config such as quantity amounts and wording etc. See the below example:</p><figure class="kg-card kg-image-card"><img src="https://lh4.googleusercontent.com/hlyBSnl8XUd0plQBC_NVOJUEYi3B7rVS2NyDxgSMT_p9X-gNbbMeGzudPg7ZwNSEU4CBveT_yaz2B5D9ei3XlSIG4xsV-9Xzckj2tolQOG6gx8kHo98O1wcgIUSipa7gjSSh5XghEOf1kMquC8XMtoM" class="kg-image" alt></figure><p>I’ve added “hello world!!” to the discount message and it’s now automatically reflected on the storefront with no need to re-deploy the entire app.</p><figure class="kg-card kg-image-card"><img src="https://lh5.googleusercontent.com/waWZYe35tlQSg_zIHnggMgCTDK3Sv5zZFsyICw_h3FwfdisI9D3i-1_jjouYgWhVJXCQHxWds2sctf4BmFCz-PeamDxKx3MhVKCH4oKjbfYkxOW0xvfPC1JJzKaj_4cZIR2hiVwYLLoVV5GIa49ctgU" class="kg-image" alt></figure><h2 id="parting-thoughts">Parting thoughts</h2><p>This seems like <em>a lot</em> of steps to reproduce the functionality we get with the more easy to use Scripts approach but I like the fact that I can use my local IDE for development instead of writing code in the browser editor. You could also place this app inside a Git repository and use source control to keep track of changes which you can't do with Scripts without manually copy/pasting every time you make changes.</p><p>One thing I haven't covered in this guide to keep it concise is the development preview feature that Functions gives us via the <a href="https://shopify.dev/docs/apps/tools/cli/commands#dev">"npm run dev"</a> command. This allows us to make changes to the Function code and it'll automatically be rebuilt and pushed to Shopify so you can immediately test your changes and iterate quickly before finally deploying.</p><p>Shopify Functions are still a work-in-progress and are constantly being improved on so I imagine the amount of steps taken to get setup will reduce over time. A developer at Shopify let me know that they're specifically looking to eliminate the GraphiQL step for extension-only apps so keep an eye out for that.</p><p>We don’t quite have feature-parity with Shopify Scripts at this time of writing but once they do, I think they’ll be a game changer and make Scripts look very inferior. <a href="https://help.shopify.com/en/manual/checkout-settings/script-editor/migrating">See this article</a> for more details about the limitations of Functions.</p>]]></content:encoded></item><item><title><![CDATA[Shopify International Pricing: Initial findings]]></title><description><![CDATA[<p>If you've worked on a Shopify store which needed to support multiple currencies, you'll of probably realised that there's only so far you can go with the platform. My main gripe with the multi-currency feature is that it didn't allow merchants to manually set product prices depending on currency being</p>]]></description><link>https://tomblanchard.co.uk/shopify-international-pricing-initial-findings/</link><guid isPermaLink="false">607864e9e4aa40041f7053f2</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Fri, 16 Apr 2021 17:49:32 GMT</pubDate><content:encoded><![CDATA[<p>If you've worked on a Shopify store which needed to support multiple currencies, you'll of probably realised that there's only so far you can go with the platform. My main gripe with the multi-currency feature is that it didn't allow merchants to manually set product prices depending on currency being being used by customers, you had to rely on an automatic exchange rate and that was about it.</p><p>The limitations with multi-currency left us with a few options, none of them were great:</p><ul><li>Create currency-specific duplicates of each product, e.g. "T-shirt - GBP", "T-shirt - EUR", set a different price and code logic into the theme which includes/excludes products based on active currency</li><li>Create a different Shopify store instance for every currency and set different product prices per-store</li></ul><p>In a nutshell, these solutions are brittle, hard to maintain and/or overly expensive.</p><p>Shopify changed this last month by launching a <a href="https://changelog.shopify.com/posts/control-pricing-by-region-with-international-pricing">new feature</a> which allows merchants to manually control product/variant prices per-country/region, it's not perfect, but it's a game-changer in my opinion. Here's a little video of it in action:</p><!--kg-card-begin: html--><iframe frameborder="0" scrolling="no" marginheight="0" marginwidth="0" width="788.54" height="443" type="text/html" src="https://www.youtube.com/embed/OZOrB-0Ffmw?autoplay=0&fs=0&iv_load_policy=3&showinfo=0&rel=0&cc_load_policy=0&start=0&end=0&origin=https://youtubeembedcode.com"><div><small><a href="https://youtubeembedcode.com/es/">youtubeembedcode es</a></small></div><div><small><a href="https://googlemapsgenerator.com">Google maps embed</a></small></div></iframe><!--kg-card-end: html--><p>Notice how the price is set to £20.00 for GBP users and €100 for EUR users - awesome!</p><p>Now, let's look into how these prices are set in the Shopify admin, spoiler alert: it's not great. Here's the basic workflow (this assumes that you've setup Shopify Payments and added at least one other country/region which uses a different currency to your shops base currency):</p><ul><li>Head to <a href="https://shopify.com/admin/products">Shopify Admin -&gt; Products</a></li><li>Select the products you wish to change the prices of</li><li>Export them as CSV</li><li>Open the CSV in your favourite <a href="http://sheets.new/">spreadsheet editor</a></li><li>Scroll all the way to the last column and for each country/region you've added, there's two new columns titled "Price / COUNTRY" and "Compare At Price / COUNTRY" where "COUNTRY" is the actual country/region. I've setup my Shopify Payments to include "United Kingdom (GBP)" and "Germany (EUR)" so I have the columns "Price / DE" and "Compare At Price / DE"</li><li>Populate the product/variant prices as you wish and save the CSV file</li><li>Go back to <a href="https://shopify.com/admin/products">Shopify Admin -&gt; Products</a> and import the CSV file making sure the "Overwrite any current products that have the same handle." checkbox is checked</li><li>Confirm the prices have been successfully set by visiting your store, selecting a different currency and viewing the products</li></ul><p>I don't know about you, but that seems like a pretty cumbersome process to me! It would have been much better if Shopify just extended the variant edit pages to include a nice UI for merchants to input country/region-specific prices.</p><p>Here's an example of what the CSV file looks for a product which has variants and a country-specific price set for each variant. The regular price for each variant is £20 but when the customer is viewing the store with the Euro currency, they'll see €100 for variant "S", €200 for variant "M" and €300 for variant "L":</p><figure class="kg-card kg-image-card"><img src="https://tomblanchard.co.uk/content/images/2021/04/image-4.png" class="kg-image" alt srcset="https://tomblanchard.co.uk/content/images/size/w600/2021/04/image-4.png 600w, https://tomblanchard.co.uk/content/images/size/w1000/2021/04/image-4.png 1000w, https://tomblanchard.co.uk/content/images/size/w1600/2021/04/image-4.png 1600w, https://tomblanchard.co.uk/content/images/size/w2400/2021/04/image-4.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>It's worth noting that this functionality <em>requires</em> you to use Shopify Payments and the Checkout will persist whichever currency the user selects - see more about the requirements/limitations of this feature in the <a href="https://help.shopify.com/en/manual/payments/shopify-payments/multi-currency/international-pricing#requirements-for-international-pricing-features">Shopify documentation</a>.</p><p>While the UX of inputting the country/region-specific product prices isn't ideal, I think this feature is well overdue and it's promising to see it finally land. There's also a <a href="https://shopify.dev/tutorials/support-different-pricing-models-with-the-price-list-api#update-a-partial-set-of-prices-on-a-price-list">GraphQL API mutation "priceListFixedPricesAdd</a>" which can do this programmatically so I imagine third-party app developers will be keen to monopolise on this and merchants will have to rely on apps to manage product prices. It seems annoying to have to pay for an app to do this but it's a lot better than importing via CSV every time!</p><p>Another feature which landed last month includes being able to use percentage price adjustments to increase/decrease product prices globally based on the customers active currency. This is done via <a href="https://shopify.com/admin/settings/payments">Shopify Admin -&gt; Settings -&gt; Payments</a> -&gt; Shopify Payments -&gt; Countries/regions -&gt; Edit:</p><figure class="kg-card kg-image-card"><img src="https://tomblanchard.co.uk/content/images/2021/04/image-5.png" class="kg-image" alt srcset="https://tomblanchard.co.uk/content/images/size/w600/2021/04/image-5.png 600w, https://tomblanchard.co.uk/content/images/size/w1000/2021/04/image-5.png 1000w, https://tomblanchard.co.uk/content/images/size/w1600/2021/04/image-5.png 1600w, https://tomblanchard.co.uk/content/images/size/w2400/2021/04/image-5.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>From here you can globally increase/decrease all product prices for this specific country via inputting a percentage into the "Price adjustment" field (value has to be between -99% and 999%). Pretty cool - I've never needed this functionality in the past but it's good to know it now exists!</p><p>These latest changes to the Shopify multi-currency feature certainly bridges the gaps and the need to use the dreaded multi-store approach is getting smaller. My only wish now is the ability to have country/region-specific products/collections, probably a pipe dream but we'll see.</p>]]></content:encoded></item><item><title><![CDATA[Taking design control of Shopify Policies]]></title><description><![CDATA[<p><a href="https://help.shopify.com/en/manual/checkout-settings/refund-privacy-tos">Shopify Policies</a> are a fairly new feature within Shopify - they're simple, text-based pages which live under the path "shop.com/policies/". At this this time of writing, there's five Policies you can choose to use:</p><ul><li>Refund policy - shop.com/policies/refund-policy</li><li>Privacy policy - shop.com/policies/privacy-policy</li></ul>]]></description><link>https://tomblanchard.co.uk/taking-design-control-of-shopify-policies/</link><guid isPermaLink="false">6076ebbbe4aa40041f705326</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Wed, 14 Apr 2021 14:03:20 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://help.shopify.com/en/manual/checkout-settings/refund-privacy-tos">Shopify Policies</a> are a fairly new feature within Shopify - they're simple, text-based pages which live under the path "shop.com/policies/". At this this time of writing, there's five Policies you can choose to use:</p><ul><li>Refund policy - shop.com/policies/refund-policy</li><li>Privacy policy - shop.com/policies/privacy-policy</li><li>Terms of service - shop.com/policies/terms-of-service</li><li>Shipping policy - shop.com/policies/shipping-policy</li><li>Subscription policy - shop.com/policies/subscription-policy</li></ul><p>They're setup via Shopify Admin -&gt; Settings -&gt; Legal:</p><figure class="kg-card kg-image-card"><img src="https://tomblanchard.co.uk/content/images/2021/04/image-1.png" class="kg-image" alt srcset="https://tomblanchard.co.uk/content/images/size/w600/2021/04/image-1.png 600w, https://tomblanchard.co.uk/content/images/size/w1000/2021/04/image-1.png 1000w, https://tomblanchard.co.uk/content/images/2021/04/image-1.png 1424w" sizes="(min-width: 720px) 720px"></figure><p>By default, a Policy looks something like this on the front-end:</p><figure class="kg-card kg-image-card"><img src="https://tomblanchard.co.uk/content/images/2021/04/image-2.png" class="kg-image" alt srcset="https://tomblanchard.co.uk/content/images/size/w600/2021/04/image-2.png 600w, https://tomblanchard.co.uk/content/images/size/w1000/2021/04/image-2.png 1000w, https://tomblanchard.co.uk/content/images/2021/04/image-2.png 1424w" sizes="(min-width: 720px) 720px"></figure><p>It doesn't look terrible, but I recently worked on a project where the designer wanted these pages to be more interesting with a different layout, images and other varied content. Shopify doesn't give you much creative freedom with Policies, all you have access to is a rich text editor for the content and you can use the theme CSS file to style the Policy pages - there's only so far you can go with this.</p><p>My first thought was to just stop using Policies to display this content and revert to using Pages, this way I can create a custom page template for each policy and make use of Sections as the content-input mechanism to code the page however I please. It turns out this isn't feasible if the store uses the Google channel because the Google Merchant Centre requirements state that you <em>must</em> use actual Policies and not regular Pages for this content.</p><p>With some Liquid trickery in the store theme, I was able to achieve the best of both worlds with keeping the Policy URL structure the same while using a custom template to code up each Policy page to look however I please, with theme Sections as the content-input mechanism.</p><p>It all starts within theme file "/layout/theme.liquid", look for this line:</p><pre><code class="language-Liquid">{{ content_for_layout }}</code></pre><p>To render a custom Section for the Refund Policy instead of the default Policy content, I changed it to this:</p><pre><code class="language-Liquid">{% if request.path == '/policies/refund-policy' %}
  {% section 'refund-policy' %}
{% else %}
  {{ content_for_layout }}
{% endif %}</code></pre><p>With this in place, I'm able to format the "refund-policy" Section however I please and ended up with this design:</p><figure class="kg-card kg-image-card"><img src="https://tomblanchard.co.uk/content/images/2021/04/image-3.png" class="kg-image" alt srcset="https://tomblanchard.co.uk/content/images/size/w600/2021/04/image-3.png 600w, https://tomblanchard.co.uk/content/images/size/w1000/2021/04/image-3.png 1000w, https://tomblanchard.co.uk/content/images/2021/04/image-3.png 1424w" sizes="(min-width: 720px) 720px"></figure><p>Notice how the URL path is still "/policies/refund-policy" and the page content is structured via a custom design.</p><p>If you want to add other custom Sections to other Policies, tweak the above code to something like this:</p><pre><code>{% if request.path == '/policies/refund-policy' %}
  {% section 'refund-policy' %}
{% elsif request.path contains '/policies/privacy-policy' %}
  {% section 'privacy-policy' %}
{% elsif request.path contains '/policies/terms-of-service' %}
  {% section 'terms-of-service' %}
{% elsif request.path contains '/policies/shipping-policy' %}
  {% section 'shipping-policy' %}
{% elsif request.path contains '/policies/subscription-policy' %}
  {% section 'subscription-policy' %}
{% else %}
  {{ content_for_layout }}
{% endif %}</code></pre><p>This will render a custom Section for every Policy setup in the store.</p><p>This seems like a convoluted solution but I don't think there's any other way to achieve this for now - as always, it's all fun and games with Shopify development!</p>]]></content:encoded></item><item><title><![CDATA[Bulk-downloading Shopify files]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><em><strong>Update (4 Oct 2022) - This script was broken for a while as Shopify made some updates to the admin but I finally found some time to fix it so enjoy it once again! 😎</strong></em></p>
<p>Shopify comes with a pretty good file storage solution, <a href="https://help.shopify.com/en/manual/sell-online/online-store/file-uploads">as documented on their help center</a>, you</p>]]></description><link>https://tomblanchard.co.uk/bulk-downloading-your-shopify-files/</link><guid isPermaLink="false">5fb15d2826ee371bd804670c</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Tue, 19 Feb 2019 13:20:44 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><em><strong>Update (4 Oct 2022) - This script was broken for a while as Shopify made some updates to the admin but I finally found some time to fix it so enjoy it once again! 😎</strong></em></p>
<p>Shopify comes with a pretty good file storage solution, <a href="https://help.shopify.com/en/manual/sell-online/online-store/file-uploads">as documented on their help center</a>, you can upload, manage, and delete files from the Files page in Shopify. All files uploaded to Shopify end up here, this means all product images, collections images and images uploaded via Theme/Section settings.</p>
<p>You can upload many files at once using the uploader, you can delete multiple files using the bulk actions toolbar but one very strange quirk is that you cannot bulk-download these files. This is a huge problem when migrating from one Shopify store to another, think moving from a development shop to the merchant's production shop.</p>
<p>I figured out a way to bulk-download these files, it involves scraping the client-side admin UI code via the browser development tools Javascript console then using a Node.js script to download the files to your local machine.</p>
<p>If you need to do this, start off by making sure you've got Node.js installed on your machine, <a href="https://nodejs.org/en/">download and run the installer from the Node.js website</a> - opt for the version which they state &quot;Recommended For Most Users&quot;.</p>
<p>Next, open a Terminal window, if you're not sure how to do that, <a href="https://www.wikihow.com/Open-a-Terminal-Window-in-Mac">see this tutorial</a>.</p>
<p>Now, <a href="https://github.com/tomblanchard/shopify-bulk-file-downloader/archive/master.zip">click this link</a> to download the Shopify Bulk File Downloader, save it to your desktop and unzip it.</p>
<p>Open your Terminal window and run the command <code>cd ~/Desktop/shopify-bulk-file-downloader-master</code>, then run the command <code>npm i</code>.</p>
<p>Next up, <a href="https://raw.githubusercontent.com/tomblanchard/shopify-bulk-file-downloader/master/client.min.js">click this link</a>, select all the code and copy it to your clipboard, with that code copied head over to the Files page of your Shopify acccount, that looks like this:</p>
<p><a href="https://shop.myshopify.com/admin/settings/files">https://shop.myshopify.com/admin/settings/files</a></p>
<p>With this page open, open the Chrome Developer Tools (View -&gt; Developer -&gt; Development Tools) then click the &quot;Console&quot; tab, paste the code copied to your clipboard in here and press the Enter key - eventually you'll see an alert which states &quot;File URLS have been copied to your clipboard!&quot;.</p>
<p>Now head over to your Desktop directory in Finder and open the file <code>file-urls.json</code>, you can open this on any text-editing app (TextEdit, Notepad, etc.) and paste the results into this file, save and close it.</p>
<p>Go back to your Terminal window and run the command <code>node server</code>, a new directory will be created in the <code>shopify-bulk-file-downloader-master</code> directory called &quot;files&quot;, all files will be downloaded there and you'll see a &quot;Success!&quot; message in the Terminal when finished.</p>
<p>Enjoy. :)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Handling lazy-loaded image reflows in Shopify themes]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><a href="https://css-tricks.com/the-complete-guide-to-lazy-loading-images/">Lazy loading images</a> is a pretty common practice nowadays, why load all images on each page if you can't garuantee your users will actually see them? To prevent your users from downloading every image, you can instead load all images which are visible/close to being visible within the users</p>]]></description><link>https://tomblanchard.co.uk/handling-lazy-loaded-image-reflows-in-shopify-themes/</link><guid isPermaLink="false">5fb15d2826ee371bd804670b</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Fri, 30 Nov 2018 11:10:43 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><a href="https://css-tricks.com/the-complete-guide-to-lazy-loading-images/">Lazy loading images</a> is a pretty common practice nowadays, why load all images on each page if you can't garuantee your users will actually see them? To prevent your users from downloading every image, you can instead load all images which are visible/close to being visible within the users browser viewport as they scroll.</p>
<p>There's a <a href="https://github.com/search?q=lazy+load">ton of libraries</a> in the wild which implement lazy loading functionality, I'd reccomend to do some research and choose one that fits your needs or even roll your own. The only assumption I'll make here is that your markup for a lazy-loaded image looks something like this:</p>
<pre><code>&lt;img data-src=&quot;//domain.com/image.jpg&quot; alt=&quot;&quot; /&gt;
</code></pre>
<p>This image will only load when it's visible/close to being visible within the users browser viewport.</p>
<p>Here's a screenshot of the page we'll be building:</p>
<p><img src="https://tomblanchard.co.uk/content/images/2018/11/1-copy.jpg" alt="1-copy"></p>
<p>The content will be fed from a Section:</p>
<pre><code>{% schema %}
  {
    &quot;name&quot;: &quot;About&quot;,
    &quot;settings&quot;: [
      {
        &quot;type&quot;: &quot;text&quot;,
        &quot;id&quot;: &quot;title&quot;,
        &quot;label&quot;: &quot;Title&quot;
      },
      {
        &quot;type&quot;: &quot;image_picker&quot;,
        &quot;id&quot;: &quot;image&quot;,
        &quot;label&quot;: &quot;Image&quot;
      },
      {
        &quot;type&quot;: &quot;richtext&quot;,
        &quot;id&quot;: &quot;copy&quot;,
        &quot;label&quot;: &quot;Copy&quot;
      }
    ]
  }
{% endschema %}

{% assign title = section.settings.title %}

{% assign image = section.settings.image %}
{% assign image_url = image | img_url: '2000x' %}

{% assign copy = section.settings.copy %}

&lt;h1&gt;
  {{ title }}
&lt;/h1&gt;

&lt;img data-src=&quot;{{ image_url }}&quot; alt=&quot;&quot; /&gt;

&lt;div class=&quot;rte&quot;&gt;
  {{ copy }}
&lt;/div&gt;
</code></pre>
<p>Here's a video of this page loading on a fast 3G connection:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/MQ7vyoZKhO0?rel=0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>Notice how initially, the copy text is visible, then once the image has loaded, the content reflows and the text gets pushed out of view underneath the image. This happens due to the browser having no knowledge of the image prior to it loading, including the image width and height.</p>
<p>We're working with a <a href="https://help.shopify.com/en/themes/liquid/objects/image">Shopify &quot;image&quot; object</a> here which provides extra info about the image, including it's width and height; if we have access to this info prior to downloading the image in the browser, we can work out it's aspect ratio like so:</p>
<pre><code>{% assign image_box_ratio = image.height | append: &quot;.0&quot; | times: 1 | divided_by: image.width | times: 100 | append: &quot;%&quot; %}
</code></pre>
<p>From there, you can wrap the image in a container which uses this aspect ratio:</p>
<pre><code>&lt;div style=&quot;--box-ratio: {{ image_box_ratio }};&quot;&gt;
  &lt;img data-src=&quot;{{ image_url }}&quot; alt=&quot;&quot; /&gt;
&lt;/div&gt;
</code></pre>
<p>Sprinkle in some CSS:</p>
<pre><code>[style*=&quot;--box-ratio:&quot;] {
  height: 0;
  padding-bottom: var(--box-ratio);
  background: #F8F8F8;
}
</code></pre>
<p>The Section will look like this:</p>
<pre><code>{% schema %}
  {
    &quot;name&quot;: &quot;About&quot;,
    &quot;settings&quot;: [
      {
        &quot;type&quot;: &quot;text&quot;,
        &quot;id&quot;: &quot;title&quot;,
        &quot;label&quot;: &quot;Title&quot;
      },
      {
        &quot;type&quot;: &quot;image_picker&quot;,
        &quot;id&quot;: &quot;image&quot;,
        &quot;label&quot;: &quot;Image&quot;
      },
      {
        &quot;type&quot;: &quot;richtext&quot;,
        &quot;id&quot;: &quot;copy&quot;,
        &quot;label&quot;: &quot;Copy&quot;
      }
    ]
  }
{% endschema %}

{% assign title = section.settings.title %}

{% assign image = section.settings.image %}
{% assign image_url = image | img_url: '2000x' %}
{% assign image_box_ratio = image.height | append: &quot;.0&quot; | times: 1 | divided_by: image.width | times: 100 | append: &quot;%&quot; %}

{% assign copy = section.settings.copy %}

&lt;h1&gt;
  {{ title }}
&lt;/h1&gt;

&lt;div style=&quot;--box-ratio: {{ image_box_ratio }};&quot;&gt;
  &lt;img data-src=&quot;{{ image_url }}&quot; alt=&quot;&quot; /&gt;
&lt;/div&gt;

&lt;div class=&quot;rte&quot;&gt;
  {{ copy }}
&lt;/div&gt;
</code></pre>
<p>With that in place, the loading experience has dramatically improved:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/51TrfWc63OM?rel=0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>Notice how the image is displayed as an empty placeholder (grey) box before the image is actually downloaded, once the image has loaded, there's no content jump because the grey box uses the same aspect ratio as the image itself.</p>
<p>I use this method throughout all Shopify sites I build and work on, it's such a huge performance boost with very minimal effort.</p>
<h2 id="whataboutimageswhicharentimageobjects">What about images which aren't &quot;image&quot; objects?</h2>
<p>It's quite common to have theme references to image URLs instead of &quot;image&quot; objects when you use metafields to store data via a metafield editor app. Depending on the app, this method is still possible with a bit of tweaking - I make use out of <a href="https://apps.shopify.com/accentuate">Accentuate Custom Fields</a> when I need resources to have non-global section data (e.g. each collection needs a different hero image).</p>
<p>While Accentuate supports an &quot;image&quot; field type, it's impossible for it to return an actual &quot;image&quot; object due to the limiations of metafields; it just returns an image URL:</p>
<pre><code>{{ collection.metafields.accentuate.hero_image }}
</code></pre>
<p>Will return an image URL like:</p>
<pre><code>https://cdn.accentuate.io/69262540870/4651314610246/image.jpg?2000×1334
</code></pre>
<p>This URL contains the image width and height in the query paramters; we can take advantage of this to calculate it's aspect ratio like this:</p>
<pre><code>{% assign image_url =  collection.metafields.accentuate.hero_image %}
{% assign image_url_width = image_url | split: &quot;?&quot; | last | split: &quot;x&quot; | first | times: 1 %}
{% assign image_url_height = image_url | split: &quot;?&quot; | last | split: &quot;x&quot; | last | times: 1 %}
{% assign image_url_box_ratio = image_url_height | append: &quot;.0&quot; | times: 1 | divided_by: image_url_width | times: 100 | append: &quot;%&quot; %}
</code></pre>
<p>We can then use it like this:</p>
<pre><code>&lt;div style=&quot;--box-ratio: {{ image_url_box_ratio }};&quot;&gt;
  &lt;img data-src=&quot;{{ image_url }}&quot; alt=&quot;&quot; /&gt;
&lt;/div&gt;
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Storing instances in Express sessions]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>When developing apps with Express, at some point you'll probably need to store user data across different requests, that's where sessions come in. I was using <a href="https://github.com/expressjs/session">express-session</a> when I came across an issue, I needed to store <a href="https://github.com/MONEI/Shopify-api-node">Shopify-api-node</a> instances across sessions.</p>
<p>This is how the app was structured at first:</p>]]></description><link>https://tomblanchard.co.uk/storing-instances-in-express-sessions/</link><guid isPermaLink="false">5fb15d2826ee371bd804670a</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Mon, 11 Jun 2018 15:38:03 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>When developing apps with Express, at some point you'll probably need to store user data across different requests, that's where sessions come in. I was using <a href="https://github.com/expressjs/session">express-session</a> when I came across an issue, I needed to store <a href="https://github.com/MONEI/Shopify-api-node">Shopify-api-node</a> instances across sessions.</p>
<p>This is how the app was structured at first:</p>
<pre><code class="language-javascript">// entry-index.js

var express = require(&quot;express&quot;);
var session = require(&quot;express-session&quot;);
var RedisStore = require(&quot;connect-redis&quot;)(session);

var middlewareShopify = require(&quot;./middleware-shopify&quot;);
var routerRoutes = require(&quot;./router-routes&quot;);

var app = express();
var appPort = process.env.PORT || 3000;

app.use(session({
  store: new RedisStore({ host: &quot;localhost&quot;, port: 6379, ttl: 260 }),
  secret: &quot;gu2cQL7z2z4pKypm&quot;,
  resave: false,
  saveUninitialized: true
}));

app.use(middlewareShopify);
app.use(routerRoutes);

app.listen(appPort, () =&gt; {
  console.log(`App listening on port ${appPort}!`);
});
</code></pre>
<pre><code class="language-javascript">// middleware-shopify.js

module.exports = (req, res, next) =&gt; {
  if( !req.session.shopName || !req.session.apiKey || !req.session.password ) {
    req.session.shopName = req.query.shopName;
    req.session.apiKey = req.query.apiKey;
    req.session.password = req.query.password;
  }

  var { shopName, apiKey, password }  = req.session;

  if( !shopName || !apiKey || !password ) {
    return res.status(500).send(&quot;Something broke.&quot;);
  }

  return next();
};
</code></pre>
<pre><code class="language-javascript">// routes-routes.js

var express = require(&quot;express&quot;);

var Shopify = require(&quot;shopify-api-node&quot;);

var router = express.Router();

router.get(&quot;/&quot;, (req, res) =&gt; {
  var { shopName, apiKey, password }  = req.session;

  var shopify = new Shopify({
    shopName,
    apiKey,
    password,
    autoLimit: true
  });

  shopify.shop.get()
    .then((shop) =&gt; {
      return res.send(JSON.stringify(shop));
    })
    .catch((err) =&gt; {
      return res.status(500).send(&quot;Something broke.&quot;);
    });
});

router.get(&quot;/other-route&quot;, (req, res) =&gt; {
  var { shopName, apiKey, password }  = req.session;

  var shopify = new Shopify({
    shopName,
    apiKey,
    password,
    autoLimit: true
  });

  shopify.shop.get()
    .then((shop) =&gt; {
      return res.send(JSON.stringify(shop));
    })
    .catch(() =&gt; {
      return res.status(500).send(&quot;Something broke.&quot;);
    });
});

module.exports = router;
</code></pre>
<p>This works like so:</p>
<ol>
<li>Visit the URL: <a href="https://localhost:3000">https://localhost:3000</a> and you'll get an error due to the lack of required query parameters (<code>shopName</code>, <code>apiKey</code> and <code>password</code>).</li>
<li>Visit a URL like: <a href="http://localhost:3000/?shopName=your-shop-name&amp;apiKey=your-api-key&amp;password=your-app-password">http://localhost:3000/?shopName=your-shop-name&amp;apiKey=your-api-key&amp;password=your-app-password</a> and you'll see data displayed from an API call to the <code>your-shop-name</code> shop.</li>
<li>The query parameters are now saved to your session, so you can now visit routes without the query parameters and it'll still work.</li>
</ol>
<p>This is great and <em>almost</em> works exactly how I want, but there's one issue; every route defines a new <code>Shopify</code> instance. If you take a look at the <a href="https://github.com/MONEI/Shopify-api-node">Shopify-api-node</a> documentation, you'll see it provides a <code>autoLimit</code> option, this is a simple way around hitting the <a href="https://help.shopify.com/api/getting-started/api-call-limit">Shopify API call limit</a>. The only catch is that for <code>autoLimit</code> to work properly, there needs to be just <em>one</em> <code>Shopify</code> instance per shop.</p>
<p>My first thought was to setup the <code>Shopify</code> instance in the <code>middleware-shopify</code> middleware, like this:</p>
<pre><code class="language-javascript">// middleware-shopify.js

var Shopify = require(&quot;shopify-api-node&quot;);

module.exports = (req, res, next) =&gt; {
  if( !req.session.shopName || !req.session.apiKey || !req.session.password ) {
    req.session.shopName = req.query.shopName;
    req.session.apiKey = req.query.apiKey;
    req.session.password = req.query.password;
  }

  var { shopName, apiKey, password }  = req.session;

  if( !shopName || !apiKey || !password ) {
    return res.status(500).send(&quot;Something broke.&quot;);
  }

  if( !req.session.shopify ) {
    req.session.shopify = new Shopify({
      shopName,
      apiKey,
      password,
      autoLimit: true
    });
  }

  return next();
};
</code></pre>
<p>Then in my <code>router-routes.js</code> router, I could use it like this:</p>
<pre><code class="language-javascript">router.get(&quot;/&quot;, (req, res) =&gt; {
  var { shopify }  = req.session;

  shopify.shop.get()
    .then((shop) =&gt; {
      return res.send(JSON.stringify(shop));
    })
    .catch((err) =&gt; {
      return res.status(500).send(&quot;Something broke.&quot;);
    });
});
</code></pre>
<p>This doesn't work because you can only store JSON-serializable data in  <code>req.session</code>, instances can't be serialized. What I ended up doing was creating a session/<code>Shopify</code> instance map, each session gets it's own <code>Shopify</code> instance, identified via the <code>session.id</code>. I started off by creating a new <code>object-shopifys.js</code> file which exports an empty object:</p>
<pre><code>// object-shopifys.js

module.exports = {};
</code></pre>
<p>I then changed the <code>middleware-shopify</code> middleware, like this:</p>
<pre><code class="language-javascript">// middleware-shopify.js

var Shopify = require(&quot;shopify-api-node&quot;);

var shopifys = require(&quot;./object-shopifys&quot;);

module.exports = (req, res, next) =&gt; {
  if( !req.session.shopName || !req.session.apiKey || !req.session.password ) {
    req.session.shopName = req.query.shopName;
    req.session.apiKey = req.query.apiKey;
    req.session.password = req.query.password;
  }

  var { shopName, apiKey, password }  = req.session;

  if( !shopName || !apiKey || !password ) {
    return res.status(500).send(&quot;Something broke.&quot;);
  }

  if( !shopifys[req.session.id] ) {
    shopifys[req.session.id] = new Shopify({
      shopName,
      apiKey,
      password,
      autoLimit: true
    });
  }

  return next();
};
</code></pre>
<p>By default, the <code>shopifys</code> object is empty, but after the middleware has executed for the first time of the users session, it'll look like:</p>
<pre><code class="language-javascript">// `Shopify...` refers to the actual instance, I left it out
// due to brevity.
{
  &quot;Xur_jku_V-pUfwu3tuNtAqEydp8CTxbK&quot;: Shopify...
}
</code></pre>
<p>I then changed the router to reference the new <code>shopifys</code> object:</p>
<pre><code class="language-javascript">// router-routes.js

var express = require(&quot;express&quot;);

var shopifys = require(&quot;./object-shopifys&quot;);

var router = express.Router();

router.get(&quot;/&quot;, (req, res) =&gt; {
  var shopify = shopifys[req.session.id];

  shopify.shop.get()
    .then((shop) =&gt; {
      return res.send(JSON.stringify(shop));
    })
    .catch((err) =&gt; {
      return res.status(500).send(&quot;Something broke.&quot;);
    });
});

router.get(&quot;/other-route&quot;, (req, res) =&gt; {
  var shopify = shopifys[req.session.id];

  shopify.shop.get()
    .then((shop) =&gt; {
      return res.send(JSON.stringify(shop));
    })
    .catch(() =&gt; {
      return res.status(500).send(&quot;Something broke.&quot;);
    });
});

module.exports = router;
</code></pre>
<p>There we go, each session gets one <code>Shopify</code> instance and we don't have to worry about hitting the API call limit if we navigate to multiple API-heavy routes at the same time.</p>
<p>One thing we do have to worry about is a potential memory leak. Over time the <code>shopifys</code> object will build up and get larger. A simple solution would be to setup a cron-job which periodically checks the session IDs in the <code>shopifys</code> map and compares them to the redis <code>sess:*</code> keys, if the key no longer exists in redis, then remove it from the <code>shopifys</code> map.</p>
<p>I'd like to thank <a href="https://github.com/lpinca">Luigi Pinca</a> (the main contributer of the <a href="https://github.com/MONEI/Shopify-api-node">Shopify-api-node module</a>) for walking me through this on a <a href="https://github.com/MONEI/Shopify-api-node/issues/191">GitHub issue</a> I opened.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How I built Shopify Liquid REPL]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I recently spent a couple of days building a tool in the form of a small web app <a href="https://shopify-liquid-repl.tomblanchard.co.uk/">Shopify Liquid REPL</a>, it's similiar to the <a href="https://babeljs.io/repl/">Bable REPL</a>, but it takes Shopify Liquid (different to <a href="https://github.com/Shopify/liquid">vanilla Liquid</a>) and outputs the compiled code.</p>
<p>I built this because I was tired of spinning</p>]]></description><link>https://tomblanchard.co.uk/how-i-built-shopify-liquid-repl/</link><guid isPermaLink="false">5fb15d2826ee371bd8046709</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Mon, 21 May 2018 12:42:29 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I recently spent a couple of days building a tool in the form of a small web app <a href="https://shopify-liquid-repl.tomblanchard.co.uk/">Shopify Liquid REPL</a>, it's similiar to the <a href="https://babeljs.io/repl/">Bable REPL</a>, but it takes Shopify Liquid (different to <a href="https://github.com/Shopify/liquid">vanilla Liquid</a>) and outputs the compiled code.</p>
<p>I built this because I was tired of spinning up a new development shop/theme every time I wanted to simply test out the capabilities of Liquid, I used to rely on another tool named <a href="https://droppen.org/">DropPen</a> for this, but that seems to be broken nowadays.</p>
<p>I originally planned on using <a href="https://github.com/Shopify/liquid">Shopify/liquid</a> (what I call &quot;vanilla Liquid&quot;) on my own server and handling the compiling through that, but there's quite a lot of differences between vanilla Liquid and the Liquid used on the actual Shopify servers. As stated <a href="https://github.com/Shopify/slate/wiki/Local-development">here</a>, Shopify claim:</p>
<blockquote>
<p>Shopify Themes use <a href="https://help.shopify.com/themes/liquid">an extended version of Liquid</a> with a variety of additional objects, tags, and filters</p>
</blockquote>
<p>For my REPL to function properly, the input Liquid needs to be run through the actual Shopify servers, luckily this isn't <em>too</em> hard when you combine usage of the <a href="https://help.shopify.com/api/reference">REST Admin API</a> with <a href="https://ecommerce.shopify.com/c/ecommerce-design/t/alternate-index-template-162420/#comment-162427">the ability to access alternative theme templates via a <code>view</code> query paramater</a>. The mechanism behind this works like this:</p>
<ul>
<li>Get Liquid input from the form POST data</li>
<li>Upload the Liquid input as a new alternate template via the <a href="https://help.shopify.com/api/reference/online_store/asset">Asset API</a>, e.g. <code>/templates/index.1nfn38hgj38g.liquid</code></li>
<li>Request the shop with the new alternate tempalte active, e.g. <code>https://shop-name.myshopify.com/?view=1nfn38hgj38g</code></li>
<li>Return the content</li>
<li>Delete the alternate template from the theme</li>
</ul>
<p>My Express route for this looks like:</p>
<pre><code class="language-javascript">app.post(&quot;/&quot;, (req, res) =&gt; {
  // Get Liquid input from the form POST data
  var input = `{% layout none %}${req.body.input}`;

  // https://github.com/klughammer/node-randomstring
  var template_suffix = randomstring.generate({
    length: 12,
    charset: &quot;alphabetic&quot;
  });

  var asset_key = `templates/index.${template_suffix}.liquid`;

  // Upload the Liquid input as a new alternate template
  shopify.asset.create(shop_theme_id, {
    &quot;key&quot;: asset_key,
    &quot;value&quot;: input
  })
    .then(() =&gt; {
      // Request the shop with the new alternate tempalte active
      return request({
        url: `${shop_url}/?view=${template_suffix}`,
        headers: { &quot;User-Agent&quot;: &quot;Mozilla&quot; }
      });
    })
    .then((template_res) =&gt; {
      // Delete the alternate template from the theme
      shopify.asset.delete(shop_theme_id, {
        asset: {
          &quot;key&quot;: asset_key
        }
      });

      // Return the content
      return res.send(template_res);
    });
});
</code></pre>
<p>Now on the front-end we just need a form like:</p>
<pre><code class="language-markup">&lt;form method=&quot;post&quot; action=&quot;/&quot;&gt;
  &lt;textarea name=&quot;input&quot;&gt;&lt;/textarea&gt;
  &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>That'll hit the Express route and give us the compiled Liquid output. I wanted a split-screen editor so I do this via AJAX with an input field to the left and the output field to the right.</p>
<p>In the end I opted to develop the entire front-end with <a href="https://polaris.shopify.com/">Shopify Polaris</a> - Shopifys own app React powered JS framework. I broke everything down into their own dedicated components, moved all state and methods into a container component <code>EditorContainer</code> and used the new <a href="https://medium.com/dailyjs/reacts-%EF%B8%8F-new-context-api-70c9fe01596b">React 16.3.0 Context API</a> to <a href="https://itnext.io/compound-components-with-react-v16-3-6679c752bd56">avoid prop-drilling</a>.</p>
<p>You can see the source code to the app on <a href="https://github.com/tomblanchard/shopify-liquid-repl">my GitHub</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Keeping HTML attributes readable within a Liquid-riddled Shopify theme]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>After spending a fair amount of time spent tweaking existing &quot;off-the-shelf&quot; Shopify themes, I've come to realise one of my biggest annoyances with them (most of them anyways, some of them are genuinely beautifully coded.) It's not the lack of CSS structure / conventions, it's not the massive spaghetti</p>]]></description><link>https://tomblanchard.co.uk/readable-html-attributes-shopify-theme-liquid/</link><guid isPermaLink="false">5fb15d2826ee371bd8046702</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Mon, 09 May 2016 13:48:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>After spending a fair amount of time spent tweaking existing &quot;off-the-shelf&quot; Shopify themes, I've come to realise one of my biggest annoyances with them (most of them anyways, some of them are genuinely beautifully coded.) It's not the lack of CSS structure / conventions, it's not the massive spaghetti code Javascript file, it's not even the never-ending theme settings (newsflash: no site needs over 20 colour options); it's the ridiculously long HTML attributes filled with Liquid logic.</p>
<p>What I'm talking about looks something like this:</p>
<pre><code class="language-markup">&lt;div class=&quot;collection-banner&quot; style=&quot;{% if settings.collection_header_bg_color != blank %}background-color: {{ settings.collection_header_bg_color }};{% endif %} {% if collection.image %}background-image: url('{{ collection.image | img_url: 'master' }}');{% endif %}&quot;&gt;
  &lt;div class=&quot;columns sixteen {% if settings.collection_image_text_align == 'left' %}text-align-left{% elsif settings.collection_image_text_align == 'right' %}text-align-right{% else %}text-align-center{% endif %}&quot;&gt;
    &lt;h1 class=&quot;headline&quot; style=&quot;{% if settings.collection_image_text_color != blank %}color: {{ settings.collection_image_text_color }};{% endif %}&quot;&gt;
      {{ collection.title }}
    &lt;/h1&gt;

    &lt;p class=&quot;subtitle&quot; style=&quot;{% if settings.collection_image_text_color_2 != blank %}color: {{ settings.collection_image_text_color_2 }};{% endif %}&quot;&gt;
      {{ collection.description }}
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>I can't express how much I loathe working with code like the above, the logic-filled HTML attributes cause so much unreadability, it's a mess and it makes debugging a bit of a nightmare.</p>
<p>I've started to use a little convention to avoid this problem, it involves creating a lot of variables, but separates the logic from the attributes pretty nicely. The above [bad] code could be re-factored to the following [improved] code:</p>
<pre><code class="language-markup">{% assign bg_color = settings.collection_header_bg_color %}
{% assign bg_color_style = &quot;background-color:&quot; | append: bg_color | append: &quot;;&quot; %}
{% if bg_color == blank %} {% assign bg_color_style = &quot;&quot; %} {% endif %}

{% assign bg_image = collection.image %}
{% assign bg_image_src = bg_image | img_url: &quot;master&quot; %}
{% assign bg_image_style = &quot;background-image: url(&quot; | append: bg_image_src | append: &quot;);&quot; %}
{% unless bg_image %} {% assign bg_image_style = &quot;&quot; %} {% endunless %}

{% assign text_align = settings.collection_image_text_align %}
{% assign text_align_class = &quot;text-&quot; | append: text_align %}

{% assign headline_color = settings.collection_image_text_color %}
{% assign headline_color_style = &quot;color:&quot; | append: headline_color | append: &quot;;&quot; %}
{% if headline_color == blank %} {% assign headline_color_style = &quot;&quot; %} {% endif %}

{% assign description_color = settings.collection_image_text_color_2 %}
{% assign description_color_style = &quot;color:&quot; | append: description_color | append: &quot;;&quot; %}
{% if description_color == blank %} {% assign description_color_style = &quot;&quot; %} {% endif %}

&lt;div class=&quot;collection-banner&quot; style=&quot;{{ bg_color_style }} {{ bg_image_style }}&quot;&gt;
  &lt;div class=&quot;columns sixteen {{ text_align_class }}&quot;&gt;
    &lt;h1 class=&quot;headline&quot; style=&quot;{{ headline_color_style }}&quot;&gt;
      {{ collection.title }}
    &lt;/h1&gt;

    &lt;p class=&quot;subtitle&quot; style=&quot;{{ description_color_style }}&quot;&gt;
      {{ collection.description }}
    &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>There we go, our logic and markup are completely separated now. You could argue that at 31 lines of code, this brings in the re-factored version at almost three times the size of the original 11 line version and that this is &quot;bad&quot;, but it really isn't; more lines of code != worse code, in this case it's 10x more readable and pretty much self-documenting, which is awesome!</p>
<p>As long as the above code is kept in it's own snippet (something like <code>snippets/collection-header.liquid</code>), as most of the main components of each template should be, then I think it's completely fine. It's not like we're adding bloat to the front-end and sacrificing load times for a little developer convenience / code readability, we're just throwing a few more lines of Liquid at the blazing fast Shopify servers, which is no biggie at all.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Using cropped (1:1 - square) product images in Shopify themes]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I recently worked on a Shopify theme project where images uploaded to the products on this store were all quite large in height compared to their width, a grid of the products looks something like this:</p>
<p><img src="https://tomblanchard.co.uk/content/images/2018/05/square-cropped-product-images-shopify-themes-product-grid.gif" alt="Product grid"></p>
<p>Shopify will generate a lot of images from each image uploaded to a product,</p>]]></description><link>https://tomblanchard.co.uk/square-cropped-product-images-shopify-themes/</link><guid isPermaLink="false">5fb15d2826ee371bd8046703</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Tue, 18 Nov 2014 14:52:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I recently worked on a Shopify theme project where images uploaded to the products on this store were all quite large in height compared to their width, a grid of the products looks something like this:</p>
<p><img src="https://tomblanchard.co.uk/content/images/2018/05/square-cropped-product-images-shopify-themes-product-grid.gif" alt="Product grid"></p>
<p>Shopify will generate a lot of images from each image uploaded to a product, all with different sizes, these sizes are documented <a href="http://docs.shopify.com/themes/liquid-documentation/filters/url-filters#size-parameters">here</a>. The problem I faced is that the different sized images generated all preserve the original aspect ratio (which is expected I guess), for example, if I uploaded an image which was <code>600x975px</code> and used the <code>large</code> generated size variation of this image it would be <code>295x480px</code> because the <code>large</code> image size returns the image at a <em>maximum</em> size of <code>480x480px</code> pixels whilst preserving the original aspect ratio. This is how you use the image with the preserved original aspect ratio:</p>
<pre><code class="language-markup">&lt;img src=&quot;{{ image.src | img_url: 'large' }}&quot; alt=&quot;{{ image.alt | escape }}&quot;&gt;
</code></pre>
<p>What if I wanted to use a square cropped version of this image like you can with WordPress? Well it turns out Shopify generates a <code>cropped</code> variation of each image uploaded to a product too, but for some reason this isn't documented anywhere. This is how you use the square cropped version of the image:</p>
<pre><code class="language-markup">{% assign cropped_img_size = 'large' %}
{% assign cropped_img = image.src | img_url: cropped_img_size | replace: '.jpg', '_cropped.jpg' | replace: '.gif', '_cropped.gif' | replace: '.png', '_cropped.png' %}

&lt;img src=&quot;{{ cropped_img }}&quot; alt=&quot;{{ image.alt | escape }}&quot;&gt;
</code></pre>
<p>The best thing about the cropped variation is that you can still use any of the already documented <a href="http://docs.shopify.com/themes/liquid-documentation/filters/url-filters#size-parameters">image sizes</a>, just change the <code>cropped_img_size</code> variable to what size you want. So the above code would spit out a <code>480x480px</code> image from any size image uploaded to the product, even very small images (below <code>480x480px</code>), small images get enlarged to fit the square. The only caveat I've found with this method is that you can't control the crop position, both the <code>x</code> and <code>y</code> axis crop positions default to <code>center</code> and cannot be changed.</p>
<h2 id="myusecase">My use case</h2>
<p>I needed this functionality because the product images needed to be responsive, what I mean by this is that on large displays the longer image should be displayed, but on smaller displays the square cropped image should be displayed instead (<a href="http://usecases.responsiveimages.org/#art-direction">art directed</a> responsive images). This is how I did it on the <code>product.liquid</code> template; listing all the images uploaded to the product, with a little help from <a href="http://scottjehl.github.io/picturefill">Picturefill</a>.</p>
<pre><code class="language-markup">{% for image in product.images %}

  {% assign img = image.src | img_url: 'grande' %}
  {% assign cropped_img = img | replace: '.jpg', '_cropped.jpg' | replace: '.gif', '_cropped.gif' | replace: '.png', '_cropped.png' %}

  &lt;picture&gt;
    &lt;source srcset=&quot;{{ img }}&quot; media=&quot;(min-width: 768px)&quot;&gt;
    &lt;source srcset=&quot;{{ cropped_img }}&quot; media=&quot;(max-width: 767px)&quot;&gt;
    &lt;img srcset=&quot;{{ img }}&quot; alt=&quot;{{ image.alt | escape }}&quot;&gt;
  &lt;/picture&gt;

{% endfor %}
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Making IE < 9 behave with responsive development]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Responsive web development can be tricky at the best of times, but even more so if the site in question has to support any version of Internet Explorer below version 9 (most of the time it's just IE 8 you need to tame due to the low usage statistics of</p>]]></description><link>https://tomblanchard.co.uk/making-old-ie-behave-with-responsive-development/</link><guid isPermaLink="false">5fb15d2826ee371bd8046704</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Sun, 11 May 2014 13:55:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Responsive web development can be tricky at the best of times, but even more so if the site in question has to support any version of Internet Explorer below version 9 (most of the time it's just IE 8 you need to tame due to the low usage statistics of IE &lt;= 7). Old IE makes things harder because media queries (the backbone of responsive websites) aren't supported, so all code inside media query blocks is ignored. Below are the various solutions I've tried out which I wasn't 100% happy with:</p>
<ul>
<li>JS based media query polyfills (<a href="https://github.com/scottjehl/Respond">Respond.js</a>, <a href="https://code.google.com/p/css3-mediaqueries-js/">css3-mediaqueries.js</a>)<br><strong>- Performance issues, also what about users with no JS and old IE?</strong></li>
<li><a href="http://www.jonikorpi.com/leaving-old-IE-behind/">Mobile-first / progressive enhancement</a><br><strong>- This is a great solution, I use a similar method on this site, but what if the client insists the layout needs to look the same on both modern browsers and old IE?</strong></li>
<li>Only ever using <code>max-width</code> based media queries<br><strong>- On larger sites this will lead to horrible CSS as it will require a lot of style resetting at certain breakpoints.</strong></li>
</ul>
<p>The solution I've found to be the best does require a bit of setup but once you're up and running supporting old IE has never been so easy. It also require Sass, in my opinion this functionality alone is enough to try Sass if you haven't before (yes, this solution is that useful). In it's simplest form the file structure consists of:</p>
<pre><code class="language-tex">- project/
  - css/
    - _base.scss
    - _config.scss
    - lt-ie-9.scss
    - style.scss
  - index.html
</code></pre>
<p><code>_base.scss</code> is the file which contains all of your SCSS / CSS, you can write it straight into there or if you prefer to be modular about it you can import all of your CSS components into that file. With no project CSS thrown in there all it's doing is importing the <code>_config.scss</code> file:</p>
<pre><code class="language-scss">@import &quot;config&quot;;
</code></pre>
<p><code>_config.scss</code> contains a map of your common breakpoints along with a media query mixin:</p>
<pre><code class="language-scss">/**
  Specify breakpoints here.
 */
$breakpoints: (
  'small':    '(min-width: 480px)',
  'medium':   '(min-width: 768px)',
  'large':    '(min-width: 1024px)'
);

@mixin media-query($media-query, $lt-ie-9-support: false) {
  /**
    If we're not in the old IE stylesheet, then output the media query block.
   */
  @if $is-lt-ie-9-stylesheet == false {
    @each $name, $declaration in $breakpoints {
      @if $media-query == $name and $declaration {
        @media only screen and #{$declaration} {
          @content;
        }
      }
    }
  }
  /**
    If the media query has been set to support old IE and we are in the old IE
    stylesheet, then output the code inside the media query block but strip the
    actual media query from around it.
   */
  @if $lt-ie-9-support == true and $is-lt-ie-9-stylesheet == true {
    @content;
  }
}
</code></pre>
<p><code>lt-ie-9.scss</code> consists of the following, this file will compile to <code>lt-ie-9.css</code> when ran through a Sass compiler:</p>
<pre><code class="language-scss">$is-lt-ie-9-stylesheet: true;

@import &quot;base&quot;;
</code></pre>
<p><code>style.scss</code> consists of the following, this file will compile to <code>style.css</code> when ran through a Sass compiler:</p>
<pre><code class="language-scss">$is-lt-ie-9-stylesheet: false;

@import &quot;base&quot;;
</code></pre>
<p>Almost done now, the last step is to add the reference to the CSS files into the HTML like so:</p>
<pre><code class="language-markup">&lt;!--[if (lt IE 9) &amp; (!IEMobile)]&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;css/lt-ie-9.css&quot;&gt;
&lt;![endif]--&gt;
&lt;!--[if (gte IE 9) | (IEMobile)]&gt;&lt;!--&gt;
  &lt;link rel=&quot;stylesheet&quot; href=&quot;css/style.css&quot;&gt;
&lt;!--&lt;![endif]--&gt;
</code></pre>
<p>This tells the browser which CSS file to use based on what browser the page is being view on, if the browser is IE and below version 9 it will load <code>lt-ie-9.css</code>, however if the browser is either IE version 9 and above or not IE at all it will load <code>style.css</code>.</p>
<h2 id="usage">Usage</h2>
<p>In <code>_base.scss</code> you'd write your SCSS / CSS as per normal but when you want to use a media query you'd do it like so:</p>
<pre><code class="language-scss">.element {
  @include media-query(small) {
    width:50%;
  }
}
</code></pre>
<p>If you want the code in the media query to be read by old IE then you just simply add a second argument of <code>true</code> to the <code>media-query</code> <code>@include</code> like so:</p>
<pre><code class="language-scss">.element {
  @include media-query(small, true) {
    width:50%;
  }
}
</code></pre>
<p>The above code will output the following in the <code>style.scss</code> file:</p>
<pre><code class="language-css">@media only screen and (min-width: 480px) {
  .element {
    width:50%;
  }
}
</code></pre>
<p>And the following in the <code>lt-ie-9.scss</code> file, stripping the media query wrapped around it:</p>
<pre><code class="language-css">.element {
  width:50%;
}
</code></pre>
<p>There you have it, almost no effort required to make old IE behave with media queries, I've used this method in a few projects of mine now and I haven't had any issues at all. I actually use this method by default on my personal front-end boilerplate which you can check out <a href="https://github.com/tomblanchard/boilerplate/tree/master/src/scss">here</a>.</p>
<h2 id="otheruses">Other uses</h2>
<p>This isn't just useful for responsive stuff either, it can also be used as an alternative to <a href="https://twitter.com/paul_irish">Paul Irish</a>'s IE <a href="http://www.paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither">conditional classes</a> on the <code>&lt;html&gt;</code> element. With this method you'd target elements being viewed in a specific version of IE like so:</p>
<pre><code class="language-scss">.element {
  /*html*/.lte9 &amp; {
    width:50%;
  }
}
</code></pre>
<p>The problem with that method is the IE specific styles would get loaded be the browsers no matter what browser it is, downloading CSS which doesn't get used is just wasted bytes which can add up and cause a slow-loading site. With the Sass method you'd do it like:</p>
<pre><code class="language-scss">.element {
  @if $is-lt-ie-9-stylesheet == true {
    width:50%;
  }
}
</code></pre>
<p>Or if you want CSS outputted only in the non-IE stylesheet you'd do:</p>
<pre><code class="language-scss">.element {
  @if $is-lt-ie-9-stylesheet != true {
    width:50%;
  }
}
</code></pre>
<p>This way the browser specific CSS only ever gets loaded by the browser which actually needs it, awesome right?</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Sassy, media query specific CSS objects]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>If you're an advocate of OOCSS (Oriented CSS) then you have most likely worked with Sass before as it makes development of OOCSS a lot more efficient. Here's a simple example of an object (stolen / slightly modified from <a href="https://github.com/csswizardry/inuit.css/blob/master/objects/_nav.scss">inuit.css</a>):</p>
<pre><code class="language-scss">.nav {
  margin-left:0;
  margin-bottom:0;
  list-style:none;

  &gt; li {
    &amp;</code></pre>]]></description><link>https://tomblanchard.co.uk/sassy-media-query-specific-css-objects/</link><guid isPermaLink="false">5fb15d2826ee371bd8046705</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Fri, 02 May 2014 14:05:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>If you're an advocate of OOCSS (Oriented CSS) then you have most likely worked with Sass before as it makes development of OOCSS a lot more efficient. Here's a simple example of an object (stolen / slightly modified from <a href="https://github.com/csswizardry/inuit.css/blob/master/objects/_nav.scss">inuit.css</a>):</p>
<pre><code class="language-scss">.nav {
  margin-left:0;
  margin-bottom:0;
  list-style:none;

  &gt; li {
    &amp;,
    &gt; a {
      display:inline-block;
       *display:inline;
      zoom:1;
    }
  }
}
</code></pre>
<p>This object turns a regular unordered list into a horizontal row of list items, mostly for use in navigation menus. To use this I can just drop in a class in my HTML like so:</p>
<pre><code class="language-markup">&lt;ul class=&quot;nav&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;/home&quot;&gt;Home&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/about&quot;&gt;About&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/contact&quot;&gt;Contact&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Alternatively I can use Sass's <code>@extend</code> directive like so:</p>
<pre><code class="language-scss">.menu {
  @extend .nav;
}
</code></pre>
<p>Just recently I've worked on a lot of responsive front-end projects in which the design briefs I'm supplied with involve usage of certain CSS objects that are only active in-between specific breakpoints. If Sass enabled usage of the <code>@extend</code> directive inside media queries then I could just do something like:</p>
<pre><code class="language-scss">.menu {
  @media (min-width:768px) and (max-width:1023px) {
    @extend .nav;
  }
}
</code></pre>
<p>This throws the following error:</p>
<pre><code>You may not @extend an outer selector from within @media.
You may only @extend selectors within the same directive.
</code></pre>
<p>My work around for this is changing the way I author my objects in the first place, instead of limiting the object from just being contained in a class (which limits usage to either a class in the HTML or the <code>@extend</code> directive in Sass) also have it in a mixin, like so:</p>
<pre><code class="language-scss">@mixin nav {
  margin-left:0;
  margin-bottom:0;
  list-style:none;

  &gt; li {
    &amp;,
    &gt; a {
      display:inline-block;
       *display:inline;
      zoom:1;
    }
  }
}
.nav { @include nav; }
</code></pre>
<p>Now when I want to use this object I have three options, I can either drop a class in the HTML, <code>@extend</code> it and thanks to the mixin and I can now <code>@include</code> it. The good thing about mixins is that they can be used pretty much anywhere, even inside of media query blocks, so say I wanted a <code>.nav</code> object which was only active in-between a certain media query I can do it like so (the <code>tablet</code> class prefix is just for brevity, make sure the actual media query specific class names have <a href="http://css-tricks.com/naming-media-queries">context</a>):</p>
<pre><code class="language-scss">@media (min-width:768px) and (max-width:1023px) {
  .tablet-nav {
    @include nav;
  }
}
</code></pre>
<p>This makes it all a lot more <a href="http://en.wikipedia.org/wiki/Don't_repeat_yourself">DRY</a>, there are other ways of doing this like having the object styles active at all times and then undoing those styles during specific media queries but that it far from ideal, but yeah this is my take on the solution and it seems to work quite well. I have open-sourced my own <a href="https://github.com/tomblanchard/boilerplate">personal front-end boilerplate</a> which makes use of this CSS object authoring method, so if you want to check out more examples (including objects with sub-components etc.) then <a href="https://github.com/tomblanchard/boilerplate/tree/master/src/scss/framework/objects">be my guest</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Saying goodbye to Windows and hello to OS X]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Ever since I started out as a front-end developer I've worked on a PC running Windows, it's served me well over the years, despite all the issues that most people in this industry seem to have with the Windows operating system I don't really have anything to complain about.</p>
<p>I</p>]]></description><link>https://tomblanchard.co.uk/saying-goodbye-to-windows-and-hello-to-os-x/</link><guid isPermaLink="false">5fb15d2826ee371bd8046706</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Mon, 24 Feb 2014 15:06:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Ever since I started out as a front-end developer I've worked on a PC running Windows, it's served me well over the years, despite all the issues that most people in this industry seem to have with the Windows operating system I don't really have anything to complain about.</p>
<p>I spent a long time mulling over whether or not I was going to join the Apple bandwagon and get a Mac. Well around a month ago I took the plunge and purchased a 13.3-inch MacBook Air and I couldn't be happier with it, I've waited a few weeks to see if this purchase has turned out to be a good investment and it definitely has. The only thing that's caused some problems is transitioning from Windows to OS X which I fully anticipated considering this is my <strong>first</strong> ever Mac.</p>
<h2 id="apps">Apps</h2>
<p>As I'm explicitly a front-end dev it was a breeze setting up the machine for work purposes, as my requirements are very lean. It was just a case of installing all the CLI tools I use (Git, Node, Ruby, Grunt etc.), installing software (Photoshop, Skype etc.) then leaving my Dropbox to download all of my files and boom, ready to <del>twerk</del> work! There were a couple of Windows-only apps I had to sacrifice but it didn't take long to find OS X alternatives, all together, the setting up process was mostly painless.</p>
<p>For a while I've gotten into the habit of using Dropbox for pretty much everything, this includes hosting of my app data, so it's easy to have the same software settings / plugins synced across different machines. I do this mostly for 1Password (all of my login details, encrypted of course), Sublime Text (all settings and packages) and XAMPP (web project files which require a local Apache / SQL server to run). I'm so glad I got everything synced on Dropbox prior to upgrading my work machine, it saved a lot of headaches and it's so efficient because I can pretty much work from any machine (given an hour or two to set it up).</p>
<h3 id="internetexplorer">Internet Explorer</h3>
<p>Just when I thought I was done for good with Windows... It turns out the best way to run IE on Mac machines is through installing virtual machines that run the Windows operating system. This was a bit of a drag to set up due to the huge file sizes of the virtual machine files from <a href="http://modern.ie">Modern.IE</a> and my not-so-great Internet speed but once everything is set up it's pretty painless to test in IE, plus it's kind of cool to see Windows actually running <strong>inside</strong> of OS X.</p>
<h2 id="portability">Portability</h2>
<p>Not only is this my first Mac work machine, it's also my first portable work machine, I've only ever worked on fixed desktop PCs. Most of the time I work with my MacBook connected to a 24-inch monitor but sometimes it's nice to have a change the surroundings where I work from, I've found it makes me a lot more productive compared to staying in the same place for hours on end.</p>
<h2 id="downsides">Downsides</h2>
<p>Though the transition from Windows to OS X has been <em>mostly</em> effortless I have come across a few things that I'm not a huge fan of.</p>
<h3 id="archives">Archives</h3>
<p>With Windows, the native way to handle <code>.zip</code> archive files is quite seamless; it just opens like a native directory. With OS X, when you open one it automatically extracts all the file contents into its own directory in the same directory as the <code>.zip</code> file. This really bugs me; it causes unnecessary clutter and doesn't feel graceful at all. I've tried a few custom OS X archive GUI apps but none seem to function as seamless as I prefer, this isn't a huge deal and I'm sure I'll get used to it.</p>
<h3 id="ds_store">.DS_Store</h3>
<p>This thing drives me crazy, basically OS X comes with a built in app called &quot;Finder&quot;, and it's for navigating around files on the machine's hard drive. Every time you open a directory Finder automatically generates a hidden <code>.DS_Store</code> file which holds meta data about the directory, if you delete it, it comes straight back. You might not think this is a big deal but when you have it so hidden files are visible it get's hugely annoying. It means I have to exclude that file from all directories when working with Git (using <code>.gitignore</code>), it also means that if I upload an entire directory via FTP (which is sometimes needed), all those annoying <code>.DS_Store</code> files come with it.</p>
<h3 id="otherlittlethings">Other little things</h3>
<p>Other things that I've noticed I don't like about OS X are quirks I've grown accustomed to while working on Windows machines all of my life. One thing is that you can't quickly permanently delete files / directories, in Windows it's a simple keyboard shortcut <code>shift + del</code>, but in OS X you have to move the file to Trash then empty the Trash which gets old fast. Another thing I grew fond of in Windows was right clicking in a directory and being able to create a new file there and then, mostly I'd use this when creating new <code>.html</code> / <code>.css</code> files, you can't do this in OS X either, I know it's a very small thing but when you're so used to doing it you grow to miss it.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In the end though I'm smitten for this MacBook and OS X, it's significantly faster than my old Windows machine and a lot more fun to work with. I've always been a lover of Apple, I've had this machine for a few weeks now and I still get blown away with even it's external appearance, the attention to detail Apple put into their products is phenomenal.</p>
<p>This upgrade has made my job easier and a lot more enjoyable, if you're reading this (people read this right?) and are wanting to get a Mac but are worried about switching from Windows to OS X, don't be... do it, do it now!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[How is this done again? #1 - WordPress loop; posts in rows & columns]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><strong>&quot;How is this done again?&quot;</strong>, those five words I'm noticing I'm asking myself more and more. A lot of the time when I'm working on a project I'll have to accomplish a specific task (which I have done before) which I know how to do, but always forget</p>]]></description><link>https://tomblanchard.co.uk/how-is-this-done-again-1/</link><guid isPermaLink="false">5fb15d2826ee371bd8046707</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Fri, 24 Jan 2014 15:07:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><strong>&quot;How is this done again?&quot;</strong>, those five words I'm noticing I'm asking myself more and more. A lot of the time when I'm working on a project I'll have to accomplish a specific task (which I have done before) which I know how to do, but always forget the best way to go about it, I'm pretty sure I'm not the only one who's guilty of this (I hope!) Most of the time this ends up with me either going back to an older projects code and pasting it from there or having to completely rewrite it which sometimes doesn't even end up as efficient as the original code, both of these solutions aren't really ideal. I suppose I could just whack these easy forgettable tasks into a <a href="https://gist.github.com">GitHub Gist</a> but instead I decided to start this tutorial themed post series so I can hopefully help out a few fellow developers who are in a similar position to me.</p>
<p>This post is focussed around the WordPress loop and manipulating it to display posts within rows and columns in a specific way. To clarify, I was working on a clients site and they wanted their posts to be displayed in a grid system where every two posts were contained in their own wrappers which acted as grid rows (<code>&lt;div class=&quot;row&quot;&gt;&lt;/div&gt;</code>), so the HTML outputted by the loop had to be like so:</p>
<pre><code class="language-markup">&lt;div class=&quot;row&quot;&gt;
  &lt;article&gt;
    &lt;a href=&quot;http://post-link&quot;&gt;Post title&lt;/a&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;a href=&quot;http://post-link&quot;&gt;Post title&lt;/a&gt;
  &lt;/article&gt;
&lt;/div&gt;

&lt;div class=&quot;row&quot;&gt;
  &lt;article&gt;
    &lt;a href=&quot;http://post-link&quot;&gt;Post title&lt;/a&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;a href=&quot;http://post-link&quot;&gt;Post title&lt;/a&gt;
  &lt;/article&gt;
&lt;/div&gt;
</code></pre>
<h2 id="coolnowwheresthecode">Cool, now where's the code!?</h2>
<p>To accomplish the loop outputting the posts in this fashion I had to make use of the PHP <code>modulus</code> and <code>post-increment</code> operators, the loop I used is shown below (commented to clarify what each snippet is for):</p>
<pre><code class="language-php">&lt;?php if (have_posts()) : ?&gt;

  &lt;?php
    /**
     * Start post counter at 0.
     */
    $i = 0;
    while (have_posts()) : the_post();
    /**
     * Define the number of posts displayed per row.
     */
    $posts_per_row = 2;
    /**
     * HTML of the row opening &amp; closing tags.
     */
    $open = '&lt;div class=&quot;row&quot;&gt;';
    $close = '&lt;/div&gt;';
    /**
     * Output HTML of the row opening &amp; closing tags when and where they should be
     * outputted.
     */
    echo !($i % $posts_per_row) &amp;&amp; $i ? $close : '',
         !($i % $posts_per_row) ? $open : '';

    /**
     * Regular post loop stuff below.
     */
  ?&gt;

    &lt;article&gt;
      &lt;a href=&quot;&lt;?php the_permalink(); ?&gt;&quot;&gt;&lt;?php the_title(); ?&gt;&lt;/a&gt;
    &lt;/article&gt;

  &lt;?php
    /**
     * Update post counter throughout each post iterated over.
     */
    $i++;
    endwhile;
    /**
     * Output HTML of the row closing tag again (this is needed in case there aren't
     * the same number of posts in each row).
     */
    echo ($i) ? $close : '';
  ?&gt;

&lt;?php endif; ?&gt;
</code></pre>
<p>Sorted, now the posts are perfectly contained in their own rows and columns and there's never any unclosed HTML elements etc. (some other tutorials on how to accomplish this fall victim to this.) All HTML outputted is always valid and one of the best things about this is that it works perfectly with pagination, I thought it would throw a spanner in the works but nope, it just works.</p>
<h2 id="thishasotherusesto">This has other uses to!</h2>
<p>I've found this  method can also be useful when you need to display posts in a carousel / slideshow, I did this when I developed a re-design of a clients site, one of the features were that the posts were displayed by their thumbnail inside an image carousel using the jQuery plugin <a href="http://jquery.malsup.com/cycle2">Cycle2</a>, you can see that in action on the live site <a href="http://goo.gl/5Gglb2">http://goo.gl/5Gglb2</a>. The HTML the loop outputted the posts in had to be structured like so:</p>
<pre><code class="language-markup">&lt;ul&gt;
  &lt;li&gt;
    &lt;img src=&quot;http://post-thumbnail&quot; alt=&quot;&quot;&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;img src=&quot;http://post-thumbnail&quot; alt=&quot;&quot;&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;img src=&quot;http://post-thumbnail&quot; alt=&quot;&quot;&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;img src=&quot;http://post-thumbnail&quot; alt=&quot;&quot;&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;img src=&quot;http://post-thumbnail&quot; alt=&quot;&quot;&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;img src=&quot;http://post-thumbnail&quot; alt=&quot;&quot;&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>All I needed to do to accomplish this was to change a few loop variables; <code>$posts_per_row</code> to <code>3</code>, <code>$open</code> to <code>&lt;ul&gt;</code>, <code>$close</code> to <code>&lt;/ul&gt;</code> and voila, done!</p>
<p>I'll no doubt be back extending the <strong>&quot;How is this done again?&quot;</strong> post series soon, I seem to be way too good at forgetting code not to.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Contagious Grunt-itis]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>If you haven't heard of <a href="http://gruntjs.com">Grunt</a> (if so, where have you been hiding?), it's a task runner powered by JavaScript and <a href="http://nodejs.org">Node.js</a>, don't worry, you don't have to be a JavaScript / Node.js developer to use Grunt (just like you don't have to be a <a href="http://ruby-lang.org">Ruby</a> developer to use</p>]]></description><link>https://tomblanchard.co.uk/contagious-gruntitis/</link><guid isPermaLink="false">5fb15d2826ee371bd8046708</guid><dc:creator><![CDATA[Tom Blanchard]]></dc:creator><pubDate>Mon, 13 Jan 2014 15:09:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>If you haven't heard of <a href="http://gruntjs.com">Grunt</a> (if so, where have you been hiding?), it's a task runner powered by JavaScript and <a href="http://nodejs.org">Node.js</a>, don't worry, you don't have to be a JavaScript / Node.js developer to use Grunt (just like you don't have to be a <a href="http://ruby-lang.org">Ruby</a> developer to use <a href="http://sass-lang.com">Sass</a>). You can use it for a <strong>lot</strong> of things thanks to the community for publishing so many plugins, a couple of examples being <a href="http://github.com/gruntjs/grunt-contrib-cssmin">Cssmin</a> (CSS compression) and <a href="http://github.com/gruntjs/grunt-contrib-uglify">Uglify</a> (JavaScript minification).</p>
<p>This post assumes you're at least a little bit familiar with Grunt so I'm not going to write up a tutorial on how to install Grunt and its dependencies, in a nutshell you set up your <code>Gruntfile.js</code> file and configure tasks and plugins to do what you want, when you want. When you've got all your tasks set up, you just run the <code>grunt</code> command (which runs the default task) and you're all set, or if you have multiple tasks you'd run <code>grunt task-name</code>.</p>
<p>Also, if you were wondering about the title of this post, it's reffering to the fact that ever since I had my Grunt &quot;aha&quot; moment I haven't been able to stop from myself sharing its power with people. Yep it's that awesome it even caused me to come up with that awfully lame form of wordplay!</p>
<h2 id="whatiusegruntfor">What I use Grunt for</h2>
<p>On this site I use Grunt for a lot of things (short of dressing and feeding me), a lot of what I use it for on this site is very specific to this site as a whole and there will be a lot of things in there most projects won't need, I've heavily commented my <code>Gruntfile.js</code> file so it makes as much sense as possible and which snippets do which task and why I want them in place. I have also noted down which plugins I have used with links to them.</p>
<p>Below is my <code>Gruntfile.js</code> setup:</p>
<pre><code class="language-javascript">module.exports = function(grunt) {

  /**
   * Instead of loading each task one by one using `grunt.loadNpmTasks`,
   * automatically load all dependencies from the `package.json` file.
   *
   * Plugin: http://github.com/sindresorhus/load-grunt-tasks
   */
  require('load-grunt-tasks')(grunt);

  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),




    /**
     * Start a local server using the compiled Jekyll site directory as the base.
     *
     * Plugin: http://github.com/gruntjs/grunt-contrib-connect
     */
    connect: {
      server: {
        options: {
          hostname: '*',
          port: 12000,
          base: '_site'
        }
      }
    },


    /**
     * Compile Sass files to CSS files.
     *
     * Plugin: http://github.com/gruntjs/grunt-contrib-sass
     */
    sass: {
      dist: {
        options: {
          style: 'compressed'
        },
        files: [{
          expand: true,
          cwd: 'lib/css',
          src: ['**/*.scss'],
          dest: 'lib/css',
          ext: '.min.css'
        }]
      }
    },


    /**
     * Uglify (minify) `main.js` file.
     *
     * Plugin: http://github.com/gruntjs/grunt-contrib-uglify
     */
    uglify: {
      dist: {
        files: {
          'lib/js/main.min.js': 'lib/js/main.js'
        }
      }
    },


    /**
     * Build Jekyll site.
     *
     * Plugin: http://github.com/dannygarcia/grunt-jekyll
     */
    jekyll: {
      dist: {
        options: {
          bundleExec: true
        }
      }
    },


    /**
     * Minify all HTML / PHP files that Jekyll builds.
     *
     * Plugin: http://github.com/gruntjs/grunt-contrib-htmlmin
     */
    htmlmin: {
      dist: {
        options: {
          removeComments: true,
          collapseWhitespace: true
        },
        files: [{
          expand: true,
          cwd: '_site',
          src: ['**/*.{html,php}'],
          dest: '_site'
        }]
      }
    },


    /**
     * Instead of getting Jekyll to rebuild every time a file in `lib` is changed
     * (slow), copy and replace the old `lib` with the new one in the compiled Jekyll
     * site directory. This only gets ran during the `watch` task because Jekyll
     * does it on the initial build.

     * Plugin: https://github.com/gruntjs/grunt-contrib-copy
     */
    copy: {
      lib : {
        files: [{
          expand: true,
          src: ['lib/**/*'],
          dest: '_site'
        }]
      }
    },


    /**
     * Runs tasks against changed watched files.
     *
     * Plugin: http://github.com/gruntjs/grunt-contrib-watch
     */
    watch: {
      /**
       * Watch any Sass files, if any are modified, recompile them to CSS.
       */
      sass: {
        files: 'lib/css/**/*.scss',
        tasks: ['sass'],
        options: {
          spawn: true
        }
      },

      /**
       * Watch any JavaScript files, if any are modified, reuglify them.
       */
      uglify: {
        files: 'lib/js/main.js',
        tasks: ['uglify'],
        options: {
          spawn: false
        }
      },

      /**
       * Watch any Jekyll related files, if any are modified, rebuild and minify the Jekyll site,
       */
      jekyll: {
        files: ['_includes/**/*', '_layouts/**/*', '_plugins/**/*', '_posts/**/*', '*.html', '_config.yml', '_site/**/*.{html,php}'],
        tasks: ['jekyll', 'htmlmin'],
        options: {
          spawn: false
        }
      },

      /**
       * Watch any files in the `lib` directory, if any are modified, copy and replace
       * that directory to the compiled Jekyll site directory (instead of getting Jekyll
       * to rebuild the entire site every time a file in `lib` is modified which is a lot
       * slower).
       */
      copy: {
        files: ['lib/**/*'],
        tasks: ['copy'],
        options: {
          spawn: false
        }
      }
    },


    /**
     * Upload (and replace the old files) the compiled Jekyll site directory to the
     * server.
     *
     * 1. For some reason when Jekyll builds the site files, it builds random `pageN`
     *    directories in the compiled site root directory, this isn't a major problem,
     *    just an annoyance. This stops these directories getting uploaded to the server.
     * 2. Certain files which should be kept on the server even when they are not
     *    presented locally.
     *
     * Plugin: http://github.com/inossidabile/grunt-ftpush
     */
    ftpush: {
      dist: {
        auth: {
          host: 'tomblanchard.co.uk',
          port: 21,
          authKey: 'tomblanchard.co.uk'
        },
        src: '_site',
        dest: '/public_html/root',
        exclusions: ['page*', '!**/*/page*'], /* [1] */
        keep: ['googlea67e882a592198c5.html', 'favicon.ico'] /* [2] */
      }
    },


    /**
     * Run command line tools.
     *
     * Plugin: http://github.com/sindresorhus/grunt-shell
     */
    shell: {
      /**
       * Push the uncompiled Jekyll source code to GitHub.
       *
       * 1. Updates the Git index (including deleted files).
       * 2. Commits any changes, with the commit message as the current date and time.
       * 3. Pushes changes to the remote repository.
       */
      git: {
        command: [
          'git add -A', /* [1] */
          'git commit -m &quot;&lt;%= grunt.template.today(&quot;isoDateTime&quot;) %&gt;&quot;', /* [2] */
          'git push' /* [3] */
        ].join('&amp;&amp;')
      }
    }




  });

  /**
   * The `default` task, it builds / compiles the site, then watches for changes,
   * then rebuilds / recompiles.
   *
   * Usage: `grunt` or `grunt default`
   */
  grunt.registerTask('default', [
    'connect',
    'sass',
    'uglify',
    'jekyll',
    'htmlmin',
    'watch'
  ]);


  /**
   * The `push` task, it uploads the compiled site to the server then pushes
   * uncompiled Jekyll source code to GitHub.
   *
   * Usage: `grunt push`
   */
  grunt.registerTask('push', [
    'ftpush',
    'shell'
  ]);

};
</code></pre>
<p>So there you have it, go fourth and learn!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>