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

Expose nunjucks rendering context to shortcode #1596

Closed
seancdavis opened this issue Jan 19, 2021 · 10 comments
Closed

Expose nunjucks rendering context to shortcode #1596

seancdavis opened this issue Jan 19, 2021 · 10 comments

Comments

@seancdavis
Copy link

Is your feature request related to a problem? Please describe.

I've build a server-side component system using Nunjucks. There is a method that dynamically generates shortcodes based on components within some given directory.

Then instead of doing this:

{% set props = { url: "/", label: "Go Home" } %}
{% include "button.njk", props = props %}

I can do this:

{% button url = "/", label = "Go Home" %}

That works well, but only when on level deep. I'm using the nunjucks library directly and so I don't have Eleventy's nunjucks rendering context. That makes it difficult to nest components or to share other customizations I've made elsewhere.

Describe the solution you'd like

I'd love to expose the nunjucks instance to the shortcode.

eleventyConfig.addNunjucksShortcode(() => {
  // access nunjucks from eleventy ...
})

Describe alternatives you've considered

I've begun playing around with other component libraries like Vue and React. But I really like the simplicity of a system with server-side templates like Nunjucks.

Additional context

I would be happy to work on the solution. I think I have an idea where to start, too. But curious if this is a good idea/direction for the project?

@zachleat
Copy link
Member

I believe this is a duplicate of #1584, can you confirm?

@zachleat
Copy link
Member

this.ctx will be available in Nunjucks shortcodes in 1.0.0-beta.9

@zachleat zachleat added this to the Eleventy 1.0.0 milestone Nov 20, 2021
@seancdavis
Copy link
Author

@zachleat If this.ctx has the ability for me to render nunjucks strings, then yes, it would solve the issue.

Longer explanation: My component shortcode calls nunjucks.renderString() directly. If this.ctx lets me hook into the the current nunjucks rendering process such that I get all the shortcodes and filters already loaded, then that's exactly what would solve this issue.

@seancdavis
Copy link
Author

@zachleat I upgraded to the latest beta to try this out and it's not solving the issue raised here.

this.ctx provides objects from the page that I could add into the shortcode, but not the context in which the page is being rendered by 11ty. After more research, I see that Nunjucks is calling the the environment.

I think what I'm looking for here is access to the njkEnv instance. That way I could bring along the renderString that already has the other custom tags loaded into it.

@zachleat
Copy link
Member

zachleat commented Apr 15, 2022

Circling back here, I wonder if you can make use of the Render plugin, which should give you access to all of your top level configuration stuff for free?

https://www.11ty.dev/docs/plugins/render/

Alternatively, I’ve also been working on a RenderManager (v2.0.0-canary.5+) class that let’s you use the render plugin programmatically (and also maintains your config stuff for free)

e.g.

test("Direct use of render string plugin, rendering Nunjucks (and nested Liquid)", async (t) => {

@seancdavis
Copy link
Author

@zachleat This is very cool and did not realize it was a thing. In my case, I'm rendering programmatically — having access to the render API is what should do the trick.

Here's a quick look at what's going on:

Dynamically-Generated Shortcodes

I have a shortcode like this:

{% button url = "/", label = "Go Home" %}

But this is generated dynamically be reading files in a _includes/components directory. It then runs the props through a data transformer (if it exists) to be able to do server-side processing, before eventually calling nunjucks.renderString() with the transformed props.

Current Issue

The system works well except that I then have to use transformers to render shortcodes used within the dynamic components.

For example, say I have a card that renders a button, where the button is also a dynamic shortcode. That requires the card to have a transformer where it does something like this:

module.exports = (input) => {
  let output = {}
  if (input.button) output.button = Component.render(input.button)
  return output
}

And then in the template, I render the string, rather than a shortcode.

{{ button | safe }}

@zachleat zachleat added the release: punted Punted out of a previous release milestone due to time constraints label Aug 15, 2022
@zachleat zachleat removed this from the Eleventy 2.0.0 milestone Dec 15, 2022
@zachleat zachleat added feature: 🪢 render plugin The Render plugin and removed release: punted Punted out of a previous release milestone due to time constraints template-language: nunjucks feature: 🥸 API consistency labels Dec 15, 2022
@zachleat
Copy link
Member

I do believe this is possible with the existing RenderPlugin, something like this:

const { EleventyRenderPlugin } = require("@11ty/eleventy");

const compileFile = EleventyRenderPlugin.File;
const compileString = EleventyRenderPlugin.String;

@DeTeam
Copy link

DeTeam commented Apr 1, 2023

Alternatively, I’ve also been working on a RenderManager

Thanks for posting about it here @zachleat. Even though I couldn't find any docs, ended up using RenderManager to render template strings coming from data, which I'm planning to dynamically compose from a headless CMS.

In case you're wondering, usage would look something like this:

{% for section in city.sections %}
  {% case section.type %}
    {% when 'content' %}
      <article>
        {% renderFromString section.content %}
      </article>
  {% endcase %}
{% endfor %}

And here's the rough version of my shortcode:

function addRenderFromString(eleventyConfig) {
  const rm = new RenderManager();

  eleventyConfig.on('eleventy.config', (cfg) => {
    rm.templateConfig = cfg;
  });

  eleventyConfig.addAsyncShortcode('renderFromString', async function(content) {
    const fn = await rm.compile(content, "liquid,md");
    return fn(this.ctx.getAll());
  });
}

I went with overwriting templateConfig, to not have to duplicate paths and general configuration 🤔 If there's a better solution, would be keen to learn about it 🤓

@zachleat
Copy link
Member

This is an automated message to let you know that a helpful response was posted to your issue and for the health of the repository issue tracker the issue will be closed. This is to help alleviate issues hanging open waiting for a response from the original poster.

If the response works to solve your problem—great! But if you’re still having problems, do not let the issue’s closing deter you if you have additional questions! Post another comment and we will reopen the issue. Thanks!

@groenroos
Copy link

Note that in 3.0.0 onwards, this.ctx.getAll() in the above code snippets should be just this.ctx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants