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

allow registering Nunjucks globals #1060

Merged
merged 1 commit into from
Nov 15, 2020
Merged

allow registering Nunjucks globals #1060

merged 1 commit into from
Nov 15, 2020

Conversation

lxg
Copy link

@lxg lxg commented Mar 29, 2020

This PR allows registering Nunjucks globals through the eleventy config. The purpose of this is mainly to allow creating global template functions where filters and tags aren’t enough.

A simple example:

in .eleventy.js

eleventyConfig.addNunjucksGlobal("foobar", (str) => `foo ${str} bar`)

usage in a template file:

{{ foobar("hello world") }}

Result:

foo hello world bar

@lxg
Copy link
Author

lxg commented Mar 29, 2020

btw, fixes #495

@nhoizey
Copy link
Contributor

nhoizey commented Mar 30, 2020

@lxg are these like Nunjucks macros?

@lxg
Copy link
Author

lxg commented Mar 30, 2020

@nhoizey Not exactly. While they both behave like funktions, Nunjucks macros are defined with Nunjucks syntax, whereas you would write such a global as Javascript and just expose the functionality to Nunjucks.

For example:

{% macro hello(who) %}
    Hello {{ who }}
{% endmacro %}

vs.

eleventyConfig.addNunjucksGlobal("hello", who => `Hello ${who}`)

Both would expose the same functionality in this simple example, for instance:

{{ hello("world") }}

But obviously native JS is more powerful in regard to syntax and context.

Also, and this is my primary reason for this PR, it gives you more power than filters and tags in Nunjucks. In my scenario, there are translation functions that take parameters and are wrapped in sprintfs … something you can hardly express with filters.

@nhoizey
Copy link
Contributor

nhoizey commented Mar 30, 2020

Ok, I understand the difference now, thanks!

It looks like it could fix my issue with tags not getting "external" data, leading to the need to pass information from one to another.

For example, I use this macro in my article template:
https://github.com/nhoizey/nicolas-hoizey.com/blob/master/src/_includes/macros/articleCard.njk

Which calls this other macro, which is called also for other content types:
https://github.com/nhoizey/nicolas-hoizey.com/blob/master/src/_includes/macros/meta.njk

I have to pass data and set default values for different use cases, that's not really easy and clean.

Globals look better.

@Dexus Dexus mentioned this pull request May 12, 2020
@Dexus
Copy link

Dexus commented May 13, 2020

ping

@lxg
Copy link
Author

lxg commented May 13, 2020

@zachleat any opinions regarding this PR?

@spl
Copy link

spl commented Sep 9, 2020

I was just looking for addGlobal, too. addNunjucksGlobal seems like a useful and straightforward addition. But if this PR isn't accepted, I think the problem can also be solved by adapting the Use Your Nunjucks Environment instructions with this .eleventy.js:

let Nunjucks = require('nunjucks')

module.exports = (config) => {
  let nunjucksEnvironment = new Nunjucks.Environment()
  nunjucksEnvironment.addGlobal('foobar', (str) => `foo ${str} bar`)
  config.setLibrary('njk', nunjucksEnvironment)
}

@edwardhorsford
Copy link
Contributor

I do what @spl suggested - making my own environment and passing globals to it.

@zachleat zachleat merged commit 84f8229 into 11ty:master Nov 15, 2020
@zachleat zachleat added this to the Eleventy 1.0.0 milestone Nov 15, 2020
@zachleat
Copy link
Member

Shipping this with 1.0, thank you!

@gremo
Copy link

gremo commented Dec 16, 2020

Any chance to use it with the current version of 11ty? Thanks...

@Ryuno-Ki
Copy link
Contributor

@gremo You could check out via git branch (but that'll pull in other changes, too).

@pdehaan
Copy link
Contributor

pdehaan commented Jul 17, 2021

Q: Does eleventyConfig.addNunjucksGlobal() support passing direct values (ie: eleventyConfig.addNunjucksGlobal("foobar", "hello!")) or do I have to wrap it in a function?

This seems to work in Nunjucks (see RunKit):

const nunjucks = require("nunjucks");
const env = new nunjucks.Environment();
env.addGlobal("cats", 42);
const res = env.renderString('{{ username }} owns {{ cats }} cats!', { username: 'James' });
console.log(res); // Output: "James owns 42 cats!"

But I get errors in Eleventy:

// .eleventy.js snippet
eleventyConfig.addNunjucksGlobal("cats", 42);
{{ cats }}

OUTPUT

function (...args) {
      benchmark.before();
      let ret = callback.call(this, ...args);
      benchmark.after();
      return ret;
    }

But then if I try:

{{ cats() }}

I get a build error with the following:

Problem writing Eleventy templates: (more in DEBUG output)
> Having trouble rendering njk template ./src/pages/home.njk

`TemplateContentRenderError` was thrown
> (./src/pages/home.njk) [Line 4, Column 7]
  TypeError: callback.call is not a function

`Template render error` was thrown:
    Template render error: (./src/pages/home.njk) [Line 4, Column 7]
      TypeError: callback.call is not a function
        at Object._prettifyError (/private/tmp/11ty-nunjucks-globals/node_modules/nunjucks/src/lib.js:36:11)
        at /private/tmp/11ty-nunjucks-globals/node_modules/nunjucks/src/environment.js:563:19
        at Template.root [as rootRenderFunc] (eval at _compile (/private/tmp/11ty-nunjucks-globals/node_modules/nunjucks/src/environment.js:633:18), <anonymous>:42:3)
        at Template.render (/private/tmp/11ty-nunjucks-globals/node_modules/nunjucks/src/environment.js:552:10)
        at /private/tmp/11ty-nunjucks-globals/node_modules/@11ty/eleventy/src/Engines/Nunjucks.js:451:14
        at new Promise (<anonymous>)
        at /private/tmp/11ty-nunjucks-globals/node_modules/@11ty/eleventy/src/Engines/Nunjucks.js:450:14
        at Template._render (/private/tmp/11ty-nunjucks-globals/node_modules/@11ty/eleventy/src/TemplateContent.js:310:28)
        at async Template.getTemplateMapContent (/private/tmp/11ty-nunjucks-globals/node_modules/@11ty/eleventy/src/Template.js:953:19)
        at async TemplateMap.populateContentDataInMap (/private/tmp/11ty-nunjucks-globals/node_modules/@11ty/eleventy/src/TemplateMap.js:459:39)

Wrote 0 files in 0.07 seconds (v1.0.0-canary.39)

Workaround seems to be to always wrap the global value in a function, which might be inconsistent w/ Nunjucks behavior:

// .eleventy.js snippet
eleventyConfig.addNunjucksGlobal("cats", () => 42);

Then use {{ cats() }} which will output 42 as expected.

@zachleat
Copy link
Member

zachleat commented Aug 9, 2021

I pushed a fix and tests for the issue with using a literal function @pdehaan #1060 (comment)

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.

9 participants