mozilla / nunjucks

A powerful templating engine with inheritance, asynchronous control, and more (jinja2 inspired)
https://mozilla.github.io/nunjucks/
BSD 2-Clause "Simplified" License
8.48k stars 635 forks source link

Is there a way to precompile a template into another template string? #1391

Open lsycxyj opened 2 years ago

lsycxyj commented 2 years ago

I want to replace those "extends", "block" etc. to the real content, since I want to render "chunked" parts of the template by some custom seperaters. However, I don't find any way to render a template string into another template string, or any other ways to render "chunked" parts natively.

ArmorDarks commented 2 years ago

You can render chunks individually by passing in context and env you want to render them with.

Here's an example of how Nunjuks used to render templates bits in Front Matter blocks. It traverses the data and renders each property of the object individually with the same env and context which is used for the page.

lsycxyj commented 2 years ago

@ArmorDarks I'm afraid I don't understand. Where's the example?

ArmorDarks commented 2 years ago

Sorry, messed up the link in the reply. Fixed. Check it out again please

lsycxyj commented 2 years ago

@ArmorDarks I'm sorry but I still don't understand. I don't see the template example and I don't know how it works. For example, how can I render "chunked" parts with following templates?

{# detail.tpl #}
{% extends 'base.tpl' %}
{% block header %}
<script src="some_resource"/>
<!-- chunked split point here  -->
{% endblock %}

{% block body %}
<div>some long content</div>
{% endblock %}
{# base.tpl, and used by many other templates #}
<html>
<head>
<script src="some_global_resource"/>
{% block header %}
{% endblock %}
</head>

<body>
<div>some other things</div>
{% block body %}
{% endblock %}
</body>
</html>
ArmorDarks commented 2 years ago

It seems like your use case is quite different. Can you provide a better example, what exactly you're trying to achieve? Not sure what <!-- chunked split point here --> means here - what parts you're trying to render?

If you wanted to just insert something at <!-- chunked split point here -->, you can use another block instead, or macros.

lsycxyj commented 2 years ago

@ArmorDarks I have a bunch of long templates, which depends on some time costing data fetching. I want to output some content to let the client load the indepent resource and render indepent content ahead before the rest parts are ready.

ArmorDarks commented 2 years ago

So what's the actual problem here? If you have already loaded chunk of the template that is renderable (note that you can't split it where your <!-- chunked split point here --> denoted since it will produce an invalid template - endblock will be unclosed), then just use Nunjucks env.render function to render the current template and output it to the client, then render next chunk and append to existing one.

Not sure though it's completely doable, because you will have to always keep track of whether what you loaded is already actually renderable. Besides, some chunks can contain variables, blocks, or macros declaration - without them, you won't be able to render chunks that are actually containing them.

So, the short answer - no, there's no built-in way to do such a thing.

It also sounds like you'd better do that client-side. It's much more trivial to load data in an async manner and render it in parts only when it's available, like with React Suspense

lsycxyj commented 2 years ago

@ArmorDarks I mean if nunjucks can't be rendered by chunks, I wonder if there's a way to precompile the templates like this, which is a valid template:

<html>
<head>
<script src="some_global_resource"/>
<script src="some_resource"/>
<!-- chunked split point here  -->
</head>

<body>
<div>some other things</div>
<div>some long content</div>
{{someData}}
</body>
</html>

Rendering by client-side works but I think it's not good enough in my case, since compared with chunked rendering, it costs 1 rtt more.

ArmorDarks commented 2 years ago

You can do something like this

<html>
<head>
<script src="some_global_resource"/>
<script src="some_resource"/>
</head>

<body>
{{varToBeRenderredImmediately}} <!-- a random far for example - it will be rendered immediatelly  -->
{{'{{someData}}'}} <!-- note that it's in quotes now, so it will produce template instead of the result  -->
</body>
</html>

That will render a valid template which you can render again later

<html>
<head>
<script src="some_global_resource"/>
<script src="some_resource"/>
</head>

<body>
Some Random Var Value
{{someData}} <!-- now it's a proper part for next rendering  -->
</body>
</html>
lsycxyj commented 2 years ago

@ArmorDarks Impressive! However, the template itself ({{'{{someData}}'}}) shouldn't be output to the client side. I suppose if I want to achieve this, I will have to render the template multiple times and get the parts I want.