Is your feature request related to a problem? Please describe.
If you opt to use React + JSX for your page templates, you don't have a clean way to use shortcodes. Sure, you can access shortcodes when mapping props to your component pages:
function getProps(eleventyData) {
const oneXEngineer = 1
return {
tenXEngineer: eleventyData.shortcodes.make10xEngineer(oneXEngineer)
}
}
// -> { tenXEngineer: 10 }
Example from our documentation
...but this requires you to use dangerouslySetInnerHtml
to use that markup. If you're attempting to use, say, the 11ty image plugin for every image on your site, this quickly becomes a lot of overhead!
It's equally frustrating if you want partial hydration while using JSX, Vue, or Svelte as your templating language. You'll need to call shortcodes.react
for every component you want to partially hydrate, including components nested within other components.
Describe the solution you'd like
I'd like to see a Shortcode
component I can easily use throughout my component pages. We'll use React as a stomping grounds for this, but the syntax would be similar across Vue and Svelte as well. For instance, say we have an image
shortcode declared in our config like so:
const Image = require("@11ty/eleventy-img");
function imageShortcode(src, alt, sizes, widths) {
let options = {
widths: widths,
formats: ['jpeg'],
};
Image(src, options);
let imageAttributes = { alt, sizes };
metadata = Image.statsSync(src, options);
return Image.generateHTML(metadata, imageAttributes);
}
module.exports = function(eleventyConfig) {
eleventyConfig.addShortcode('image', imageShortcode);
}
You could call out this shortcode using a component like so:
// 1. Import Shortcode from the slinkity package. I'd imagine a set of /client exports specifically for this
import { Shortcode } from 'slinkity/client'
export default function MyPage() {
return (
<div>
<Shortcode.image args={['/super/cool/image.jpg', 'Super cool image']} />
</div>
)
}
Pulling apart that syntax:
- Shortcodes would be available as object keys. Since our shortcode is declared as
image
, we call Shortcode.image
. Note: will need to investigate whether Vue and Svelte can do this.
- This will be a wrapper for dangerously setting inner HTML on a shortcode output. Yes, it really is that simplistic.
args
receives an array of unnamed arguments. In this case, /super/cool/image.jpg
will be the src
param passed to 11ty image, and Super cool image
will be the alt.
Named parameters?
I considered this for the React shortcode in particular. We'd allow something like this:
<Shortcode.react args={['/path/to/component.jsx']} hydrate="eager" secondProp={42} />
There's a couple problems with this though:
- We've introduced some "reserved" names that can't be used as named arguments. For instance, if your component ever wants
args
as a prop, you're kinda screwed!
- Using
args
just for the component path isn't very legible. It feels like args
is a half-solution we could handle better.
So, I'm considering a different option: named arguments will be an object {...}
as the last parameter to args
. Here's how we'd refactor that previous example:
<Shortcode.react args={['/path/to/component.jsx', { hydrate: 'eager', secondProp: 42 }]} />
This avoids any naming collisions and treats args
as the source of truth! We'll just need to be smart about mapping named arguments to nunjucks shortcodes, which was already an outstanding issue.
Major limitation: You can't use shortcodes in hydrated components!
Shortcodes can make any call that Node is okay with. In other words, reading and writing to the file system is fair game. This means shortcode components need to be processed serverside without accidentally shipping to the client. As I see things today, you'll need to avoid hydrating any component pages (or nested component shortcodes) if you want to use this Shortcode
component.
We'll need some linting to gracefully warn the user when they're hydrating a shortcode. We'll probably link them to the approach I mentioned at the top of this RFC.