How I built Shopify Liquid REPL
I recently spent a couple of days building a tool in the form of a small web app Shopify Liquid REPL, it's similiar to the Bable REPL, but it takes Shopify Liquid (different to vanilla Liquid) and outputs the compiled code.
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 DropPen for this, but that seems to be broken nowadays.
I originally planned on using Shopify/liquid (what I call "vanilla Liquid") 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 here, Shopify claim:
Shopify Themes use an extended version of Liquid with a variety of additional objects, tags, and filters
For my REPL to function properly, the input Liquid needs to be run through the actual Shopify servers, luckily this isn't too hard when you combine usage of the REST Admin API with the ability to access alternative theme templates via a view
query paramater. The mechanism behind this works like this:
- Get Liquid input from the form POST data
- Upload the Liquid input as a new alternate template via the Asset API, e.g.
/templates/index.1nfn38hgj38g.liquid
- Request the shop with the new alternate tempalte active, e.g.
https://shop-name.myshopify.com/?view=1nfn38hgj38g
- Return the content
- Delete the alternate template from the theme
My Express route for this looks like:
app.post("/", (req, res) => {
// 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: "alphabetic"
});
var asset_key = `templates/index.${template_suffix}.liquid`;
// Upload the Liquid input as a new alternate template
shopify.asset.create(shop_theme_id, {
"key": asset_key,
"value": input
})
.then(() => {
// Request the shop with the new alternate tempalte active
return request({
url: `${shop_url}/?view=${template_suffix}`,
headers: { "User-Agent": "Mozilla" }
});
})
.then((template_res) => {
// Delete the alternate template from the theme
shopify.asset.delete(shop_theme_id, {
asset: {
"key": asset_key
}
});
// Return the content
return res.send(template_res);
});
});
Now on the front-end we just need a form like:
<form method="post" action="/">
<textarea name="input"></textarea>
<button type="submit">Submit</button>
</form>
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.
In the end I opted to develop the entire front-end with Shopify Polaris - 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 EditorContainer
and used the new React 16.3.0 Context API to avoid prop-drilling.
You can see the source code to the app on my GitHub.