11ty / eleventy

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

Shortcodes #13

Closed Heydon closed 6 years ago

Heydon commented 6 years ago

I'm wondering how shortcodes might be handled. I really like how Hugo uses shortcodes (https://gohugo.io/content-management/shortcodes/) but they would be even better if they could be run through node scripts (Hugo is written in Golang and has no support for this currently).

Essentially, I'd expect to be able to write little snippets that take arguments, referencing a script that outputs markup and injects it back as part of the processed template. E.g.

{{shortcodeName arg1="foo" arg2="bar"}}

would run a script called shortcodeName.js that takes "foo" and "bar" then exports/returns markup with those values processed somehow.

zachleat commented 6 years ago

Very interesting! I like this idea. Are you tied to any specific templating engine? Looks like it’d be pretty easy to make some minor modifications here to get something working in Nunjucks using function calls and/or macros. I could get data imports working with .js files in addition to .json to facilitate this.

https://mozilla.github.io/nunjucks/templating.html#macro

{{ field('user') }}
{{ field('pass', type='password') }}

which works well with import for namespacing: https://mozilla.github.io/nunjucks/templating.html#import

{% import "forms.html" as forms %}

{{ forms.label('Username') }}
{{ forms.field('user') }}

Lemme think about how to do this a little bit more.

Heydon commented 6 years ago

No, not tied to anything really! Just like the concept. The basic function call/macro mechanism sounds like the right base.

NB Hugo also allows this kind of form, where the inline content can be processed as a further argument to the shortcode function/template:

{{% shortcodeName %}}
   Inner content
{{% /shortcodeName %}}
zachleat commented 6 years ago

Also considering something more future-compatible with some form of (no JS at runtime) web components. I think it’s very important to maintain a 0-JS output by default here. I don’t want this to turn into a JS framework.

Discussion here https://twitter.com/zachleat/status/947206049228083201

Highlights https://w3c.github.io/webcomponents/spec/custom/# https://developers.google.com/web/fundamentals/web-components/customelements https://github.com/skatejs/skatejs/blob/master/packages/ssr/README.md

zachleat commented 6 years ago

Sorry to bikeshed in public but this looks very interesting. I’m leaning towards postHTML here, especially if I can post-process templates and keep this layer rendering engine agnostic:

https://github.com/posthtml/posthtml-custom-elements

A combination of custom elements and this would be quite interesting, I think: https://github.com/island205/posthtml-web-component

Heydon commented 6 years ago

Boo to private bikesheds. Bike storage for the people!

So, if I get the implication correctly, one could use this to create custom elements, that could be inserted into the markdown (or HTML) and expanded as a shortcode might be. Sounds good to me.

I think it’s very important to maintain a 0-JS output by default here. I don’t want this to turn into a JS framework.

Right, I was only interested in JS running on the server (a node script) per shortcode / custom element, as required. Nothing on the client.

cferdinandi commented 6 years ago

One area where Hugo falls flat that I think would be a big win if you can swing it: Hugo has partials and shortcodes, and they're two separate things.

You can reference a partial in a shortcode but not vice-versa, and partials can't accept arguments or variables.

Being able to use repeatable code (with some conditional information) in both content and templates would be huge.

I have a lot of content on my site that's similar but with small variations, and shortcodes help me keep the whole thing a lot more DRY and manageable.

Heydon commented 6 years ago

@cferdinandi Right! I actually wrote a special shortcode for pulling in partials by filename to get round this: https://thepaciellogroup.github.io/cupper/patterns/writing/snippets/

cferdinandi commented 6 years ago

@Heydon That looks awesome! Do you mind sharing the code behind that shortcode? (If its on that page, I'm sorry, but I can't find it.)

Heydon commented 6 years ago

@cferdinandi It's in the Cupper repo. It's really simple: https://github.com/ThePacielloGroup/cupper/blob/master/themes/cupper/layouts/shortcodes/snippet.html

cferdinandi commented 6 years ago

Thanks @Heydon !

zachleat commented 6 years ago

Starting to look at this closer @cferdinandi, can you elaborate on a use case where you’d want to pass data from the shortcode to the partial? Trying to wrap my head around that.

cferdinandi commented 6 years ago

@zachleat For me, personally, it's less about passing info from a shortcode to a partial and more that I don't understand why Hugo treats them as two separate entities.

This is perhaps a bias from my WordPress background, but I use repeatable content in both theme/template files and content. For example, I use an encoded version of my email address to minimize spammer harvesting. Sometimes that's embedded in content itself (like on my about page), while other times I use it in a template or theme file (hypothetically, like my footer).

Today in Hugo, that's a shortcode and a partial, because you need both for both use cases. They each return a link element with the encoded email address as both the href and the link text.

The shortcode version also allows me to pass in an optional query string to autopopulate a subject and body to the mailto: link—something I can't do with the partial.

In an ideal world, there would be just one shortcode/partial type that would work everywhere and allow variables to get passed in.

Did I explain that well? If I did a bad job explaining anything, please ask away!

eeeps commented 6 years ago

Just wanted to pop in here and say I had a brief discussion with @philhawksworth about this issue at VueConf a week+ ago and he opined that this kind of functionality might be best left to the templating engines. I think the example he used was Nunjucks macros? https://mozilla.github.io/nunjucks/templating.html#macro

EDIT: doh Zach said the same thing above back in December.

chrisdmacrae commented 6 years ago

@zachleat @cferdinandi I've done this in Hugo, for Cloudinary support.

I created a series of Hugo partials that generated the Cloudinary URLs and handled transforms. The shortcodes then just specified their shortcode logic (params, etc) and then passed the logic to the partials.

This is necessary in Hugo, because you need to have a template file that encapsulates the shortcode logic. I don't think making shortcodes === partials would ever make sense in Hugo, as it would create a really ugly interface.

However, in Eleventy you could have that all encapsulated in the plugin/config file, and just have a template that takes the standard data.

chrisdmacrae commented 6 years ago

Anyways, this should already be handled by Engine & Universal Filters, provided we build wider support for all engines.

dillonbheadley commented 6 years ago

This all sounds great! FYI to consider: an alternative to postHTML is Reshape. I too am a fan of Hugo snippets but feel like they missed an opportunity to combine them with partials. You have to have code in 2 places if you want a partial and a snippet that outputs the same thing, or as mentioned a snippet that just then uses a partial internally and passes along the props.

zachleat commented 6 years ago

image

Making progress! 😎

zachleat commented 6 years ago

After reading all of the feedback about what y’all would like shortcodes to do (and making a working demo in Liquid), I think the best implementation here is going to wrap a nicer API around template engine Custom Tags. This should give you full access to template engine features (local variables, full tag access inside the shortcode, scoping, etc).

While it isn’t template engine agnostic, I think it’ll be far more useful.

zachleat commented 6 years ago

v0.5.0 is out, with Shortcodes!

https://v0-5-0.11ty.io/docs/shortcodes/

kleinfreund commented 6 years ago

I’m currently writing a shortcode for post excerpts/summaries:

{%- for post in posts -%}
<article>
  {% excerpt post %}
</article>
{%- endfor -%}

Something along the lines of this:

module.exports = function (eleventyConfig) {
  eleventyConfig.addLiquidShortcode('excerpt', post => {
    if (!post.templateContent) {
      return;
    }

    const content = post.templateContent;
    const excerptEnd = content.includes('<!--more-->')
      ? content.indexOf('<!--more-->')
      : content.indexOf('\n\n');

    return content.substring(0, excerptEnd).trim();
  });

  // …
};

Now, it seems like post.templateContent doesn’t always contain what I want. Sometimes, this property doesn’t exist. How would I go about this? Do I use post.inputContent and strip any front matter myself? Is that safe? Seems odd.

zachleat commented 6 years ago

@kleinfreund can you open a new issue with this comment? Thanks!

Heydon commented 5 years ago

@zachleat Thanks, now using them in anger!

zachleat commented 5 years ago

@heydon 👍 but oh no, why anger??

Heydon commented 5 years ago

@zachleat LOL

Actually, I'm having a bit of trouble with strings. In the following, just 'foo' would work, but 'foo bar' makes the template renderer crash?

{% note 'Foo bar' %}

Hello world!

Bye.

{% endnote %}

Here's the shortcode def (I had to put all the code on one line or it gets escaped):

  eleventyConfig.addPairedShortcode('note', function (content, title) {
    return `<aside><h4>${title}</h4>${content}</aside>`;
  });
zachleat commented 5 years ago

@Heydon ugh yeah—my bad. You’re using Liquid right? That’s filed and fixed for 0.7.0 at issue #347.

Heydon commented 5 years ago

@zachleat Fixed already? How can I complain? Cheers