Closed Heydon closed 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.
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 %}}
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
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
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.
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.
@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/
@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.)
@cferdinandi It's in the Cupper repo. It's really simple: https://github.com/ThePacielloGroup/cupper/blob/master/themes/cupper/layouts/shortcodes/snippet.html
Thanks @Heydon !
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.
@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!
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.
@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.
Anyways, this should already be handled by Engine & Universal Filters, provided we build wider support for all engines.
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.
Making progress! 😎
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.
v0.5.0 is out, with Shortcodes!
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.
@kleinfreund can you open a new issue with this comment? Thanks!
@zachleat Thanks, now using them in anger!
@heydon 👍 but oh no, why anger??
@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>`;
});
@Heydon ugh yeah—my bad. You’re using Liquid right? That’s filed and fixed for 0.7.0 at issue #347.
@zachleat Fixed already? How can I complain? Cheers
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.
would run a script called
shortcodeName.js
that takes "foo" and "bar" then exports/returns markup with those values processed somehow.