Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix included macros aren't working #632

Merged
merged 2 commits into from
Mar 18, 2024

Conversation

gebolze
Copy link
Contributor

@gebolze gebolze commented Mar 13, 2024

Fix for #551

Macros included using the include statement are always undefined because they are defined within the scope of the include statement. To prevent this, this pull-request changes the implementation of the macro statement to always register the macro to the root scope.

@sebastienros
Copy link
Owner

I was thinking about how this could be an issue when multiple files are included, or when a macro would then overwrite and existing property with the same name (from another template). Or you think you are in an isolated template but your local macro will replace the RootScope property with the same name.

I checked Jinja (where I think I took the idea from, and why this feature is behind a flag as it's not standard). https://jinja.palletsprojects.com/en/2.10.x/templates/#import
What they do is make it explicit, so if you want to import the macros from a template you just say it, and pass a variable name that will be in your current scope.

Do you think this could be a better solution? Local macros don't have to be imported so there is no breaking change. It would only require a new tag, copied from include and render. If so keep this PR around in case the other way is too hard to implement.

@gebolze
Copy link
Contributor Author

gebolze commented Mar 14, 2024

I've updated the PR. I've changed the implementation and introduced a new from tag that allows importing macros from external templates.

{% from 'file' %} imports all defined macros.
{% from 'file' import foo, bar %} imports only foo, bar.

I've implemented the TagStatement such that it doesn't render anything to the output and only import macros to the local scope. This is a bit opinionated, but I think the from tag should not be used to for scenarios where include or render could be used.

Copy link
Owner

@sebastienros sebastienros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was not expecting anybody to be able to do this without asking more questions or saying "it's too complicated", good job!

Give me some time to look again into Jinja and if their way for naming macros has advantages. I don't think it would be more complex than what you implemented, just checking the user cases and how simple the usage is.

Fluid/Ast/FromStatement.cs Show resolved Hide resolved
Fluid/Ast/FromStatement.cs Outdated Show resolved Hide resolved
@@ -934,7 +934,18 @@ Now `field` is available as a local property of the template and can be invoked
{{ field('pass', type='password') }}
```

> Macros need to be defined before they are used as they are discovered as the template is executed. They can also be defined in external templates and imported using the `{% include %}` tag.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is not changing the behavior wrt includes, so this will still work, right? Not saying this should be removed. I also remember that include is not recommended in Liquid (because like in this case it leaks to the caller).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR is not changing the behavior wrt includes, so this will still work, right?
Yes behavior of includes isn't changed by this PR. But I would still say that include can't be used to add external macros to the local scope. The following example won't work:

functions.liquid

{%- macro hello_world() -%}
Hello world!
[%- endmacro -%}

template.liquid

{%- include 'functions' %}
{{ hello_world() }}

This will not render Hello world!

Actually the opposite will work.
You can access a marco declared in a template from the included file.

Hope this answers your question.

@sebastienros
Copy link
Owner

This is a bit opinionated, but I think the from tag should not be used to for scenarios where include or render could be used.

Are you talking about not rendering the imported template? Is so then yes definitely should not render, just import functions from it.

@sebastienros
Copy link
Owner

Done with checking Jinja again. Can you confirm that what you did is compliant with Jinja's syntax, but you just don't handle the as keywork either in the from or the import expressions? But this could be added later.

Examples:

{% from 'forms.html' import input as input_field, textarea %} -> imports input but names it input_field locally, then imports textarea

{% import 'forms.html' as forms %} -> imports all function but as forms.input and forms.textarea.

@sebastienros sebastienros merged commit e9cd780 into sebastienros:main Mar 18, 2024
1 check passed
@gebolze gebolze deleted the feature/issue-551 branch March 20, 2024 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants