Open emichael opened 9 years ago
This could be combination of the capture
and raw
tag to create the macro and a change to include
to use the macro.
@carsonreinke Sorry, but just for my benefit, are you suggesting a way that I could effectively have a macro in Liquid as-is or a way that Liquid could be modified?
Liquid out of the box, no. Customizations to the include
tag could work, but I was just really identifying the concept behind how a macro
tag could re-use some existing components.
FWIW, I've implemented this in a soon-to-be-released C# port of Liquid:
{% macro mymacro arg1 arg2 %}
You said '{{ arg1 }}'. Also '{{arg2}}'.
{% endmacro %}
then
{% assign myvar = "there" %}
{% mymacro "hello" myvar %}
-->You said 'hello'. Also 'there'.
My implementation is a simplified version of "include", but without 'for' or 'with', and with positional rather than named arguments.
@mikebridge Looks great to me!
Any status on getting this included in a release of the main version?
Is there interest from maintainers in adding this in if someone were to work on it?
IMO, the macro tag is important to add. My use case is for Shopify themes. Maintaining logic in themes is extremely hard. In many cases I can't use Javascript because of SEO concerns. This usually leads to 100s of lines of imperative mess.
Being able to "componentize" my UI would result major reduction in duplicated code. Creating variations of templates using snippets is painful because I can't pass children elements to {% include %}
.
@Thibaut can you comment on that?
I don't think Liquid should support global macros (shared between templates), because it would significantly increase complexity (allowing anyone to basically make their own language on top of Liquid, thereby increasing the learning curve for anyone editing templates they didn't create), and add more dependencies between templates (load order).
Would local macros be of any use? I'm not convinced they'd be a good tradeoff against complexity and performance.
I can't pass children elements to
{% include %}
.
@sdn90 Not sure what you mean by that. Snippets inherit their parent template's variables. Doesn't that solve your problem?
You can essentially do the same thing with named parameters now:
{% include 'module', arg1: 'Hey', arg2: 'Thibaut' %}
@emichael can you create a gist of your 400 character lines example and what the alternative would look like with a macro?
@cshold I think that it wouldn't look all that different? There might be limitations to include
I'm not remembering. It's been a while. One benefit of macros in Jinja is that they can have kwargs with defaults; not sure if that's true of include
.
The biggest difference is that with include
, you're separating the definition from the use, which isn't always what you want. Think about the functions in your favorite programming language. If every function had to go in a different file, that would be a pain, right?
In Liquid, you should be able to define and use a subroutine in the same file, too. It lets template designers keep closely coupled bits of template all in the same place and doesn't force large projects to have huge numbers of snippet files lying around.
Just chiming in to say I too think this would be very useful.
@Thibaut http://jinja.pocoo.org/docs/dev/templates/#call
Let's say I want to create a Bootstrap component library. Using {% includes %}
, it's hard to nest/compose components
Bootstrap Panel http://getbootstrap.com/components/#panels-basic (note: never used Jinja before, don't know if this is valid)
Panel Macro
{% macro panel(color='default') %}
<div class="panel panel-{{ color }}">
<div class="panel-body">
{{ caller() }}
</div>
</div>
{% endmacro %}
Usage
{% call panel(color='success') %}
<h1>Successfully added to cart</h1>
<a href="/cart" class="btn btn-success">View cart</a>
{% endcall %}
Expected output
<div class="panel panel-success">
<div class="panel-body">
<h1>Successfully added to cart</h1>
<a href="/cart" class="btn btn-success">View cart</a>
</div>
</div>
@sdn90 This isn't something we're interested in supporting in Shopify, because it would significantly increase the complexity of templates.
When reading a template you'd see something completely different from the output. If you wanted to make a change to a theme you didn't create, you'd have to search for macros, figure out what they do, etc. Every theme would have a learning curve, and what you'd learn in one theme wouldn't apply to other themes. You also couldn't easily copy/paste code from one theme to another anymore.
Global macros would also introduce dependencies between templates that would be hard to resolve, as well as potential conflicts. {% include %}
tells Liquid exactly where to find the piece of code you want to include. A macro's name doesn't tell Liquid anything about where that macro is defined. So then you end up adding {% include 'macros' %}
to every template, complexity goes up another level, and performance takes a hit.
@Thibaut Sorry, but that's complete nonsense.
First of all, providing named functions (a feature of every modern programming language I can think of) doesn't mean the user has to use them. Secondly, you already have named functions. They just have the strange property that they have to go in a file of the same name. It is already the case in Liquid that the output of a template doesn't correspond 1-1 with the template itself (you also have variable assignment and usage). Thirdly, complexity isn't a function of the programming language being used; it's a function of the problem being solved. And as it stands, when users of Liquid need to manage some inherent complexity in certain problems, problems that require nested function calls etc., the only thing they can do is create a slurry of tiny snippet files instead of packaging the related functionality into one file (a much better way to manage complexity).
Also, about performance..... include
is a disk read. call
is an in-memory operation. Why would turning every call into a disk operation be faster?
You seem to have a certain idea about how this would be implemented, and I think it's a bit of a strawman. There are lots of ways to manage namespacing, conflicts, etc. But even something basic like the C preprocessor's include would be better than what you have.
@emichael Liquid is not a modern programming language, nor does it want to be. Liquid is designed to be approachable to (and usable by) a wide range of users, from designers to front-end developers to even non-technical people. Templates need to be easy to understand. Repeated code is perfectly ok in that context.
Adding global macros would be akin to encouraging every developer to make their templates more complex. We do not want this in Shopify, sorry. Keeping the majority of templates as simple as possible in the eyes of the many is more important to us than making the few templates that are doing complex things a bit simpler in the eyes of a technical audience.
To be clear, I'm not arguing that macros are not a good solution to manage complexity — only that they would significantly increase the average complexity of templates, and that is what matters to us.
If you're really interested in macros, why not simply extend your instance of liquid?
https://github.com/Shopify/liquid/wiki/liquid-for-programmers#create-your-own-tags
@dyspop Liquid <- Jekyll <- Github Pages
if you can stand to store your macros in the same file you can do:
{% capture panel %}
<div class="panel panel-|color|">
<div class="panel-body">
|caller|
</div>
</div>
{% endcapture %}
{% capture caller %}
<h1>Successfully added to cart</h1>
<a href="/cart" class="btn btn-success">View cart</a>
{% endcapture %}
{{ panel | replace: '|color|', 'success' | replace: '|caller|', caller }}
I didn't run this code, but I use this technique (as a concept) extensively, with a lot of enhancements that make it more manageable than calling like this, but this is the core concept.
@dyspop That's not what I'm suggesting. You can only do simple string replacement with that technique. You can't feed it more complex data structures, and you can't do any sort of control flow based on the "inputs."
not directly, no... fair enough.
@Thibaut I can see you are not creating complex templates and you never run in the issue of really missing macros, not having macros is a pain when you have a complex layout, you end up having several snippets with never ending if and capture or assign. The templates becomes unreadable. Cut and paste is not your friend. It's a pity because having simple macros would improve readability and have no performance impact compared to cascading if/capture/assign etc.. No need to figure out namespace or anything, you just include your macros in your base template, and then you can call them in your pages.
In my experience, the lack of macros in fact creates far more complexity in the readability of templates, as theme authors frequently have to resort to hacky, complex, and incomprehensible workarounds to get the job done where a short, simple macro could have solved the problem perfectly. I don't buy this argument that readability would suffer. I think the opposite would be the case. There are some truly horrible hacks (out of necessity) to be found in some third party themes that probably even the original author can barely follow. That hurts all of us who have to understand that code to make customizations for clients.
Jinja2 has the incredibly useful macro tag which allows quick and efficient parameterized code reuse. I've looked and asked around on Stack Overflow, and it doesn't look like there is anything equivalent in Liquid. There really needs to be.
As it stands, I'm having to write horrendous 400 character lines with no whitespace and lots of repeated elements (just with different variables).