11ty / eleventy

A simpler site generator. Transforms a directory of templates (of varying types) into HTML.
https://www.11ty.dev/
MIT License
17.29k stars 496 forks source link

How to use nunjucks macro inside markdown files ? #613

Closed alienlebarge closed 4 years ago

alienlebarge commented 5 years ago

Hi !

Question

I would like to create a macro to embed images inside a <figure> with a <figcaption> tag.

I'm trying to use nunjucks macro inside my markdown files but it doesn't work. Am i doing it wrong or is it not possible ?

Context

I have created a src/_includes/macro.njk file with a test macro

{% macro figure(src) %}
<figure>
    <p>{{ src }}</p>
</figure>
{% endmacro %}

The macro is imported in the base template src/_includes/layouts/base.njk

{% import 'macros.njk' as macro %}

Then I use macro inside a markdown file src/_content/pages/styleguide.md but nothing happen

### Figures

{{ macro.figure("test") }}
danfascia commented 5 years ago

Have you enabled NJK as a template language acceptable for use in markdown files? within your elventyConfig ?

edwardhorsford commented 5 years ago

I don't think you can import your macros in the base template.

I have to import my macros on every page to work around this.

roobottom commented 5 years ago

@edwardhorsford How are you importing macros from a md file? There seems to be only a subset of tags that work with the passthrough code and from doesn't seem to be one of them.

edwardhorsford commented 5 years ago

@roobottom

I import all macros under a macro namespace rather than calling each one individually with from.

Sample from one of my md files:

---
title: This is the title
date: 2019-09-07
tags: 
- foo
- bar
- baz
---

{% import "includes/macros.njk" as macro with context %}

Some content here

And then use a macro like this: {{ macro.macroName() }}

zachleat commented 4 years ago

Thanks @edwardhorsford!

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 I will reopen the issue. Thanks!

julientaq commented 3 years ago

@edwardhorsford thanks for sharing this solution. I’m trying to implement something along those lines, but i get lost a bit. Would you mind sharing how you create a macro namespace? Or how you macros.njk file is done?

edit i think i found it: https://github.com/11ty/eleventy/issues/212#issuecomment-681924372

julientaq commented 3 years ago

So i manage to cheat my way to add the call to the macros when the collection is built: 

let filters = `{% import "../../layouts/macros.njk" as macro with context %}`

eleventyConfig.addCollection("posts", collection => {
    collection = collection.getFilteredByGlob("src/content/posts/**/*.md");
    collection.forEach(el => {

      // add macros on the fly to the collection

      // that was my first try 
      el.template.inputContent = el.template.inputContent.replace('---\n\n', `---\n\n${filters}\n`)

      // i think this one is the important one that eleventy use to build the layout
      el.template.frontMatter.content = `${filters}\n${el.template.frontMatter.content}`

    })
    return collection;
  });

I wish i could use an absolute link to the macros, but so far this should be working :)

pdehaan commented 3 years ago

I wish i could use an absolute link to the macros

Would it need an absolute path if you put the macros.njk file into the _includes/ directory?

julientaq commented 3 years ago

oh my! they were in the includes folder (as per my configuration), but i got lost and wanted to make it work from the source.

So absolute actually work, i can symply use let filters = {% import "macros.njk" as macro with context %}

Thanks a lot @pdehaan! this is gonna remove a lot of run around!

tedw commented 2 years ago

@julientaq Great idea! This has been one of the few annoyances I’ve had with Eleventy. This will make is so much easier for clients to add new content without having to remember to import macros.

Here’s my slightly refactored code fwiw:

// Automatically import macros on every page
// (otherwise we need to manually include on each page that uses them)
// https://github.com/11ty/eleventy/issues/613#issuecomment-968189433
config.addCollection('everything', (collectionApi) => {
  // Note: Update the path to point to your macro file
  const macroImport = `{% import "macros/index.njk" as macro with context %}`;
  // Note: Update the pattern below to include all files that need macros imported
  // Note: Collections don’t include layouts or includes, which still require importing macros manually
  let collection = collectionApi.getFilteredByGlob('src/**/*.njk');
  collection.forEach((item) => {
    item.template.frontMatter.content = `${macroImport}\n${item.template.frontMatter.content}`
  })
  return collection;
});

FYI I use a single file to define all of my macros (macros/index.njk) so only a single import is required:

{% macro foo(params) -%}
  {% include "./foo.njk" %}
{%- endmacro %}

{% macro bar(params) -%}
  {% include "./bar.njk" %}
{%- endmacro %}
edwardhorsford commented 10 months ago

I'm trying the above approach and whilst it seems to work for individual pages, it's crashing my build if applied to a paginated template that generates lots of pages - my tag pages. Has anyone else experienced similar?

The error is [11ty] RangeError: Maximum call stack size exceeded (via Template render error)

If I truncate the paginated data to 100 items it renders ok.


Edit: for now, I'm going to exclude all paginated pages - this seems to work for blog posts, which is the bulk of my files anyway. Would be nice if there was a more 'official' way of doing this / one that could apply to paginated pages too.

brycewray commented 9 months ago

For what it’s worth, code based on the excellent example shown by @tedw above doesn’t work in the current alpha of Eleventy 3.0 (3.0.0-alpha.4 as of this writing); it errors out with, among other messages, the following (here, the macro name is liteYT):

Error: Unable to call `macro["liteYT"]`, which is undefined or falsey (via Template render error)

The same macro works fine in Eleventy 2.x.

cc: @zachleat

groenroos commented 4 months ago

Ran into the same issue with 3.0.0-alpha.14, regarding the following line from @tedw's snippet above.

item.template.frontMatter.content = `${macroImport}\n${item.template.frontMatter.content}`

As of 3.0.0-alpha.14, Eleventy quits with:

[11ty] Unfortunately you’re using code that monkey patched some Eleventy internals and it isn’t async-friendly. Change your code to use the async `read()` method on the template instead! (via Error)
zachleat commented 4 months ago

Working example posted here: https://github.com/11ty/eleventy/issues/3345#issuecomment-2211172226

zachleat commented 4 months ago

Fix posted here #3345 and #188