[Proposal] Args for render block
Nobody has claimed this yet.
- Dominant language
- Ruby
- Stars
- 11.9k
- Forks
- 1.5k
- Avg merge
- 17h 55m
- Merged PRs (30d)
- 3
Description
Hi everyone,
Michael from Maestrooo here. I decided to close the original ticket #1530 and open a new one, as the other one was different ideas that came through my mind but it became a bit confusing and, as I did various experiments with components in Liquid, more use cases arises, so I wanted to formalize it.
Having such a feature would open a lot of flexibility to Liquid and allow better composition through better code re-use.
This proposal is based on @dylanahsmith suggestion, which actually makes a lot of sense, and add new concepts without complexifying the syntax of Liquid.
Block support for render
As of today, render tag cannot be used as a form block. This new syntax would introduce a new "args":
{% render 'button', args: %}
{{ block.settings.text | capitalize }}
{% endrender %}
In the snippet, the content could be accessed through the "args" argument, and through the default block:
button.liquid
<button>{{ args.block | escape }}</button>
Support for named arguments
In order to have better composability, render should support named arguments:
{% render 'dropdown', args: %}
{% args 'toggle' %}
{% render 'button' %}Toggle{% endrender %}
{% endarg %}
{% args 'values' %}
...
{% endarg %}
{% endrender %}
dropdown.liquid
<div>
{{ args.toggle }}
<div>
{{ args.values }}
</div>
</div>
Support for props
Using named arguments should NOT remove the ability to pass custom properties, so we should still be able to do that:
{% render 'button', size: 'sm', args: %}
<svg class="icon">...</svg> {{ block.settings.text | capitalize }}
{% endrender %}
Maybe the "args" syntax is a bit unneeded here, and could be implicit as soon as we are using the render as a block form.
Non-leak of arguments
An absolute requirement would be to make sure that any args define inside the block form is not leak outside to prevent any accidental override of variable:
{% assign header = 'bar' %}
{% render 'modal', args: %}
{% arg 'header' %}
Foo
{% endarg %}
{% endrender %}
{{ header }} <<---- Must render 'bar'
Optional
One "nice to have" would be able to pass extra parameters to args:
{% render 'modal', args: %}
{% arg 'header', bordered: true %}
Header
{% endarg %}
{% endrender %}
modal.liquid:
<div class="{% if arg.header.bordered %}bordered{% endif %}">
{{ args.header }}
</div>
Support for nested folders
At least in the snippet folder, Shopify should add support for folders to make it more maintainable:
{% render 'component/button' %} // Would look for "snippets/component/button.liquid"
After lot of trying, I feel this proposal would add something very useful without increasing the API too much, in a completely backward compatible way, and would allow most of the use cases that are required for a truly component based approach in Liquid.
Condensed syntax
If we want to make Liquid more component friendly, we should be able to allow us to easily compose components. While the render syntax with block solves the issue, it makes the syntax a bit cumbersome:
{% render 'customer-address' %}
{% render 'icon-with-text', icon: 'picto' %}Shipping{% endrender %}
{{ order.shipping_address | format_address }}
{% endrender %}
An alternate syntax that would be a shortcut for block render could be introduced, for instance:
{# customer-address #}
{# 'icon-with-text', icon: 'picto' #}Shipping{# end #}
{{ order.shipping_address | format_address }}
{# end #}
With a self-closing component (which would be the same as render, but with a nicer syntax):
{# arrow-button #}
Or even (although this does not "look" very Liquid-ish):
<Liquid.CustomerAddress>
<Liquid.IconWithText icon="picto">Shipping</Liquid.IconWithText>
{{ order.shipping_address | format_address }}
</Liquid.CustomerAddress>
And have the parser detect the to interpret this as a component. This would have the benefit of allowing the usage of filter (for instance here on the "icon"):
<Liquid.CustomerAddress>
<Liquid.IconWithText icon="{{ section.settings.picto }}">Shipping</Liquid.IconWithText>
{{ order.shipping_address | format_address }}
</Liquid.CustomerAddress>
One very nice benefit of having a condensed syntax would be to be able to make the Liquid much more expressive. For instance, in our templates we are constantly doing that kind of code absolutely everywhere by testing if a given setting is empty before outputting it:
With a condensed syntax, such code could be expressed like this, for instance:
{# text: variation: 'strong' #}{{ section.settings.subheading }}{# end #}
{# heading: style: 'h1', gradient: section.settings.gradient #}{{ section.settings.heading }}{# end #}
And simply have the component decide to render itself or not based on the content of the slot. This would also helps drastically to create more re-usable sections
Styles
It would be nice if components would allow to bundle their own style, so that everything can be in the same place. For instance:
banner.liquid:
{% stylesheet %}
.banner {
...
}
{% endstylesheet %}
{% javascript, module: true %}
export class Banner extends HTMLElement {
...
}
window.customElements.define('x-banner', Banner);
{% endjavascript %}
<div class="banner">
...
</div>
As of today Shopify has a "stylesheet" tag but this can be used only on sections, and not inside snippet. However, with component being self-contained re-usable element, it would make lot of sense to allow their usage.
Potential issues
This proposal currently introduces one potential issue, is that it would cause a breaking change for merchant using a parameter called "args":
{% render 'modal', args: 'test' %}
One possible solution would be to detect if the render is used in block mode or not, and if not, re-use the value.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
This is a broad proposal covering block render arguments, named arguments, nested snippet folders, condensed component syntax, and bundled styles; no implementation files or tests are identified. Start by narrowing the scope around the render tag, then define acceptance tests for the selected behavior, including argument isolation and backward compatibility.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ruby
- Domain
- web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100