Making a Real Bonafide Plugin for 11ty

Last week I wrote about owning your own Feedbin data with 11ty. I talked about what it’s like accessing and using data with 11ty, and how you can do so in your own projects.

But I didn't want to stop there. Feedbin's API has tons of features that could be useful in our blogging journeys, and I wanted to create a plugin that allows people to make the most of it. Enter my 11ty Feedbin plugin, a tiny little plugin that makes your Feedbin data accessible to your 11ty installation.

I went through many hardships in order to make this work, so I wanted to document the experience. This was probably one of the less polished experiences I’ve had with 11ty—I get the feeling that plugins haven’t reached prime-time yet. They don’t have the same flexibility that you get with the base installation of 11ty.

What is an 11ty plugin?

Before we start building something, we should try to understand what we’re building. In the case of 11ty, plugins are pretty straightforward: an 11ty plugin is an NPM package that adds functionality to your .eleventy.js config file. If you've used 11ty you have probably used a plugin or two, and there’s good documentation showing how to use plugins. Unfortunately there’s not much in the way of creating them. So how do we go about modifying our Feedbin script to make it into a plugin?

The good news about plugins

Here's the great thing about 11ty plugins: there’s no special API. Creating an 11ty plugin is similar to writing code for your own 11ty website.

This is good for me, because my Feedbin script originated on this site. I had a JavaScript file that I developed here, and I hoped that I could just use essentially the same file for the plugin. So I created a new repo on Github, created a new .eleventy.js file, dropped my favorites.js file into place, and hoped for the best.

Did it work? I don’t know yet. First I needed to make my package testable.

Adding a package.json file

11ty plugins are NPM modules, as I mentioned above, and all NPM modules require a package.json. I have little experience creating NPM modules, so I copied the package.json from a core 11ty plugin, and modified the relevant parts. I also used npm install --save-dev <package-name> for each of my dependencies, namely Eleventy and node-fetch, to make api requests.

Testing

Now that I have a package file, I am ready to "install" and try to use my package. I noticed that many 11ty plugins have "sample" folders that show how to use the plugin, so I created one of those myself. I kept the code as simple as possible, it went something like this:

// filename: /sample/.eleventy.js
const FeedbinPlugin = require("../");

module.exports = function(eleventyConfig) {
eleventyConfig.addPlugin(FeedbinPlugin);
};

So that’s my test code that calls my plugin. As for my plugin, I had a pretty clean solution there as well:

// filename: /.eleventy.js (plugin initialization)
const feedbin = require("./feedbin");

module.exports = async function(eleventyConfig) {
return await feedbin.getAllFavorites();
};

This should be the only code we need to enable and test the plugin. So I ran it, and I got an error: sadly, .eleventy.js cannot be an asynchronous function. This left me with a pretty serious issue.

How do we use async code in a plugin?

If you've never dealt with asynchronous code in JavaScript, then prepare yourself. It’s tremendously powerful, but can have a lot of hard-to-debug issues, and requires a different way of thinking about code.

Unfortunately, since we’re calling an API, we need to have async code. This is non-negotiable. So I started looking for a way to make this happen.

I tried dozens of different ways to call an async function inside my .eleventy.js file, but nothing worked. Perhaps someone smarter than me could figure it out, but I ran into a brick wall, and wasted hours on this one issue.

Then I looked through a bunch of plugins on Github, and I did find one that called an API. They use an async filter to do this, a featured added in the 0.10 version of 11ty. I didn't want to resort to this for my plugin, because filters are meant to filter text or data, and I wanted to return unfiltered data. If I were to use a filter, I would have to return markup, which I don’t want to do. I want my plugin to create templateable data, I don’t want to mandate a template that everyone has to use.

So the filter was out. I searched again through the 11ty docs for anything asynchronous, and it was then that I stumbled upon shortcodes. Shortcodes are similar to filters, and again don’t 100% fit our needs, but they were the only thing I could find that allowed me to run asynchronous code. So I decided to try to make it work.

Using a shortcode to run async code

Similar to my first attempt, I wrapped my code in a shortcode:

// filename: /.eleventy.js (plugin initialization)
const feedbin = require("./feedbin");

module.exports = async function(eleventyConfig) {
eleventyConfig.addNunjucksAsyncShortcode("favorites", async() => {
return await feedbin.getAllFavorites();
});
};

This had no effect. I ran my sample code, and nothing happened. Then I recalled that shortcodes have to be called in your code. So I created a small template, and called my "favorites" shortcode at the top:

<!-- filename: index.njk -->
{% favorites %}

To my great surprise, this worked! Calling the shortcode caused my async code to run successfully, it fetched my Feedbin data, and there were no errors. Nice.

We still had one problem though. My Feedbin data existed, but it was not accessible to my template. My plugin worked by exporting data, but the 11ty data cascade is dependent on file location. My file was located within a plugin, so it wasn't accessible to the "parent" project. One more thing we would have to solve.

Solving the data issue

I half hoped my shortcode could solve this. I tried any number of ways to use the data that the shortcode returned, but I couldn't get it to work:

// these don't work :(
{% set favs = favorites %}
{% for post in favorites %}

This isn't how you're supposed to use shortcodes, but it still surprises me that I can't use that data. It prints out [object Object], and I’m honestly not sure why I can see the data but I can't use it. I feel like there must be a way to do this, but I wasn't able to find one.

So I had to get a little more creative. After re-reading the documentation on the data cascade again and again, I only could think of one solution: rather than create a "_cache" folder for my Feedbin data, why not just add the data directly to the "_data" folder?

This idea seemed too simple to work, but after a couple of hours of unsuccessfully trying other things, I decided to give it a shot.

It worked. Our plugin now not only adds our Feedbin data to our 11ty site, but that data is also accessible through the global data folder. I was honestly surprised that this worked, I wasn't expecting a plugin to be able to manipulate files in the parent project. But they totally can, and as far as I could find, it was the only way to get this done.

Final thoughts

This project was definitely more difficult than I expected, and since the pain is fresh in my mind, I’d like to explain why I struggled as much as I did.

First of all, I wasn't expecting data manipulation to be so difficult. Using data is so easy and intuitive when using 11ty: you can create a special file for it, you can create any type of file in the _data folder, there are many ways to do it. But for some reason it’s much much harder when developing a plugin. I tried all of the methods of creating data that exist in the documentation, and the only one that worked was physically creating a file. I would like to see this improve.

I would also like a more intuitive way to run asynchronous code in an .eleventy.js config file. It’s possible that I’m missing something obvious here, and there is a better way to run async code within a plugin, in which case this is just a documentation issue.

I was also worried about publishing my first NPM module, but it turns out that it was trivial. I’m sure I got things wrong with my configuration, but no one has complained yet, so I’m not sweating it too much. I can, and will, fix things with later versions.

Overall though, I am overjoyed to have an 11ty plugin published, and to be the first official user of said plugin. If you want to try it out, you can see instructions on the Github repo. If you run into any difficulties please let me know by filing an issue. Definitely planning on continuing to develop this as I have time!

← Home