Closed wrapperup closed 1 year ago
In some sense what you want to accomplish is already supported internally. When a template extends another one, it basically needs to find all the blocks. I did not think too much about the details yet, but it should be doable with the current semantics of the language. I did not think this would require any special code other than maybe rethinking how RenderParent
works.
Today if {% extends %}
appears anywhere in the template, then the code generator emits a RenderParent
at the very end which loads the parent instructions and resets the pc
to 0
. In some sense this could be implied runtime behavior (basically it means that parent_instructions
was loaded). With that change and maybe an extra parameter to the vm it should be possible to eval toplevel with CaptureMode::Discard
. Then once it's done you have all the blocks loaded as they should be in which case you just trigger eval as Instruction::CallBlock
would do.
This would definitely require some minor refactoring, but it should not require you to do anything fancy that the engine doesn't already have to do in one form or another.
Instead of using this
render_block
bodge method, I think it may be a better idea for Source to use some kind of syntax liketemplate.html#block
(suggested by the article) and just have users render normally. This way, it is supported by default out of the box without any additional API on templates, and Source can deal with compiling and caching the block fragments. What do you think?
I think I would prefer an explicit render_block
method. In Jinja2 the blocks are exposed on the template objects under .blocks
and they can be invoked from there which is how I assume the linked Python thing works.
This is really useful with htmx. Thank you for implementing it!
Note that I changed the interface for block rendering significantly on main
for MiniJinja 1.0. I will try to get a beta out before the weekend so that folks can give feedback on the new interface.
I gave v1.0.0-alpha.2 a try, and my usage of blocks went from:
let rendered = template.render_block(block, context)?;
To:
let rendered = template.eval_to_state(context)?.render_block(block)?;
Works for me. I have no strong feelings about the change as a user; it's not a problem for my usage of blocks.
I have noticed one ergonomic issue with blocks (unrelated to v1.0). Say I have a template like this:
{{ foo.bar }}
{% block baz_block %}
{{baz}}
{% endblock %}
And I attempt to render only baz_block
:
let rendered = tmpl.eval_to_state(context!(baz => "baz"))?.render_block("baz_block")?;
That blows up with the following error:
----------------------------------- foo.txt -----------------------------------
1 > {{ foo.bar }}
i ^^^^^^^ undefined value
2 |
3 | {% block baz_block %}
4 | {{baz}}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
No referenced variables
-------------------------------------------------------------------------------
Ideally this would succeed, because I only want to render baz_block
which does not use foo.bar
. For now I'm working around this by adding safeguards like {% if foo is defined %}
.
I can raise an issue if you think this is a legitimate problem.
Currently it works by evaluating the entire template with your context before rendering out block, so things like inheritance work correctly. It might be possible to fix, I'm not sure how important it is to know if an expression will fail during the eval-only stage (if there's some possible side-effects there I'm not aware of, or if it can even be skipped), and just pass that onto the actual render eval.
There is also the less ideal temporary solution of using Environment::set_undefined_behavior
to suppress these errors.
Feel free to make a new issue for it!
In essence, this feature would allow you to render only a block from a template. This is great in the context of htmx where you typically want to just re-render a small section of html to send down the wire
(See https://htmx.org/essays/template-fragments/ and https://github.com/sponsfreixes/jinja2-fragments).
Example:
If I ran
template.render_block("myblock", &ctx)
, I would getMy current implementation of this feature adds a
render_block
method, but it's quite a bodge since it uses the pre-compiled blocks already part of the template. They are also missing all the necessary block instructions, so things likesuper()
don't work.Before I make a PR with some of the proposed changes below, I have some questions about the design of it:
super()
, compiling block fragments would need to be supported at the compiler level (and/or right above it inSource
). Assuming I just want to render the block with all the necessary instructions, what instructions could sensibly be stripped? EmitRaw seems like an obvious one, but I'm not super familiar with it all.render_block
bodge method, I think it may be a better idea for Source to use some kind of syntax liketemplate.html#block
(suggested by the article) and just have users render normally. This way, it is supported by default out of the box without any additional API on templates, and Source can deal with compiling and caching the block fragments. What do you think?I'd love to hear your thoughts. In any case, thanks for the great library!