Optimize your img tags with Eleventy Image and WebC
October 23, 2022 - 10 min
I am very excited about WebC and couldn’t wait to try out the corresponding Eleventy plugin.
The first use-case I had in mind was to use Eleventy Image for the native HTML img
tag, that can be generated from Markdown and without shortcode. I had done this before, for my unpublished blog rewrite that’s been waiting to be released for ever now, through hooking up Eleventy Image into a markdown-it plugin. It wasn’t pretty as markdown-it plugins are pretty alien to me.
But now, this can be done “natively”, à-la-Cloudflare Workers for your native HTML elements!
UPDATE (04/11/2022): After sharing this article on Twitter, Zach Leatherman pointed out there should be an easier solution. It didn’t work as expected at first, which put me on a journey to fix the underlying issue in @11ty/webc
. I’m very pleased to share that my first contribution to the project was accepted and landed in v0.6.0. As a result, the content of the last section was updated to remove the dirty data file hack with _data/Image.js
: your image optimization logic can now truly live in a single file.
The documentation for WebC mentioned this use-case exactly, in the shape of this innocent puzzle:
“Free idea: use the Eleventy Image plugin to return optimized markup”
So it should be easy, right?
Remember this lad?
I have discovered a truly remarkable proof of this theorem which this margin is too small to contain. Pierre de Fermat, cca. 1637
No need to wait 3 centuries for the answer this time, because it was ultimately not so hard… But I did struggle and wrestle with it for a few hours :D
Anyway, hope that can be useful to someone.
If you want to see the finished implementation, you can get it on GitHub: RobinCsl/11ty-webc-img-demo.
Set up a WebC-powered Eleventy project
or take a shortcut by cloning this repo and going to the 01-initial
folder.
Initialise the project
Let’s start with an empty folder and add a basic package.json file:
mkdir 11ty-webc-img
cd 11ty-webc-img
npm init -y
We need to install the latest canary release (2.0.0-canary.16 at the time of writing) of Eleventy as well as the WebC plugin:
npm install @11ty/[email protected] @11ty/eleventy-plugin-webc
Configure Eleventy
We will now configure Eleventy to use the WebC plugin by creating a .eleventy.js
file with the following content:
const webc = require("@11ty/eleventy-plugin-webc");
module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(webc, {
components: "_includes/webc/*.webc"
});
}
In particular, components in the _includes/webc
folder will be available in the global scope, no need to import them explicitly!
Scaffold a minimal project
To finish our setup, we create a layout file and a simple WebC component which we will use in a basic page, to make sure our setup is working correctly.
We create the layout file under _includes/layouts/base.webc
. It is a simple HTML template with the @html="content"
WebC directive set on the body
tag. (See my-layout.webc in the docs)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Eleventy Images + WebC rocks!</title>
</head>
<body @html="content"></body>
</html>
We now create a simple WebC component and write the following content, taken from the promising website 11ty.webc.fun, in _includes/webc/site-footer.webc
:
<footer>
<p>© 2022 Yours Truly.</p>
</footer>
Finally, we create our basic page at the root of the project in index.webc
:
---
layout: layouts/base.webc
---
Fun with WebC
<site-footer></site-footer>
If all went according to plan, we should now be able to run
npx @11ty/eleventy --serve
in the terminal and see the following output in our browser at http://localhost:8080:
Create an img.webc component
or check the code on GitHub in the 02-img
folder if you want to see the code right away.
In this section, we will create our img.webc
override for the native img
HTML tag and use Eleventy Image within it.
As a starting point, we will use the example from the documentation from which the screenshot at the start of this post is taken. In a new file _includes/webc/img.webc
, add the following:
<script webc:type="render" webc:is="template">
function() {
if(!this.alt) {
throw new Error("[img.webc] the alt text is missing, no fun");
}
return `<img src="${this.src}" alt="${this.alt}">`;
}
</script>
This essentially verifies at build-time that all your images have a non-empty alt text, neat!
Let’s add a “broken” image to our index page, in index.webc
, to verify that this works correctly:
---
layout: layouts/base.webc
---
Fun with WebC
<img src="https://images.unsplash.com/photo-1627224286457-923944e5b159?auto=format&fit=crop&w=387&q=80">
<site-footer></site-footer>
And indeed, running npx @11ty/eleventy --serve
yields an expected error:
$ npx @11ty/eleventy --serve
[11ty] Problem writing Eleventy templates: (more in DEBUG output)
[11ty] 1. Having trouble rendering webc template ./index.webc (via TemplateContentRenderError)
[11ty] 2. [img.webc] the alt text is missing, no fun (via Error)
Add an alt text (“possum hanging from a palm leaf” should work) and try again. Oh no! Another error:
$ npx @11ty/eleventy --serve
[11ty] Problem writing Eleventy templates: (more in DEBUG output)
[11ty] 1. Having trouble rendering webc template ./index.webc (via TemplateContentRenderError)
[11ty] 2. Circular dependency error: You cannot use <img> inside the definition for ./_includes/webc/img.webc (via Error)
That makes sense, when you think about it! All our .webc
files are processed by WebC, so if we return an img
tag in a img.webc
component, the compiler would work indefinitely to get to the bottom of it, because it’s turtles img
all the way down!
Luckily for us, we can let WebC know when its job is done through the webc:raw
attribute:
Use webc:raw to opt-out of WebC template processing for all child content of the current node. Notably, attributes on the current node will be processed.
Let’s fix it in _includes/webc/img.webc
:
<script webc:type="render" webc:is="template">
function() {
if(!this.alt) {
throw new Error("[img.webc] the alt text is missing, no fun");
}
- return `<img src="${this.src}" alt="${this.alt}">`;
+ return `<img webc:raw src="${this.src}" alt="${this.alt}">`;
}
</script>
It compiles now! 🎉
Here’s the expected output:
We have effectively augmented our img
tag to verify at build time that alt text is present.
Let’s now add Eleventy Image to the party!
Optimize your HTML img with Eleventy Image
or check the code on GitHub in the 03-final
folder if you want to see my solution.
Let’s first install Eleventy Image in our project:
npm install @11ty/eleventy-img
We will more or less copy the example from the Eleventy Image docs in the Asynchronous Shortcode section, because our JavaScript render function can be async!
In the file _includes/webc/img.webc
, let’s add the following:
<script webc:type="render" webc:is="template">
- function() {
- if(!this.alt) {
- throw new Error("[img.webc] the alt text is missing, no fun");
- }
- return `<img webc:raw src="${this.src}" alt="${this.alt}">`;
+ const Image = require("@11ty/eleventy-img");
+ module.exports = async function() {
+ let metadata = await Image(this.src, {
+ widths: [300],
+ formats: ["avif", "jpeg"],
+ outputDir: "_site/img/",
+ });
+ let imageAttributes = {
+ alt: this.alt,
+ sizes: "100vw",
+ loading: "lazy",
+ decoding: "async",
+ };
+ // You bet we throw an error on missing alt in `imageAttributes` (alt="" works okay)
+ return Image.generateHTML(metadata, imageAttributes);
}
</script>
Note that we harcoded the value for sizes
in the imageAttributes
object, and prepended both src
and alt
by this
to wire things up correctly. We also instruct the Image plugin to output optimized images in our _site/img/
folder directly for them to be available when using the following markup:
<img alt="a lovely image" src="/img/hash-width.jpeg">
Let’s run npx @11ty/eleventy --serve
once more:
$ npx @11ty/eleventy --serve
[11ty] Problem writing Eleventy templates: (more in DEBUG output)
[11ty] 1. Having trouble rendering webc template ./index.webc (via TemplateContentRenderError)
[11ty] 2. Circular dependency error: You cannot use <img> inside the definition for ./_includes/webc/img.webc (via Error)
That error sounds familiar, right?
We need to tell WebC to ignore img
tags generated by Eleventy Image, and it’s as simple as adding "webc:raw": true
to the imageAttributes
object:
<script webc:type="render" webc:is="template">
const Image = require("@11ty/eleventy-img");
module.exports = async function() {
let metadata = await Image(this.src, {
widths: [300],
formats: ["avif", "jpeg"],
outputDir: "_site/img/",
});
let imageAttributes = {
alt: this.alt,
sizes: "100vw",
loading: "lazy",
decoding: "async",
+ "webc:raw": true,
};
// You bet we throw an error on missing alt in `imageAttributes` (alt="" works okay)
return Image.generateHTML(metadata, imageAttributes);
}
</script>
And we’re done!! (Note how the image now aligns perfectly with localhost:8080
#IncontestableTruth)
with corresponding markup:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Eleventy Images + WebC rocks!</title>
</head>
<body>Fun with WebC
<picture><source type="image/avif" srcset="/img/SwdsuTpRbT-300.avif 300w"><img alt="possum hanging from a palm leaf" loading="lazy" decoding="async" src="/img/SwdsuTpRbT-300.jpeg" width="300" height="450"></picture>
<footer>
<p>© 2022 Yours Truly.</p>
</footer>
</body>
</html>
Parting words
I am so excited to explore further the possibilities of using WebC to augment other native HTML elements.
This article only scratches the surface when it comes to the img
tag. You could pass props down from the markup to Eleventy Image and control the behaviour of the image optimization. But we can now augment our native img
tag without much hassle as it’s all in one file.
It even works with Markdown images (and yields the cutest webpage ever):
---
layout: layouts/base.webc
---
Fun with WebC
<img alt="possum hanging from a palm leaf" src="https://images.unsplash.com/photo-1627224286457-923944e5b159?auto=format&fit=crop&w=387&q=80">
+ <template webc:type="11ty" 11ty:type="md">
+
+ !['close-up of a baby possum among ferns'](https://images.pexels.com/photos/13960994/pexels-photo-13960994.jpeg)
+
+ </template>
<site-footer></site-footer>
All the more reasons to optimize your images now!
Find the code supporting this article on the RobinCsl/11ty-webc-img-demo repository.
Personal blog written by Robin Cussol
I like math and I like code. Oh, and writing too.