eleventy icon indicating copy to clipboard operation
eleventy copied to clipboard

Expose nunjucks rendering context to shortcode

Open seancdavis opened this issue 4 years ago • 6 comments

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?

seancdavis avatar Jan 19 '21 12:01 seancdavis

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

zachleat avatar Nov 19 '21 20:11 zachleat

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

zachleat avatar Nov 20 '21 21:11 zachleat

@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 avatar Nov 24 '21 19:11 seancdavis

@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.

seancdavis avatar Jan 05 '22 11:01 seancdavis

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. https://github.com/11ty/eleventy/blob/f05caea9877a10c87ad61ecda1963a806d5ad68f/test/TemplateRenderPluginTest.js#L252

zachleat avatar Apr 15 '22 22:04 zachleat

@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 }}

seancdavis avatar Apr 21 '22 10:04 seancdavis

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;

zachleat avatar Dec 15 '22 17:12 zachleat

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 🤓

DeTeam avatar Apr 01 '23 17:04 DeTeam

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!

zachleat avatar Jul 10 '24 18:07 zachleat