<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://www.martingunnarsson.com" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Martin Gunnarsson</title>
    <link>https://www.martingunnarsson.com</link>
    <atom:link href="https://www.martingunnarsson.com/feed-rss.xml" rel="self" type="application/rss+xml" />
    <description>Personal blog of Martin Gunnarsson, mostly focused on small hobby projects</description>
    <language>en</language>
    <item>
      <title>Advent of Code 2025:3, Rockstar edition</title>
      <link>https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-3/</link>
      <description>&lt;p&gt;Having solved &lt;a href=&quot;https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-1.md&quot;&gt;the first&lt;/a&gt; and &lt;a href=&quot;https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-2.md&quot;&gt;second&lt;/a&gt; Advent of Code puzzles in &lt;a href=&quot;https://www.codewithrockstar.com/&quot;&gt;Rockstar&lt;/a&gt;, it was only a matter of time before I finished the third one. Hopefully the assignments will be too difficult soon ...&lt;/p&gt;
&lt;pre class=&quot;language-rockstar&quot;&gt;&lt;code class=&quot;language-rockstar&quot;&gt;Let the letter be gone
Put it into the fire
Listen to the wind
Until it is gone
Scream the wind
Shatter it into shards
The heart is like loneliness
Put shards with the heart into the abyss
Cast nothing into darkness
Let the light be it
My dreams are so cumbersome
Put the heart into the beginning
The soul is levitating
Build it up
While my dreams are lower than the abyss without the soul
Let hope be shards at my dreams
If it&#39;s greater than darkness
Put hope into darkness
Let the beginning be my dreams
If hope is crackling break on to the other side, yeah!
Build my dreams up, baby
Let my dreams be the beginning
Build them up
While my dreams is less than the abyss
Put shards at them into hope
If it&#39;s greater than the light, put it into the light, yeah
Build my dreams up, baby
Put darkness with the light into the smoke
Cast it with like a superpower
The fire is with the smoke
Listen to the wind
Yeah
Shout the fire&lt;/code&gt;&lt;/pre&gt;
</description>
      <pubDate>Mon, 08 Dec 2025 20:48:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-3/</guid>
    </item>
    <item>
      <title>Advent of Code 2025:2, Rockstar edition</title>
      <link>https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-2/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The other day I posted &lt;a href=&quot;https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-1.md&quot;&gt;my first attempt at coding in Rockstar&lt;/a&gt;. After that I couldn&#39;t help myself, and continued with the second &lt;a href=&quot;https://adventofcode.com/&quot;&gt;Advent of Code&lt;/a&gt; puzzle.&lt;/p&gt;
&lt;h2&gt;The code&lt;/h2&gt;
&lt;p&gt;Just like the first time, I wrote a little JavaScript implementation first, to make sure my logic was sound, then I implemented it in Rockstar. This time I hit the ground running, and except for a few frustratingly vague error messages (Unexpected &#39;Let&#39; at line 1 col 1: Failed to parse &#39;program&#39;, basically saying that this program here &lt;em&gt;as a whole&lt;/em&gt; couldn&#39;t be parsed) I got it working fairly easily. This time I decided to make it sound a bit like a Christmas rock song, but it also ended up sounding a bit like a cookie recipe.&lt;/p&gt;
&lt;pre class=&quot;language-rockstar&quot;&gt;&lt;code class=&quot;language-rockstar&quot;&gt;Everybody wants cookies
Cut silence with cookies into joy
His dreams are so overloaded
Let the holiday be joy with them
Let angels be silent
Put them into the night
Fire is like you
Let the spirit be like wow!
It is without fire
While the spirit is weaker than the holiday
Let praise be joy at the spirit
When the spirit is smaller than the holiday between like us
Angels are with praise
Otherwise, put the night with praise into the night, baby
Build the spirit up! Yeah
Let wonderful be angels are really the night
Let the message be it! Send it!

Listen to the singing
Let the dream be gone

The streets are silent
Rock them with like snow plow!
Let us be the streets

Put the singing into baking
The cookies are empty
Rock them with like corn flour!
Let the dough be baking

Cut the dough into pieces with us
Put the cookies into us

For the piece in pieces
Cut it into halves with us
Roll halves into the night; cast them with like a gingersnap
Roll halves into the oven; cast them with like a nutcracker
Let peace be the night
While peace is as low as the oven
When everybody taking peace is right
The dream is with peace, baby
Build peace up
Oh yeah
Shout the dream&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Surely this is it now. I can&#39;t possibly be bothered writing another one of these, right?&lt;/p&gt;
</description>
      <pubDate>Sun, 07 Dec 2025 17:39:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-2/</guid>
    </item>
    <item>
      <title>Advent of Code, Rockstar edition</title>
      <link>https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-1/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;At &lt;a href=&quot;https://www.oredev.org/&quot;&gt;Øredev&lt;/a&gt; I met my pal &lt;a href=&quot;https://www.bjoreman.com/&quot;&gt;Fredrik&lt;/a&gt;, who told me about this presentation he had just been to. It was about a fun programming language called &lt;a href=&quot;https://www.codewithrockstar.com/&quot;&gt;Rockstar&lt;/a&gt;, which lets you write 80&#39;s rock ballad lyrics that compile. Fun! One faithful evening a few weeks later, after the rest of the family had gone to bed, I decided to have a couple of beers and give it a try.&lt;/p&gt;
&lt;h2&gt;The code&lt;/h2&gt;
&lt;p&gt;Since we&#39;re approaching Christmas, it only felt appropriate to use the first &lt;a href=&quot;https://adventofcode.com/&quot;&gt;Advent of Code&lt;/a&gt; puzzle as my first assignment. I signed in, read the instructions, and immediately decided to &lt;em&gt;cheat&lt;/em&gt; and solve the problem in a more familiar language first. I wrote a quick JavaScript solution, verified the output, and moved on to Rockstar. Conveniently there&#39;s an online editor and interpreter right on the Rockstar website, so I was able to get started without downloading a single thing.&lt;/p&gt;
&lt;p&gt;Rockstar was a little awkward to get used to, I basically jumped back and forth between the documentation and the online editor, slowly building up the same solution I had just written in JavaScript. Some of the examples in the documentation made no sense at all, as there are several quirky language features made specifically to enable coding in somewhat natural language. Other challenges included things like having to implement a crude modulo function of my own, since very few rock songs are about modulo operations.&lt;/p&gt;
&lt;p&gt;I wrote the implementation in a clear style first, with clear variable names etc, and then had a bit of fun trying to dress it up as more of a rock song. I&#39;m not really an expert in either esoteric programming languages, coding puzzles, &lt;em&gt;or&lt;/em&gt; rock lyrics, so it&#39;s probably neither the best solution nor the best rock song, but at least it works!&lt;/p&gt;
&lt;p&gt;So, without further ado, here&#39;s my solution to the first puzzle of Advent of Code 2025, written in Rockstar:&lt;/p&gt;
&lt;pre class=&quot;language-rockstar&quot;&gt;&lt;code class=&quot;language-rockstar&quot;&gt;Magic is found everywhere
Space is like a rocketship, rocketship
The truth is empty
The west is empty
Rock them with like glitter horses!

The reaper takes the lover and the scythe
Let judgement be the lover
When judgement is weaker than nothing
Let it be nothing without it
Let the jester be right
Yeah
While judgement is as strong as the scythe, judgement is without the scythe, baby
If the jester is right let judgement be nothing without judgement
Give judgement back!

Listen to the word!
Until the word is gone
Roll the word into heading
Cast the word with like a loneliness
The force is bittersweet
When heading is the west, the others are like us
Put the force without them into the force, baby
Let progress be magic with the force of the word
Put progress into magic
The stars are like electricity
When magic is higher than space without the stars or magic is weaker than nothing,
let magic be the reaper taking magic &amp; space, yeah!
When magic is empty, build the truth up, baby
Listen to the word
Yeah

Say the truth!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had fun writing this, and I&#39;m weirdly tempted to do it again, but I&#39;m not making any promises. Maybe if I find myself with another evening on my own and a couple of cold beers at hand ...&lt;/p&gt;
</description>
      <pubDate>Sat, 06 Dec 2025 14:39:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/advent-of-code-rockstar-2025-1/</guid>
    </item>
    <item>
      <title>Am I a bicycle commuter now?</title>
      <link>https://www.martingunnarsson.com/posts/am-i-a-bicycle-commuter-now/</link>
      <description>&lt;p&gt;Two months ago I started collecting &lt;a href=&quot;https://www.martingunnarsson.com/posts/making-a-case-for-commuting-by-bike.md&quot;&gt;my thoughts on commuting by bike&lt;/a&gt;, mostly to convince myself to giving it a shot. Being in the middle of the winter, the weather was pretty rough for a period of time after that, but about a month later I &lt;a href=&quot;https://www.martingunnarsson.com/posts/bicycle-commuting-premiere.md&quot;&gt;rode to work for the first time&lt;/a&gt;. The weather was still a bit questionable on that day, with a very strong crosswind, but I went anyway and was thrilled to have taken the first pedal stroke.&lt;/p&gt;
&lt;p&gt;This was 3-4 weeks ago, and including that first time I&#39;ve now commuted by bike ten times. I&#39;ve been working from home at least two days during this period as well, so I&#39;ve actually commuted almost 60% by bike. That&#39;s a good start! One of the reasons this has worked out so well is that the weather has been surprisingly decent for this time of the year, with very little rain. I still haven&#39;t invested in rain clothes, so my choice of transportation is pretty weather-dependent.&lt;/p&gt;
&lt;p&gt;Mornings like this, however, riding to work is a real pleasure.&lt;/p&gt;
&lt;p&gt;&lt;video controls=&quot;&quot; disablepictureinpicture=&quot;&quot; disableremoteplayback=&quot;&quot; playsinline=&quot;&quot; width=&quot;100%&quot; preload=&quot;metadata&quot; poster=&quot;https://www.martingunnarsson.com/posts/am-i-a-bicycle-commuter-now/poster.jpg&quot;&gt;&lt;source src=&quot;https://www.martingunnarsson.com/posts/am-i-a-bicycle-commuter-now/commute.mp4&quot; type=&quot;video/mp4&quot; /&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2&gt;Practicalities&lt;/h2&gt;
&lt;p&gt;I ride my simple but robust &lt;a href=&quot;https://www.martingunnarsson.com/posts/my-bicycle/my-bicycle.md&quot;&gt;Skeppshult bicycle&lt;/a&gt;. Since it lacks racks or other luggage space I pack everything I need in a pretty tight &lt;a href=&quot;https://www.haglofs.com/en/men/backpacks-bags-men/backpacks-bags-daypacks-laptop-backpacks-men/corker-20-3381573N5&quot;&gt;Haglöfs Corker backpack&lt;/a&gt;. It has a laptop compartment along the back with &lt;em&gt;just&lt;/em&gt; enough room for my 14&amp;quot; MacBook Pro in a neoprene sleeve. I also bring my notepad, lunchbox, a spare t-shirt, and a flexible steel lock for the bike. After a couple of rides I noticed my saddle had started coming loose, and added a small set of Allen keys to the backpack as well.&lt;/p&gt;
&lt;p&gt;During this period we&#39;ve had typical early spring weather, with temperatures between -2 and 8°C or so. I ride in my regular, casual office clothes, usually a pair of chinos and a t-shirt with a hoodie or a button-down shirt over, and then a fleece jacket as an outer layer.&lt;/p&gt;
&lt;p&gt;I usually have a tailwind going to the office. Since it&#39;s also pretty cool in the morning, I rarely get sweaty from that ride. I&#39;ve changed into my spare t-shirt once, but I wouldn&#39;t say it was strictly necessary, I just really want to avoid smelling offensive in the office. The ride home is a different story. It&#39;s warmer that time of the day, there&#39;s almost always a headwind, and the last kilometer (after battling that headwind over the open fields) is up a pretty tall hill. I&#39;ve arrived at home very sweaty a number of times, but of course that isn&#39;t much of a problem. I think I&#39;ll have to find a thinner jacket soon though, since I tend to get very sweaty even without the middle layer of clothing.&lt;/p&gt;
&lt;h2&gt;Adjustments&lt;/h2&gt;
&lt;p&gt;The only thing I&#39;ve been tweaking is my saddle position. I was a bit uncomfortable the first few rides, which is natural I guess, but also got a bit of numbness in the nether regions. After watching a few bike fitting videos and learning that most people (especially men) have their saddles too high, I tried lowering mine a bit. I think I got a bit of a smoother stroke as a result, and the numbness problem all but went away. I might try tweaking the position a bit more, but I think it&#39;s pretty good where it&#39;s at now.&lt;/p&gt;
&lt;h2&gt;Setbacks&lt;/h2&gt;
&lt;p&gt;After a couple of weeks I went to work by bike two days in a row for the first time, and after that an old annoying knee pain of mine re-surfaced. It&#39;s probably something like runner&#39;s knee or jumper&#39;s knee. I did see a physiotherapist about it regularly a few years ago, which had a positive effect, so I was pretty bummed-out about having it back. I really want to keep riding to work, and I&#39;ve also signed up for a pretty cool half-marathon next summer, and have been thinking of my biking as a low-impact way of increasing my fitness and strenthening my legs before starting running again.&lt;/p&gt;
&lt;p&gt;I decided to try riding to work again a couple of days later, and to my relief didn&#39;t feel any pain, so I&#39;ve kept going. There&#39;s no way to tell whether the pain was caused by riding the bike or not. Perhaps I jumped around too much playing with the kids or something, I can&#39;t really remember. It&#39;s come and gone a bit since then, but it&#39;s been very mild and seemingly not gotten worse by biking.&lt;/p&gt;
&lt;h2&gt;Success&lt;/h2&gt;
&lt;p&gt;I feel really good on the days when I commute by bike. It&#39;s a great way of starting the day, and I get a lot of energy from it. From a physical perspective, the commute of course consumes energy rather than producing it, usually between 6-700 kcal for the rountrip according to my Apple watch, and I&#39;ve noticed I feel more hungry now. I&#39;ve also lost almost 2 kg without any other changes or efforts, so that&#39;s a nice side effect!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If I could find a good set of rain clothes I could see myself going all-in on bike commuting after getting used to it properly. I just hope I don&#39;t get held back by any annoying injuries. Riding every day I would really start to see financial benefits from it, on top of all the other positives, as I wouldn&#39;t have to pay for the monthly parking permit that I have now. In fact, I think I&#39;m almost at the point where it would have been cheaper to pay by the hour for the days when I do drive rather than getting the monthly permit. Without rain clothes, however, not getting one is a bit of a gamble.&lt;/p&gt;
&lt;p&gt;I record all my rides &lt;a href=&quot;https://www.strava.com/athletes/54831&quot;&gt;on Strava&lt;/a&gt;, and I&#39;m always happy to find more inspiring people on there, so feel free to give me a follow. Happy riding!&lt;/p&gt;
</description>
      <pubDate>Mon, 18 Mar 2024 11:20:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/am-i-a-bicycle-commuter-now/</guid>
    </item>
    <item>
      <title>Eleventy excerpts</title>
      <link>https://www.martingunnarsson.com/posts/eleventy-excerpts/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Wherever I list posts on this site, e.g. on the &lt;a href=&quot;https://www.martingunnarsson.com/&quot;&gt;Home page&lt;/a&gt; and &lt;a href=&quot;https://www.martingunnarsson.com/archive&quot;&gt;Archive page&lt;/a&gt;, I want to display a reasonably long excerpt from the linked post, to give visitors an idea of its content. There seems to be an almost comical variation in what people want from excerpts - how long they should be, whether they should be marked manually or picked out automatically, or entered separately from the content altogether, whether images should be included or not etc. etc. Fun!&lt;/p&gt;
&lt;p&gt;Of course I also have my own idea of how everything should work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The excerpt should be an automatically selected portion of the beginning of the post.&lt;/li&gt;
&lt;li&gt;Only body text should be included, no images, headlines etc.&lt;/li&gt;
&lt;li&gt;All formatting should be removed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think that&#39;s it. Pretty modest requirements if you ask me.&lt;/p&gt;
&lt;h2&gt;The Eleventy solution&lt;/h2&gt;
&lt;p&gt;This site is built using &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, and thanks to its use of the &lt;a href=&quot;https://www.npmjs.com/package/gray-matter&quot;&gt;gray-matter&lt;/a&gt; npm package, Eleventy has a way of extracting excerpts from posts automatically. It requires manually setting the excerpt cutoff-point in the text, but you can freely choose what separator to use:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setFrontMatterParsingOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;excerpt&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;excerpt_separator&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;!-- cut --&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Further configuration&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this snippet in place in your &lt;code&gt;.eleventy.js&lt;/code&gt;, the &lt;code&gt;item.data.page&lt;/code&gt; object will include a property called &lt;code&gt;excerpt&lt;/code&gt; when iterating over collections:&lt;/p&gt;
&lt;pre class=&quot;language-jinja2&quot;&gt;&lt;code class=&quot;language-jinja2&quot;&gt;&lt;span class=&quot;token jinja2 language-jinja2&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    ...
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token jinja2 language-jinja2&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;excerpt&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    ...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token jinja2 language-jinja2&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This solution works well, but I don&#39;t want to mark my excerpts manually. I feel pretty strongly that the content of the site should be kept as free as possible from technical implementation details. This feels like a good principle, and will also make the content as portable as possible.&lt;/p&gt;
&lt;p&gt;Another limitation of this solution is that the excerpt generated will be extracted from the raw content, rather than the rendered page. I use &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; for my posts, so the excerpts I get using the built-in functionality will be snippets of the unprocessed Markdown source. Stripping Markdown formatting from text is a lot trickier than e.g. stripping HTML, as there is no common formatting of control characters etc. I&#39;m sure there are clever regular expressions out there that solves this, but I feel more confident stripping out HTML. Another common approach is to manually run the excerpt through the &lt;a href=&quot;https://markdown-it.github.io/&quot;&gt;markdown-it&lt;/a&gt; processor used by Eleventy, but I always thought that felt a bit clunky and unnecessary. The page will be processed by markdown-it as part of the site rendering anyway, why do it again just to generate an excerpt?&lt;/p&gt;
&lt;h2&gt;My solution&lt;/h2&gt;
&lt;p&gt;I settled on building a custom solution in the form of an Eleventy filter, that extracts the excerpt from the rendered page rather than from the source. To keep my &lt;code&gt;.eleventy.js&lt;/code&gt; as short as possible, I put the code in a separate class:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExcerptGenerator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;getExcerpt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; length&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; excerptParagraphs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; currentLength &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; paragraphs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&amp;lt;p&gt;.*?&amp;lt;&#92;/p&gt;&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;gs&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; paragraph &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; paragraphs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Strip HTML from the paragraph&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; paragraph&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;(&amp;lt;([^&gt;]+)&gt;)&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;gi&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentlength &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; currentLength &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

            excerptParagraphs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            currentLength &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; excerptParagraphs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code finds all paragraph tags in the content, and then starts picking them out one by one, until adding another one would break the specified length limit. As part of this process, any HTML within the added paragraphs is stripped. A bit of a brutal approach perhaps, but it gets the job done. It &lt;em&gt;feels&lt;/em&gt; like there&#39;s an elegant map reduce-like solution to this problem, but I&#39;m not so sure that elegant solution would be any easier to read or maintain. I could also have used something like &lt;a href=&quot;https://cheerio.js.org/&quot;&gt;Cheerio&lt;/a&gt; to first parse the HTML and then pick out the paragraphs that way, but that would most likely have been slower and probably not much of an improvement readability-wise either. I&#39;ll stick to the brutalist approach for now.&lt;/p&gt;
&lt;h3&gt;Usage&lt;/h3&gt;
&lt;p&gt;The filter is then hooked up in &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ExcerptGenerator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./_scripts/excerptgenerator&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;excerpt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExcerptGenerator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getExcerpt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The string passed into the &lt;code&gt;addFilter&lt;/code&gt; function is the name that the filter will be accessed through, in this case &lt;code&gt;excerpt&lt;/code&gt;. This filter name is now ready to be used from any template:&lt;/p&gt;
&lt;pre class=&quot;language-jinja2&quot;&gt;&lt;code class=&quot;language-jinja2&quot;&gt;&lt;span class=&quot;token jinja2 language-jinja2&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    ...
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token jinja2 language-jinja2&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;templateContent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;excerpt&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    ...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token jinja2 language-jinja2&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;token tag keyword&quot;&gt;endfor&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;%}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code takes the template content, i.e. the rendered content, from each post and sends it through the &lt;code&gt;excerpt&lt;/code&gt; filter, resulting in a reasonably sized excerpt void of any styling and formatting.&lt;/p&gt;
&lt;h3&gt;Drawbacks&lt;/h3&gt;
&lt;p&gt;The above solution has worked well for all posts I&#39;ve written so far, but in theory it could generate somewhat incoherent excerpts in certain cases. Consider a post with a very short first paragraph, followed by an image or a code block, and then a second paragraph discussing that middle content. The excerpt for this post would just consist of a single string combining the first and the second paragraph, completely omitting the middle content, which could make the text a bit difficult to understand. I&#39;ll deal with that if and when it happens. Just like with the &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-automatic-image-pre-processing-part-2.md&quot;&gt;automatic image scaling&lt;/a&gt; I wrote about recently, my goal here isn&#39;t perfection, but to have a decent end-result without any manual work. The lazier I can be when publishing new content, the more likely I am to actually do it.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I see a lot of people implementing custom excerpt functionality in Eleventy, which is a testament to its flexibility. On the other hand, it also says something about the current excerpt support. A couple of the customizations I&#39;ve implemented as part of this site in the past have been workarounds for little rough edges like this in Eleventy, and they have all since been made obsolete by improvements either in Eleventy itself or in official plugins. I have a feeling excerpts could be up for a revamp as well at some point in the future. Until then, I&#39;m happy with my own little flavor of excerpts.&lt;/p&gt;
&lt;p&gt;If you want to read more about the ways I&#39;m using Eleventy on this site, I&#39;ve collected all my posts on a separate &lt;a href=&quot;https://www.martingunnarsson.com/tags/eleventy&quot;&gt;Eleventy page&lt;/a&gt;. Happy blogging!&lt;/p&gt;
</description>
      <pubDate>Fri, 15 Mar 2024 22:29:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/eleventy-excerpts/</guid>
    </item>
    <item>
      <title>Automatic image pre-processing in Eleventy, Part 2</title>
      <link>https://www.martingunnarsson.com/posts/eleventy-automatic-image-pre-processing-part-2/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;One of the &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; customizations I&#39;ve set up for this site is automatic generation of responsive &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; tags from images in &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; posts. I was going to build it myself using the &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/&quot;&gt;Eleventy Image&lt;/a&gt; plugin, but then randomly stumbled upon a plugin for the &lt;a href=&quot;https://github.com/markdown-it/markdown-it&quot;&gt;markdown-it&lt;/a&gt; Markdown processor used by Eleventy that did exactly that. It&#39;s called &lt;a href=&quot;https://www.npmjs.com/package/markdown-it-eleventy-img/&quot;&gt;markdown-it-eleventy-img&lt;/a&gt; and provides the glue needed between the Eleventy Image plugin and the Markdown parsing performed by markdown-it. I wrote &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-automatic-image-pre-processing.md&quot;&gt;a post about this setup&lt;/a&gt; back in the day, but that is now obsolete. Starting with version 4.0.0, the Eleventy Image plugin itself performs all the steps needed itself.&lt;/p&gt;
&lt;h2&gt;Motivation&lt;/h2&gt;
&lt;p&gt;The main reason I want this functionality on my site is lazyness. Or, rather, I want to be lazy without paying the price! I want to be able to just dump a large JPEG or PNG image into the directory of one of my posts, include it as a regular Markdown image, and have it offered to the browser both in multiple sizes and various modern image formats.&lt;/p&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;To get this functionality set up using Eleventy Image, you first need to install the plugin using &lt;code&gt;npm&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @11ty/eleventy-img&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up, add it to your &lt;code&gt;.eleventy.js&lt;/code&gt; and start adding some configuration:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; eleventyImagePlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy-img&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eleventyImagePlugin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// The image formats to generate, in order of preference&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;avif&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;webp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// The images sizes to generate&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;widths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;368&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;736&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

        &lt;span class=&quot;token literal-property property&quot;&gt;defaultAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Assumptions&lt;/h2&gt;
&lt;p&gt;The first time I set this functionality up, I realized my assumption about the browser&#39;s image format selection was incorrect. I thought that you could just offer a number of different formats, and the it would pick the best one automatically. In hindsight that feels a bit naïve, what &lt;em&gt;is&lt;/em&gt; even the best format? Instead, the browser picks the first format it has support for, so the order of the formats matter.&lt;/p&gt;
&lt;p&gt;Once my post on the subject was published I got some very helpful feedback, that made me realize my assumption about the size selection was also incorrect. Again, I thought you could offer a number of different image sizes, and the browser would pick the most suitable one automatically, but, again, it does not. Instead it&#39;s up to the developer to describe to the browser under what circumstances the different sizes should be used, using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Media_queries&quot;&gt;media queries&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I haven&#39;t specified any media queries in my code, just a single &lt;code&gt;auto&lt;/code&gt; value, which acts as a default fallback rule. This will pick the image size best matching the full width of the image&#39;s parent element, which works well for me, as all of the (very few) images I use on this site are intended to be displayed across the full width of the page. With more varying image sizes I guess the ideal solution would be to set breakpoints for each individual image, but since my main motivation here is lazyness, I prefer a decent catch-all solution to a perfect one requiring a lot of manual work.&lt;/p&gt;
&lt;h2&gt;Fine-tuning&lt;/h2&gt;
&lt;p&gt;Using the above code, the responsive image generation just worked. I then proceed to add a couple of very optional extras to my configuration, specifically &lt;code&gt;loading: &amp;quot;lazy&amp;quot;&lt;/code&gt; and &lt;code&gt;decoding: &amp;quot;async&amp;quot;&lt;/code&gt;. This improves initial page load speed, since the loading of off-screen images can be deferred until the user scrolls down to them. Having these attributes on all images is a bit of an anti-pattern, since images near the top of the page should ideally be loaded in-line rather than asynchronously. However, most of my images tend to be near the end of the post, so I don&#39;t feel too bad about adding the attributes.&lt;/p&gt;
&lt;p&gt;They are added to the defaultAttributes, which are inserted into all generated &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;defaultAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;decoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;async&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another small detail I&#39;ve changed is the naming of the scaled images. This can be controlled by specifying a custom &lt;code&gt;filenameFormat&lt;/code&gt; function. I know I can safely do this on this specific site, since I keep all post-related images in the directory of the post, but read up on it and make sure you pick a format that works for your site without causing any duplicate names:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;

&lt;span class=&quot;token function-variable function&quot;&gt;filenameFormat&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;filename&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;width&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;format&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, all in all, my configuration looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eleventyImagePlugin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// The image formats to generate, in order of preference&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;avif&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;webp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// The images sizes to generate&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;widths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;368&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;736&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token literal-property property&quot;&gt;defaultAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;auto&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;decoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;async&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token function-variable function&quot;&gt;filenameFormat&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;filename&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;width&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;format&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Result&lt;/h2&gt;
&lt;p&gt;The output of the above configuration means a simple Markdown image like this:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;My image&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;./image.png&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Turns into this HTML:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;lazy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;decoding&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;async&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/.../image-368.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;My image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;900&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;762&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/.../image-368.png 368w, /.../image-736.png 736w, /.../image-900.png 900w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image/avif&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/.../image-368.avif 368w, /.../image-736.avif 736w, /.../image-900.avif 900w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image/webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/.../image-368.webp 368w, /.../image-736.webp 736w, /.../image-900.webp 900w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;    
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, the image is located in the same directory as the post, which is the structure I&#39;m always using. The truncated path is the path of the post, e.g. &lt;code&gt;/posts/my-post/&lt;/code&gt;. You can control the output location of the Image plugin using the &lt;code&gt;urlPath&lt;/code&gt; option, but I prefer keeping the images with the rest of the post.&lt;/p&gt;
&lt;p&gt;Another thing that might be good to keep in mind is that the image pre-processing will find &lt;em&gt;all&lt;/em&gt; images in your pages, even e.g. the little header image on this site. That one I&#39;ve already optimized manually, and I like to keep it as it is. You can both override certain parameters as well as have specific images be ignored entirely by the pre-processor using special parameters on the HTML elements themselves. For example, to have the Image plugin ignore my header image, I&#39;ve added the &lt;code&gt;eleventy:ignore&lt;/code&gt; parameter to it, like so: &lt;code&gt;&amp;lt;img eleventy:ignore ... /&amp;gt;&lt;/code&gt;. Check out the &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/&quot;&gt;Image plugin documentation&lt;/a&gt; for the full specification.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It seems like with every release of Eleventy or its plugins, a customization I&#39;ve built for this site becomes redundant, and I couldn&#39;t be happier about it. Even though the above solution does exactly the same thing as my previous one, I feel more comfortable using the same functionality from an official plugin. It also means that it will be maintained and keep working with future Eleventy versions.&lt;/p&gt;
&lt;p&gt;If you want to read more about the ways I&#39;m using Eleventy on this site, I&#39;ve collected all my posts on a separate &lt;a href=&quot;https://www.martingunnarsson.com/tags/eleventy&quot;&gt;Eleventy page&lt;/a&gt;. Happy blogging!&lt;/p&gt;
</description>
      <pubDate>Wed, 13 Mar 2024 05:41:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/eleventy-automatic-image-pre-processing-part-2/</guid>
    </item>
    <item>
      <title>My bicycle</title>
      <link>https://www.martingunnarsson.com/posts/my-bicycle/</link>
      <description>&lt;p&gt;My bicycle is a silver Skeppshult STC Basic from 2005 or so. I don&#39;t remember exactly when I bought it, but I think it was around that time. &lt;a href=&quot;https://www.skeppshult.se/&quot;&gt;Skeppshult&lt;/a&gt; is a Swedish manufacturer of bicycles founded in 1911. They&#39;ve been making bicycles at the same location without interruption since then, and they&#39;re the only Swedish manufacturer that makes frames in-house. Iron, and later steel, has been very important resources in Sweden for centuries, and Swedish steel is world-famous for its strength and quality. It feels pretty cool then that the frame of my bike and the entire supply chain of raw materials needed to make it are extracted, refined, and produced within the borders of our little country. I&#39;m sure pretty much all the other parts on the bike are made in China though, that&#39;s pretty much unavoidable these days.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.martingunnarsson.com/posts/my-bicycle/bicycle.jpeg&quot; alt=&quot;A silver bicycle standing against a yellow brick wall&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The bike is probably best described as a &amp;quot;city bike&amp;quot;, and has a pretty upright riding position. It has a Shimano Nexus 7 internally geared rear hub, a Shimano dynamo front hub with internal breaks, and pretty narrow, straight handlebars. It doesn&#39;t have any rack, which gives it a very clean look, but it does have mudguards on both wheels. Removing those would give the bike an even cleaner profile, but in this case practicality wins.&lt;/p&gt;
&lt;p&gt;The hub dynamo was not part of the bike as presented in the catalog, but was custom made for me at the factory. Skeppshult assemble their wheels in-house, so I could get the correct rim, matching the rear one, but with a hub dynamo instead of the original one. I also asked for a metal chain guard instead of the ghastly transparent plastic one the bike was supposed to come with. I was pleasantly surprised that this level of customization was possible directly from the factory, and even more so that the man at the bicycle shop didn&#39;t seem to think it was anything out of the ordinary. The customizations were surprisingly cheap as well as I recall it, so the whole ordering process was a very positive experience.&lt;/p&gt;
&lt;p&gt;I&#39;ve been very happy with the bike since the day I got it. A difference I noticed from other bikes I&#39;ve had was that it didn&#39;t make any sounds running on uneven surfaces like cobbled streets (of which we have plenty in the area). No rattling and no squeaking, very pleasant. Nowadays I get the occasional clank from the chain if I run over a bigger bump, but putting a little more tension on it would solve that. The rest of the bike is still rattle-free even after all these years.&lt;/p&gt;
&lt;p&gt;I&#39;ve ridden it to work from time to time and used it to get some exercise here and there, but I can&#39;t deny that it&#39;s mostly been stainding still. During a majority of the years I&#39;ve had the bike, I&#39;ve kept it outdoors, exposed to the elements. A few parts have gotten a bit of surface rust, but all in all it seems to cope pretty well with the Swedish climate. I&#39;ve replaced the saddle, and when the original chain started getting rusty I replaced it with a stainless steel one. I&#39;ve also replaced the tiny, cheap rear light a couple of times, but that&#39;s it for major maintenance I think. At least up until this year.&lt;/p&gt;
&lt;h2&gt;Recent modifications&lt;/h2&gt;
&lt;p&gt;Ahead of my &lt;a href=&quot;https://www.martingunnarsson.com/posts/bicycle-commuting-premiere.md&quot;&gt;bicycle commuting premiere&lt;/a&gt; I replaced the old, broken, halogen front-light with a new LED one. If I were to just order one online I would most likely have gone for a &lt;a href=&quot;https://www.bumm.de/en&quot;&gt;Busch + Müller&lt;/a&gt; one, as they have a wide range of high-quality dynamo lights in various designs, made in Germany. This time I had a city gift card that I got from work for Christmas that I wanted to use, and ended up buying a &lt;a href=&quot;https://spanninga.com/product/axendo-40/&quot;&gt;Spanninga Axendo 40&lt;/a&gt; from a local shop. It certainly isn&#39;t the best looking light I&#39;ve ever seen, and I did hesitate a bit before giving in and letting destiny guide me.&lt;/p&gt;
&lt;p&gt;When it was time to buy the rear light I didn&#39;t have any gift card limiting me to certain shops. Instead the biggest constraint was my bike&#39;s lack of a rack. The vast majority of rear lights available are made to be mounted on the back of a rack. With that option removed, ideally I would have liked a light mounted to the saddle post or even saddle rails, but I couldn&#39;t think of any good looking way of routing a cable up there. After a bit of digging around I ordered a &lt;a href=&quot;https://www.bumm.de/en/products/dynamo-rucklichter/produkt/331-2ask.html&quot;&gt;Busch + Müller Secula&lt;/a&gt; designed to be mounted on the wheel strut.&lt;/p&gt;
&lt;p&gt;Both lights were easy to install on the bike, and they also seem to work well together. My dynamo is a bit underpowered at 2,4 W, but they shine steadily even at very low riding speeds.&lt;/p&gt;
&lt;p&gt;In addition to installing the lights, I also replaced the gear-shift cable and its housing, which was cracked and rusty.&lt;/p&gt;
&lt;p&gt;I might add a rack to the bike in the future, but I&#39;ll probably go for a minimal low-rider front pannier rack or something, to make sure the bike is still as impractical as possible. We&#39;ve lived in a house for a couple of years now, with a nice and comfy garage for the bike to sleep in, so I hope we have many years (and kilometers) together ahead of us.&lt;/p&gt;
</description>
      <pubDate>Sun, 25 Feb 2024 10:48:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/my-bicycle/</guid>
    </item>
    <item>
      <title>Bicycle commuting premiere</title>
      <link>https://www.martingunnarsson.com/posts/bicycle-commuting-premiere/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;A while back I &lt;a href=&quot;https://www.martingunnarsson.com/posts/making-a-case-for-commuting-by-bike.md&quot;&gt;convinced myself&lt;/a&gt; bicycle commuting was a feasible alternative to driving for me, and started preparing to giving it a go. The plan wasn&#39;t to give up driving entirely, but to start going by bike maybe once per week or so, to introduce some much needed exercise into my life.&lt;/p&gt;
&lt;p&gt;My preparations mostly consisted of some light bicycle maintenance, installing new lights and replacing the gear shift cable and housing, as well as browsing for racks and panniers and drooling over the beautiful photography over at &lt;a href=&quot;https://bikepacking.com/&quot;&gt;Bikepacking.com&lt;/a&gt;. I also started using the bike to run errands in the village where we live, but the weather felt a bit too rough still to start commuting by bike. Of course I could have done it, it was by no means impossible, but part of my plan was to make at least the first few rides a positive experience, to lay the foundation for a new habit. The weather remained pretty bad for a while, then the entire family got the flu, and a few more weeks passed.&lt;/p&gt;
&lt;h2&gt;Just do it&lt;/h2&gt;
&lt;p&gt;Once I felt recovered and ready to give bicycle commuting a go, spring was in the air, and most days the weather was pretty decent. Ironically the days were also long enough that I wouldn&#39;t need my newly installed bike lights for the commute, but having them surely doesn&#39;t hurt.&lt;/p&gt;
&lt;p&gt;Reviewing the weather forecast early in the week, the Friday looked like a good day to start. It was going to be windy, but no heavy rain like on the previous days. I don&#39;t have any rain clothes, so rain is currently a bit of a hindrance.&lt;/p&gt;
&lt;p&gt;Reading the news the night before my maiden voyage, I saw warnings for storm gusts along the coast, which put my plans in jeopardy. Friday morning was indeed pretty windy, but the weather app on my phone showed winds from the south the entire day, which means a crosswind both ways of the commute. Battling a really strong headwind in either direction would probably have been a bit too much for my first attempt, but crosswinds I could live with. Also, I had sort of set my mind on going, and I was excited to finally give it a try.&lt;/p&gt;
&lt;p&gt;During my &lt;em&gt;research phase&lt;/em&gt; I thought a lot about a perfect setup, with panniers for my computer, spare clothes, a towel etc, and maybe even a bike trailer to take the kids to preschool. On the other hand, I knew that if I had to have all of that in place before riding to work, it would probably never happen.&lt;/p&gt;
&lt;p&gt;So, I cleared my mind of all the cool adventure bikes from the internet and just put my computer and notepad in my trusty backpack, chucked a spare t-shirt in there for good measure, and that was it for packing. I dropped the kids off at preschool using the car, and then drove home and changed vehicles.&lt;/p&gt;
&lt;h2&gt;Departure&lt;/h2&gt;
&lt;p&gt;And just like that, I was off. We live at the top of a hill, so heading out on the bike is always easy. Still, even going down the steepest part of the hill I could feel the wind holding me back. This could be challenging.&lt;/p&gt;
&lt;p&gt;I tried not to push too hard, as I didn&#39;t want to arrive at the office all sweaty. At the same time I didn&#39;t want my commute to take &lt;em&gt;too&lt;/em&gt; long, since I had a pretty busy day ahead of me. I found a pace and cadence that felt sustainable without being too slow, and just chugged along. The first 2/3 of the distance is a very straight bike path across open fields, and the wind was always present but actually not that annoying. It slowed me down I&#39;m sure, but it didn&#39;t stop me.&lt;/p&gt;
&lt;p&gt;One stretch of this open part of the commute felt surprisingly boring. I first thought it was because there were no good mental milestones in sight. You could see pretty far ahead, but there was nothing to look forward to. When I drove into town with the family the day after I could clearly see that the boring part is also slightly uphill, and I&#39;m sure that contributes to the tediousness. Still, it&#39;s only an 11 km commute in total, so it&#39;s by no means endless. Once I had pedaled through the boring part and pushed up a short but somewhat steep hill, I found myself in the city. Here I expected to be slowed down by traffic lights and other people in general, but I had an almost comically good timing with the traffic lights and hardly had to slow down at all. At this point my legs were letting me know that they were not used to this much pedaling, so I probably wasn&#39;t going very fast anyway.&lt;/p&gt;
&lt;h2&gt;Arrival&lt;/h2&gt;
&lt;p&gt;After exactly 40 minutes I arrived at work. I haven&#39;t timed my car commute (which includes a ~10 minutes walk from the parking garage), but I did get to the office a little bit later than usual. Maybe on a day with less wind the ride would have been a bit quicker, but the biggest time saver in the long run is probably just getting used to the commute and getting more fit.&lt;/p&gt;
&lt;p&gt;I wasn&#39;t sweaty after the ride, so once I got into the office I could just grab a cup of coffee and get to work as I always do. This very good, as a change of clothes or a shower would have made the bike commute less viable from a scheduling perspective.&lt;/p&gt;
&lt;h2&gt;Heading home&lt;/h2&gt;
&lt;p&gt;Last summer I did a test-ride, going almost all the way to work and then back home again in one go. On that day my legs were getting pretty sore on the way back, but with an entire working day between the ride there and the one home, my legs would have had plenty of time to recover, right? Wrong.&lt;/p&gt;
&lt;p&gt;Jumping on the bike in the afternoon my legs started complaining almost immediately, and the ride home felt much tougher even though the crosswind had shifted slightly to give me a tiny bit of tailwind.&lt;/p&gt;
&lt;p&gt;Going up the steepest part of the hill up to my village, I acutally jumped off the bike and walked a couple of hundred meters. I could have made it on the bike if I really wanted to, but I&#39;d rather take a short break and get home with a positive impression of the adventure. Again, building a new habit is the long term goal here.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Commuting by bike &lt;em&gt;is&lt;/em&gt; a very feasible alternative for me. With some good rain clothes and waterproof shoes I could probably ride in almost any weather without too much hassle, but I&#39;ll try to stick to nice days to begin with. I&#39;m very happy that I just got going instead of waiting to have the perfect gear and perfect weather. It was a bit risky to get sucked into articles and videos about bikepacking and adventure bikes when in reality I have very modest needs. It&#39;s not like I&#39;m crossing the African continent or anything, I&#39;m just riding my bike to work.&lt;/p&gt;
&lt;p&gt;I&#39;m not opposed to the idea of upgrading my equipment, but doing it after a number of rides makes for a much more informed decision. It&#39;s also very satisfying to buy something that you know will improve your life, rather than spending a lot of money upfront and then ending up with half of the stuff gathering dust in some closet anyway.&lt;/p&gt;
&lt;p&gt;I&#39;ll try to commute by bike at least once per week, and I&#39;m pretty tempted to do it on Monday already. I&#39;m also well aware that I have a tendency to overdo things in the beginning, so taking an extra rest day or two certainly won&#39;t hurt.&lt;/p&gt;
&lt;p&gt;The ride was a bit tough, but it was a very positive experience all in all. I could see myself going all-in on bike commuting in the future, but we&#39;ll see. A little bit is certainly better than nothing.&lt;/p&gt;
</description>
      <pubDate>Sat, 24 Feb 2024 06:15:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/bicycle-commuting-premiere/</guid>
    </item>
    <item>
      <title>Local links in Eleventy, Part 2</title>
      <link>https://www.martingunnarsson.com/posts/local-links-in-eleventy-part-2/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The only thing I didn&#39;t like about &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; when I started using it was that local &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; links, like &lt;code&gt;/posts/my-post.md&lt;/code&gt; wasn&#39;t automatically transformed into their rendered URL counterparts, like &lt;code&gt;/posts/my-post/&lt;/code&gt;. Having links pointing to the source files allows you to follow them locally when working with the content, and many Markdown editors can both help autocomplete links as you type as well as highlight any broken ones. For me, however, the most important benefit of working this way is that the content can remain unaware of the output format, so to speak. The less site details there are in the content, the more portable that content will be.&lt;/p&gt;
&lt;p&gt;A while back I wrote a post about &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-maintaining-working-local-links/index.md&quot;&gt;how to maintain working local links in Eleventy&lt;/a&gt;, and have them automatically transformed into the correct corresponding URLs at build-time. After publishing that post, I got a comment from Eleventy author &lt;a href=&quot;https://www.zachleat.com/&quot;&gt;Zach Leatherman&lt;/a&gt;, acknowledging that the current behavior isn&#39;t optimal, and that &lt;a href=&quot;https://github.com/11ty/eleventy/issues/84&quot;&gt;the issue&lt;/a&gt; would be solved in Eleventy 3.0.0. Delivering on that promise, Zach has now introduced the updated behavior as part of Eleventy 3.0.0-alpha5, yay! If you&#39;re ready to jump aboard the 3.0.0 train, here are some &lt;a href=&quot;https://www.11ty.dev/blog/canary-eleventy-v3/&quot;&gt;good, official directions&lt;/a&gt; to follow.&lt;/p&gt;
&lt;h2&gt;New times&lt;/h2&gt;
&lt;p&gt;My own solution to this problem was to implement an &lt;a href=&quot;https://www.11ty.dev/docs/config/#transforms&quot;&gt;Eleventy Transform&lt;/a&gt; that replaced all local paths in links with rendered URLs. The official solution comes in two parts, a plugin and a filter, and the plugin uses a similar approach to mine. It&#39;s always nice to see you&#39;ve gotten &lt;em&gt;something&lt;/em&gt; right as a beginner, so that felt validating!&lt;/p&gt;
&lt;h3&gt;The plugin&lt;/h3&gt;
&lt;p&gt;The new Eleventy behavior comes as a bundled plugin called &lt;a href=&quot;https://www.11ty.dev/docs/plugins/inputpath-to-url/&quot;&gt;InputPath to URL&lt;/a&gt; that is optional and turned off by default. It can be enabled with a couple of lines in &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; InputPathToUrlTransformPlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;InputPathToUrlTransformPlugin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  
  &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once activated, the plugin will replace all references to local paths with their URL counterparts. My own link transformer only dealt with &lt;code&gt;a[href]&lt;/code&gt;, but the InputPath to URL plugin also transforms &lt;code&gt;audio[src]&lt;/code&gt;, &lt;code&gt;img[src]&lt;/code&gt;, &lt;code&gt;[srcset]&lt;/code&gt; and a few more.&lt;/p&gt;
&lt;h3&gt;The filter&lt;/h3&gt;
&lt;p&gt;In addition to the above plugin, the new Eleventy release provides a filter called &lt;a href=&quot;https://www.11ty.dev/docs/filters/inputpath-to-url/&quot;&gt;inputPathToUrl&lt;/a&gt;, that offers the same URL-remapping functionality. Here’s a Nunjucks example:&lt;/p&gt;
&lt;pre class=&quot;language-jinja2&quot;&gt;&lt;code class=&quot;language-jinja2&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token jinja2 language-jinja2&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;{{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/posts/my-post.md&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;inputPathToUrl&lt;/span&gt; &lt;span class=&quot;token delimiter punctuation&quot;&gt;}}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;My post&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above will be rendered as:&lt;/p&gt;
&lt;pre class=&quot;language-jinja2&quot;&gt;&lt;code class=&quot;language-jinja2&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/posts/my-post/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;My post&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I think my own link transformer turned out pretty good, but it’s still a relief to remove custom code in favor of a common solution. Packaging the functionality as a plugin is tidier than explicitly configuring a transform, which I previously did. The next time I want to customize something in Eleventy, I’ll probably use that as an excuse to try implementing a plugin of my own.&lt;/p&gt;
&lt;p&gt;I’m very grateful that Zach picked up this old issue, Eleventy just keeps getting better and better. I can also happily report that the migration of this site to Eleventy 3.0.0 required only very minor changes. Check out &lt;a href=&quot;https://github.com/11ty/eleventy/milestone/40&quot;&gt;the plan for the 3.0.0 release&lt;/a&gt; for more details about what’s included.&lt;/p&gt;
&lt;p&gt;And lastly, if you want to read more about Eleventy, I&#39;ve created a page with &lt;a href=&quot;https://www.martingunnarsson.com/tags/eleventy&quot;&gt;all my Eleventy posts&lt;/a&gt;, so that you can follow along on my adventures.&lt;/p&gt;
&lt;p&gt;Happy blogging!&lt;/p&gt;
</description>
      <pubDate>Wed, 14 Feb 2024 05:58:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/local-links-in-eleventy-part-2/</guid>
    </item>
    <item>
      <title>Making a case for commuting by bike</title>
      <link>https://www.martingunnarsson.com/posts/making-a-case-for-commuting-by-bike/</link>
      <description>&lt;p&gt;I live 11 km from work, and there are excellent bike paths all the way, yet I commute by car. I&#39;ve successfully made several small but positive changes to my life in the last few months, and I would like to take on my habit of commuting by car next. I&#39;m not looking to change it completely, but at least soften it up a bit. I have a colleague and a neighbor who frequently commute the same route as me by bike, so I know it&#39;s both physically and practically possible.&lt;/p&gt;
&lt;p&gt;This post is mostly a way for me to start considering biking to work as a realistic option, rather than a theoretical possibility, but I hope it can also inspire others to change up their daily commutes.&lt;/p&gt;
&lt;h2&gt;Pros&lt;/h2&gt;
&lt;p&gt;The biggest benefit of biking to work (by far) would be getting exercise and fresh air. I spend much of my working day sitting, either at the computer or in different meeting rooms. By driving to work I waste a great opportunity to fit exercise into my busy schedule and add some more sitting instead. No good. Starting the day with a decent bike ride would most likely improve my workdays in many ways as well.&lt;/p&gt;
&lt;p&gt;For my current commute, I drive an electric car that I charge at home, which is cost-effective compared to some other car types. Still, biking is &lt;em&gt;free&lt;/em&gt;, and every day I choose the bike instead of the car I would work towards lowering our electricity bill.&lt;/p&gt;
&lt;p&gt;Another driving-related expense is parking, but here the savings aren&#39;t as clear-cut. In the parking garage I use, parking a single working day costs a little more than 100 kr (which at the time of writing roughly equals $10). That&#39;s not what I pay though, as there are also monthly permits for 750 kr, which is a very good deal for me since I drive to the office pretty much every weekday of the month. Come to think of it, I get more value out of a monthly permit the more I drive that month, which is not great motivation.
To save money on parking by driving less, I would have to drive &lt;em&gt;a lot&lt;/em&gt; less. On paper, this looks like an added incentive to take the bike, but in reality, I find the pressure off-putting. I would rather bike because I enjoy it than because I &amp;quot;have to&amp;quot;.&lt;/p&gt;
&lt;h2&gt;Cons&lt;/h2&gt;
&lt;p&gt;I live in southern Sweden, and it’s almost always windy here. Most of my commute is across open fields without any cover from the wind. Some days it probably wouldn’t be that pleasant to be a bike commuter here, if I&#39;m being honest. I would need some flexible rain clothes in order not to arrive at work completely soaked on rainy days, but depending on how hard I have to pedal I might need a change of clothes (and perhaps even a quick shower) after the commute anyway.&lt;/p&gt;
&lt;p&gt;Previously I parked at a garage closer to work, but that meant more time-consuming driving through the city center as well as a substantially higher parking fee. The change to my current garage reduced my drive by 5-7 minutes but introduced a ten-minute walk instead. Before making the change I had the same concerns about the weather as I do regarding the bike commute now, but in reality, it hasn’t been a problem at all. Chances are that the bike commute too would be less of a problem than I think.&lt;/p&gt;
&lt;p&gt;In any case, clothes for the day may have to be brought along, and so would my briefcase or other bag with my computer, notepad, and various other small items. All of this would have to be well-protected from the elements and carried comfortably. My bike has no rack either front or back, so a backpack is the most straightforward option. I&#39;m not thrilled by the idea of biking back and forth with a backpack every day, so this is an area I would like to explore more.&lt;/p&gt;
&lt;p&gt;The biggest problem I have to solve to make the bike commute work in practice is dropping off the kids at preschool. We have a two-year-old and an almost-six-year-old, and I currently bring them to preschool every morning as part of my drive to work. I think our son, the almost-six-year-old, could ride his own bike there without a problem, but then there are &lt;em&gt;those&lt;/em&gt; mornings, when everything has the potential to cause a meltdown. Those mornings would not be pleasant if he had to make it there under his own power, and I would have to have an alternative solution in place from the beginning.&lt;/p&gt;
&lt;p&gt;There are two good options the way I see it: getting some kind of long-tail or cargo bike with room for two kids, or getting a bike trailer. Utility bikes like that are really cool, but a trailer is the most sensible option in every way. They are much cheaper than a utility bike and could be detached and left at the preschool for when my wife picks the kids up in the afternoon. Many bike trailers can also be pushed like a stroller, so she could use it with or without her bike. Regardless, leaving the extra bulk behind before crossing those windy fields would be a big relief!&lt;/p&gt;
&lt;p&gt;I guess there&#39;s also the option of driving the kids to preschool and then going back home and switching to the bike. It feels unnecessarily complicated and time-consuming, but could be a way of getting started without any new gear at all, or work as a last resort on difficult days.&lt;/p&gt;
&lt;h2&gt;Time&lt;/h2&gt;
&lt;p&gt;Time is, perhaps surprisingly, an aspect of the commute I haven&#39;t thought that much about yet. I think driving from the preschool to the parking garage takes roughly 25 minutes depending on traffic, and walking from the garage to the office takes about 10 minutes on top of that.&lt;/p&gt;
&lt;p&gt;Last summer I tried riding my bike to the city and back just to see what it was like, but on that day I didn&#39;t have time to go all the way to the office. Starting at our house, the part of my commute covering the countryside and city outskirts took 18 minutes on a day with unusually calm wind. The rest of the commute into the city center would be slowed down by traffic lights and people in general, so even though I went more than half the distance in those 18 minutes, the remainder could easily take just as long. As mentioned I also started from our house rather than from the preschool, and I&#39;m not sure how that affects the time. The preschool is located on top of a pretty steep hill, so going there by bike will also take much longer (relatively speaking) than doing so by car.&lt;/p&gt;
&lt;p&gt;All in all, the car will likely be faster in the end, but perhaps not by that much. The potential need for a change of clothes after biking would also take some time, which must be taken into consideration. I&#39;m prepared to spend slightly longer on my commute to get all the benefits of biking, but not too much. My days are already unnecessarily tight, and I often work a bit at home in the evening to compensate for not reaching a full workday at work. If possible I would like to avoid adding to this part of my day.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Commuting by bike &lt;em&gt;is&lt;/em&gt; a realistic option for me, and by writing this post my mind is set on giving it a try. As mentioned, I could start off with what I have in the beginning, but there are some investments I could make to ensure the transition is as practical and friction-free as possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Getting a bike trailer&lt;/li&gt;
&lt;li&gt;Getting a helmet&lt;/li&gt;
&lt;li&gt;Getting rain clothes&lt;/li&gt;
&lt;li&gt;Minor bike fixes/maintenance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these are huge expenses, and even if it would take a while to make the money back by not driving, the benefits are clearly worth it. Also, buying gear is great fun when you have such a good reason for doing so!&lt;/p&gt;
&lt;p&gt;Additional investments could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding some kind of rack to my bike&lt;/li&gt;
&lt;li&gt;Getting a waterproof bag for said rack&lt;/li&gt;
&lt;li&gt;Getting a good bike phone mount&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The phone mount is the least important thing on this list, but I know that numbers, analytics, and charts motivate me, so it could be an additional incentive.&lt;/p&gt;
&lt;h3&gt;First steps&lt;/h3&gt;
&lt;p&gt;My plan is to not aim too high, but to start off by taking the bike &lt;em&gt;some&lt;/em&gt; days. Like, the sunny ones, when there aren’t any gale-force winds. On the other hand, I’ll also make sure not to wait for perfect weather either, because then I&#39;m never going to get started.&lt;/p&gt;
&lt;p&gt;This is a whole new area of my life to geek out about, and I have to say the challenge itself is very exciting! The roads are pretty snowy and slippery now, but as soon as conditions improve just a little, my mind is set on making a test run without any new equipment, etc. just to see how it feels, how much time it takes, and whether there are any unexpected pros or cons. I&#39;m looking forward to this new adventure!&lt;/p&gt;
</description>
      <pubDate>Thu, 18 Jan 2024 06:03:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/making-a-case-for-commuting-by-bike/</guid>
    </item>
    <item>
      <title>Right here, right now</title>
      <link>https://www.martingunnarsson.com/posts/right-here-right-now/</link>
      <description>&lt;p&gt;One day while reading a blog post I noticed the blog had a &amp;quot;Now&amp;quot; page, where the author briefly described what they were up to at the moment. Unfortunately, I can&#39;t recall the specific blog, but it did link to &lt;a href=&quot;https://nownownow.com/about&quot;&gt;Nownownow.com&lt;/a&gt;, which is a page by &lt;a href=&quot;https://sive.rs/&quot;&gt;Derek Sivers&lt;/a&gt; promoting the concept of the Now page. Two things stuck with me: Derek seemed to be a very interesting person, and as someone with periodic &amp;quot;flavor of the week&amp;quot;-style obsessions, the Now page concept was very appealing to me.&lt;/p&gt;
&lt;p&gt;Derek compares the Now page to an About page (albeit a less static one), an auxiliary page with an easy URL that tells visitors something about the author. I used to have an About page, but as part of the &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-brings-me-joy.md&quot;&gt;rewrite of this blog&lt;/a&gt;, I turned it into a section on the &lt;a href=&quot;https://www.martingunnarsson.com/&quot;&gt;homepage&lt;/a&gt; instead. This eliminated the need for any menu or other navigation elements, and to avoid adding any back solely for a Now page, I decided to include that too as a section on the homepage.&lt;/p&gt;
&lt;h2&gt;An unsatisfactionary attempt&lt;/h2&gt;
&lt;p&gt;I could have simply added a paragraph to the homepage and updated that as needed, but my mind rarely lets me get away that easily. I knew I wanted to keep a record of the history of my Now content one way or the other, perhaps to list it as a timeline of sorts in the future.&lt;/p&gt;
&lt;p&gt;My first attempt was having the content as a separate file, rendered as part of the homepage using the &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; &lt;a href=&quot;https://www.11ty.dev/docs/plugins/render/&quot;&gt;Render plugin&lt;/a&gt;. The plugin is included with Eleventy, and can be activated in the &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; EleventyRenderPlugin &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;EleventyRenderPlugin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the plugin activated, you can render any Eleventy template from any other template like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    {% renderFile &#39;./_includes/now.md&#39; %}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This worked fine, and I got an automatic file history in Git. However, that isn&#39;t the most accessible place if I would like to list previous entries on the site in the future.&lt;/p&gt;
&lt;h2&gt;Round 2&lt;/h2&gt;
&lt;p&gt;One night as I was falling asleep I realized that my Now content should &lt;em&gt;of course&lt;/em&gt; be treated like any other content in Eleventy. This would probably have been obvious from the beginning if I had a little bit more Eleventy experience, but it took some time for the penny to drop.&lt;/p&gt;
&lt;h3&gt;Content&lt;/h3&gt;
&lt;p&gt;Eleventy is highly flexible regarding content. You can have different types of content for different purposes, located anywhere in the file structure, and you have a lot of control over the behavior it all.&lt;/p&gt;
&lt;p&gt;For my Now content, I had a fairly clear idea of what I wanted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A list of tiny Markdown files in a separate directory, to keep things tidy.&lt;/li&gt;
&lt;li&gt;An Eleventy collection of that content, sorted by date.&lt;/li&gt;
&lt;li&gt;Prevent the content from being rendered into individual pages.&lt;/li&gt;
&lt;li&gt;Keep the files clear of any frontmatter or other metadata.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I created a directory called &lt;code&gt;now&lt;/code&gt;, and added my first post called &lt;code&gt;2024-01-04.md&lt;/code&gt;, for lack of a better name. Even though I didn&#39;t want any metadata in the files, I still needed to specify at least two pieces of information for each of them: a tag to keep the content organized in a collection and a date to have them sorted correctly.&lt;/p&gt;
&lt;h3&gt;Automatic tagging&lt;/h3&gt;
&lt;p&gt;Since the Now posts live in a directory of their own, I knew I could utilize the &lt;a href=&quot;https://www.11ty.dev/docs/data-cascade/&quot;&gt;Data cascade&lt;/a&gt; in Eleventy, and add a directory data file that applies common attributes to all files in that directory. Directory data files should be located inside their respective directories and named following the pattern &lt;code&gt;[directory name].11tydata.[format]&lt;/code&gt;. Following this convention, I created a file named &lt;code&gt;now.11tydata.json&lt;/code&gt; in my &lt;code&gt;now&lt;/code&gt; directory, containing a single property:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;now&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This little snippet assigns the &lt;code&gt;now&lt;/code&gt; tag to all posts in the &lt;code&gt;now&lt;/code&gt; directory. As a resuly, they&#39;ll be accessible as &lt;code&gt;collections.now&lt;/code&gt; in Eleventy.&lt;/p&gt;
&lt;h3&gt;Excluding content from rendering&lt;/h3&gt;
&lt;p&gt;With a small addition to the directory data file, I could also disable the content from being rendered into individual pages:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;now&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;permalink&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;permalink&lt;/code&gt; property can be set to a pattern telling Eleventy where to put the pages rendered from each post. Setting it to &lt;code&gt;false&lt;/code&gt; means no rendering will take place at all.&lt;/p&gt;
&lt;h3&gt;Dates&lt;/h3&gt;
&lt;p&gt;As mentioned, I also wanted to set the date of each post somehow, both for my historical records and to be able to display the latest post on the homepage. I was prepared to implement some kind of computed property setting the date based on the filename. To have something to work with, I added a couple of more posts to the &lt;code&gt;now&lt;/code&gt; directory. To my surprise, they automatically appeared in the correct order. Was this just a fluke, or some clever feature in Eleventy? A bit of both, it turns out!&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.11ty.dev/docs/dates/&quot;&gt;Content Dates&lt;/a&gt; section of the Eleventy docs held the answer. Eleventy goes to great lengths to find a date for each post. You can specify one in different ways, but ...&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If a date key is omitted from the file, we then look for a YYYY-MM-DD format anywhere in the file path (even folders).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Clearly a very clever feature in Eleventy, but I also got lucky naming my files according to &lt;a href=&quot;https://en.wikipedia.org/wiki/ISO_8601&quot;&gt;the one true date format&lt;/a&gt;, and that Eleventy looks for this very format as part of the file path. What I expected to be the trickiest part of this setup turned out to work straight out of the box. Sweet!&lt;/p&gt;
&lt;h3&gt;Rendering&lt;/h3&gt;
&lt;p&gt;With the &lt;code&gt;now&lt;/code&gt; collection sorted (*badum-tss*), all I had to do was to include the latest post from it on my homepage. I already do this with my latest &lt;em&gt;post&lt;/em&gt; post, so this part was easy:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;{% set now = collections.now | last %}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Right now&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    {{ now.content | safe }}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The latest post in the collection is picked out using the &lt;code&gt;last&lt;/code&gt; filter. The content of the post can then be accessed using the &lt;code&gt;now.content&lt;/code&gt; property. The &lt;code&gt;safe&lt;/code&gt; filter inserts the content of the post as-is, without any escaping of special characters.&lt;/p&gt;
&lt;p&gt;The same thing can be written as a shorter but less readable oneliner:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;{{ (collections.now | last).content | safe }}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The solution to my problem turned out to be quite straightforward, perhaps even too much so for a tutorial like this. However, I learned several things about Eleventy in the process, and I hope others will too.&lt;/p&gt;
&lt;p&gt;My main takeaways are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eleventy is built to deal with content, and large amounts of it too. Any time you&#39;re working with a series of content, even if you just want to use something like the latest piece of it, consider using tags and the resulting collections before exploring other alternatives.&lt;/li&gt;
&lt;li&gt;The Data cascade in Eleventy is both flexible and powerful, and directory data files are a convenient way to avoid repetition of metadata for content within a directory. In my case, leaving out the entire frontmatter section from my content files felt like a big win.&lt;/li&gt;
&lt;li&gt;It&#39;s pleasing to see all the ways Eleventy tries to find a date for each piece of content, including inspecting the file path, and extra pleasing to see it&#39;s specifically looking for ISO 8601 dates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting to know Eleventy has been a true delight, and this little project only strengthens my positive impressions of it. For those who want to know more, I&#39;ve collected &lt;a href=&quot;https://www.martingunnarsson.com/tags/eleventy&quot;&gt;all my Eleventy-related posts&lt;/a&gt; for easy reference. I encourage anyone embarking on a web project to consider Eleventy. It&#39;s flexibility and user-friendlyness makes it a joy to work with!&lt;/p&gt;
</description>
      <pubDate>Sun, 07 Jan 2024 21:51:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/right-here-right-now/</guid>
    </item>
    <item>
      <title>The apps I use</title>
      <link>https://www.martingunnarsson.com/posts/apps-2023/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://listen.hemisphericviews.com/097&quot;&gt;Episode 97&lt;/a&gt; of the &lt;a href=&quot;https://www.hemisphericviews.com/&quot;&gt;Hemispheric Views&lt;/a&gt; podcast, called Duel of the Defaults, set of quite a trend on the web. In that episode, &lt;a href=&quot;https://loungeruminator.net/&quot;&gt;Martin Feld&lt;/a&gt; and &lt;a href=&quot;https://grepjason.sh/&quot;&gt;Jason Burk&lt;/a&gt; compete for the coveted &amp;quot;Mr. Default&amp;quot; title, by comparing the different apps they use, and deciding who is most &lt;em&gt;default&lt;/em&gt; (according to arbitrary decisions by judge &lt;a href=&quot;https://canion.omg.lol/&quot;&gt;Andrew Canion&lt;/a&gt;). After this, people started listing their own &amp;quot;default apps&amp;quot;, which is not quite what the show was about, but a fun thing to do nontheless. &lt;a href=&quot;https://rknight.me/&quot;&gt;Robb Knight&lt;/a&gt; has collected many of these on &lt;a href=&quot;https://defaults.rknight.me/&quot;&gt;his website&lt;/a&gt;, and allow people to add theirs using Git pull requests (which I did). I&#39;m a bit late to the game with this, but better late than never!&lt;/p&gt;
&lt;h2&gt;Apps&lt;/h2&gt;
&lt;p&gt;These are the apps I use personally, i.e. not for work, on both my iPhone and Mac:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;📨 Mail Client: Mail&lt;/li&gt;
&lt;li&gt;📮 Mail Server: iCloud, with custom domain&lt;/li&gt;
&lt;li&gt;📝 Notes: Notes, &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;✅ To-Do: Reminders&lt;/li&gt;
&lt;li&gt;📷 iPhone Photo Shooting: iOS Camera&lt;/li&gt;
&lt;li&gt;🟦 Photo Management: Photos&lt;/li&gt;
&lt;li&gt;📆 Calendar: Calendar&lt;/li&gt;
&lt;li&gt;📁 Cloud File Storage: iCloud Drive&lt;/li&gt;
&lt;li&gt;📖 RSS: N/A&lt;/li&gt;
&lt;li&gt;🙍🏻‍♂️ Contacts: Contacts&lt;/li&gt;
&lt;li&gt;🌐 Browser: Safari&lt;/li&gt;
&lt;li&gt;💬 Chat: Messages (if that&#39;s considered chat), very occasionally Slack&lt;/li&gt;
&lt;li&gt;🔖 Bookmarks: Some old bookmarks in Safari, nothing else&lt;/li&gt;
&lt;li&gt;📑 Read It Later: Occasionally using the Reading list in Safari, but rarely read the things I put there&lt;/li&gt;
&lt;li&gt;📜 Word Processing: N/A&lt;/li&gt;
&lt;li&gt;📈 Spreadsheets: Google Sheets&lt;/li&gt;
&lt;li&gt;📊 Presentations: N/A&lt;/li&gt;
&lt;li&gt;🛒 Shopping Lists: Notes, a shared note with my wife&lt;/li&gt;
&lt;li&gt;🍴 Meal Planning: N/A&lt;/li&gt;
&lt;li&gt;💰 Budgeting and Personal Finance: Google Sheets, &lt;a href=&quot;https://www.zlantar.se/&quot;&gt;Zlantar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📰 News: N/A (I get news from websites and &lt;a href=&quot;https://mastodon.social/@gunnarsson&quot;&gt;Mastodon&lt;/a&gt;, but don&#39;t use a specific news app)&lt;/li&gt;
&lt;li&gt;🎵 Music: Apple Music&lt;/li&gt;
&lt;li&gt;🎤 Podcasts: Apple Podcasts&lt;/li&gt;
&lt;li&gt;🔐 Password Management: iCloud keychain, and a sprinkle of &lt;a href=&quot;https://1password.com/&quot;&gt;1Password&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;When I started going through this list I was actually surprised by how &amp;quot;default&amp;quot; I am. I&#39;m hardly using any non-Apple apps/services at all. Part of this is because I want to stay away from Google, but also that Apple services usually work pretty damn well. Of course, there are more powerful and advanced options for every single category, but I rarely need anything outside the ordinary for my daily business, so the default apps suit me well.&lt;/p&gt;
&lt;p&gt;If you speak Swedish, or wouldn&#39;t mind hearing me and &lt;a href=&quot;https://www.bjoreman.com/&quot;&gt;Fredrik Björeman&lt;/a&gt; doing so, we&#39;ve recorded &lt;a href=&quot;https://synk.fm/podcast/9-ett-schweiziskt-mejlkonto/&quot;&gt;an episode&lt;/a&gt; of our podcast &lt;a href=&quot;https://synk.fm/&quot;&gt;Synk&lt;/a&gt; on this very topic.&lt;/p&gt;
</description>
      <pubDate>Thu, 04 Jan 2024 06:43:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/apps-2023/</guid>
    </item>
    <item>
      <title>Automatic pre-processing of post images in Eleventy</title>
      <link>https://www.martingunnarsson.com/posts/eleventy-automatic-image-pre-processing/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;When I moved this blog &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-brings-me-joy.md&quot;&gt;from Hugo to Eleventy&lt;/a&gt; I wanted to set everything up from scratch to be &lt;em&gt;in control&lt;/em&gt;. No more massive themes with mysterious features you&#39;re afraid to change because you don&#39;t know the potential side-effects. That being said, I did look around at different &lt;a href=&quot;https://www.11ty.dev/docs/starter/&quot;&gt;Eleventy starters&lt;/a&gt;, like the &lt;a href=&quot;https://github.com/11ty/eleventy-base-blog&quot;&gt;Eleventy base blog&lt;/a&gt;, and took note of a few features to set up myself.&lt;/p&gt;
&lt;p&gt;Since I was in full control of the site, getting good performance was surprisingly easy, resulting in a perfect score from the &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/overview/&quot;&gt;Lighthouse&lt;/a&gt; analyzer in Chrome. While working on the site I would occationally run the analyzer again, to make sure the score hadn&#39;t deteriorated. &lt;a href=&quot;https://www.martingunnarsson.com/posts/serial-debugging-on-attiny85/index.md&quot;&gt;One single post&lt;/a&gt; always failed, since it had a massive image in JPEG format that I just hadn&#39;t bothered to resize manually. Ideally I shouldn&#39;t have to, either. This was a perfect use case for some Eleventy automation!&lt;/p&gt;
&lt;h2&gt;Plugins&lt;/h2&gt;
&lt;p&gt;I was already aware of the official &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/&quot;&gt;Image plugin&lt;/a&gt; for Eleventy, which can take a source image and generate copies in various sizes and formats. It&#39;s using the &lt;a href=&quot;https://www.npmjs.com/package/sharp&quot;&gt;Sharp&lt;/a&gt; image processor for Node under the hood. I&#39;m very familiar with this, so writing a similar feature from scratch would probably have been very doable. However, it&#39;s very comfortable to just use a proven implementation when available.&lt;/p&gt;
&lt;p&gt;What did surprise me a little was that the Image plugin works exclusively with a custom shortcode, rather than regular &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; images. I write all my posts in Markdown, and I wanted to have all images automatically pre-scaled in various formats, so that visitors could get the best quality and performance for their device. So, I had the Eleventy Image plugin, and I knew the Markdown processor used in Eleventy, &lt;a href=&quot;https://github.com/markdown-it/markdown-it&quot;&gt;markdown-it&lt;/a&gt; has good support for plugins, to alter the behavior of certain elements. I just had to tie it all together. Things were looking promising!&lt;/p&gt;
&lt;p&gt;By pure chance I then stumbled upon the &lt;a href=&quot;https://github.com/solution-loisir/markdown-it-eleventy-img&quot;&gt;markdown-it-eleventy-img&lt;/a&gt; plugin for markdown-it, that does exactly what I was just about to do myself. The thought of something like this already existing hadn&#39;t even crossed my mind, and I got that familiar feeling of relief mixed with disappointment that arises when a challenge is taken away from you. Of course I could still have written my own implementation, but in this case the goal was more important to me than the journey itself.&lt;/p&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Time to get everything set up! First I installed the &lt;code&gt;markdown-it-eleventy-img&lt;/code&gt; plugin using &lt;code&gt;npm&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; markdown-it-eleventy-img --save-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I added it to my &lt;code&gt;.eleventy.js&lt;/code&gt; configuration file:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; markdownItEleventyImg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;markdown-it-eleventy-img&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;amendLibrary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;mdLib&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; mdLib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdownItEleventyImg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;imgOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;widths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;368&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;736&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;auto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;webp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;avif&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Don&#39;t do this, see below&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;outputDir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;_site/assets/images/scaled&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;        
            &lt;span class=&quot;token literal-property property&quot;&gt;urlPath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/assets/images/scaled&#39;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;globalAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token literal-property property&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;100vw&#39;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m using the &lt;code&gt;amendLibrary&lt;/code&gt; function to add the plugin to the &lt;code&gt;markdown-it&lt;/code&gt; instance already in use in Eleventy. In addition to the plugin itself, I also supply a tiny bit of configuration. Here I specify the image widths and formats I want to generate, and specify both where I want the resulting files to be saved and what URL path this corresponds to.&lt;/p&gt;
&lt;p&gt;Lo and behold, it worked!&lt;/p&gt;
&lt;p&gt;An image in a Markdown post like this:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;[&lt;span class=&quot;token content&quot;&gt;An image&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;/assets/images/image.png&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Used to be converted into a regular image tag:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/assets/images/image.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;An image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But now, it resulted in a whole &lt;em&gt;buffet&lt;/em&gt; of images for the browser to choose from:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image/png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/assets/images/scaled/FR-0A22Z5T-368.png 368w, /assets/images/scaled/FR-0A22Z5T-736.png 736w, /assets/images/scaled/FR-0A22Z5T-900.png 900w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;100vw&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image/webp&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/assets/images/scaled/FR-0A22Z5T-368.webp 368w, /assets/images/scaled/FR-0A22Z5T-736.webp 736w, /assets/images/scaled/FR-0A22Z5T-900.webp 900w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;100vw&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;image/avif&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;srcset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/assets/images/scaled/FR-0A22Z5T-368.avif 368w, /assets/images/scaled/FR-0A22Z5T-736.avif 736w, /assets/images/scaled/FR-0A22Z5T-900.avif 900w&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;100vw&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;An image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/assets/images/scaled/FR-0A22Z5T-368.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;900&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;900&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;picture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Problems&lt;/h2&gt;
&lt;p&gt;Even though my first test was successful, I immediately noticed a few problems as I started clicking around the site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large images, while shrunk to fit the page width, didn&#39;t have their height shrink proportionally, resulting in a stretched appearance.&lt;/li&gt;
&lt;li&gt;I have my post images located in the same directory as the post, e.g. &lt;code&gt;/posts/my-post/index.md&lt;/code&gt; may be using &lt;code&gt;/posts/my-post/image.png&lt;/code&gt; by just referring to it as &lt;code&gt;./image.png&lt;/code&gt;, and these couldn&#39;t be found by the plugin.&lt;/li&gt;
&lt;li&gt;The browser still loaded the much larger JPEG or PNG file even though I was offering delicious WEBP and AVIF versions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Scaling proportionally&lt;/h3&gt;
&lt;p&gt;I&#39;m still not &lt;em&gt;entirely&lt;/em&gt; sure why the images wouldn&#39;t scale proportionally automatically, but I think I may have caused it myself. Before I had any image scaling in place, I was just using CSS to make sure the images fit within the page:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;article img&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the new setup, the &lt;code&gt;img&lt;/code&gt; element automatically received &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes. It seems like browsers set the image width according to the &lt;code&gt;max-width&lt;/code&gt; CSS rule and the height based on the &lt;code&gt;height&lt;/code&gt; property. The solution was to add a CSS rule for the height as well:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;article img&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;max-width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; auto&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It feels like I might be missing something here, but this workaround has consistently solved the problem in all browsers I&#39;ve tested.&lt;/p&gt;
&lt;h3&gt;Post-relative images&lt;/h3&gt;
&lt;p&gt;As I prefer to keep post images in the same directory as the post itself, avoiding a large central collection of images with unclear usage, the issue with the &lt;code&gt;markdown-it-eleventy-img&lt;/code&gt; plugin not finding theses was a showstopper. Fortunately, the solution was very straightforward, as the plugin offers full control over where to look for different images using the &lt;code&gt;resolvePath&lt;/code&gt; option:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;amendLibrary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;md&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;mdLib&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; mdLib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdownEleventyImg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;imgOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;globalAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;resolvePath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; isPostImage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isPostImage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Resolve path to post-relative images&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Resolve path to global images&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_site&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I always refer to images relative to posts using paths beginning with &lt;code&gt;./&lt;/code&gt;, which makes them easy to identify here. While I don&#39;t currently use any &amp;quot;global&amp;quot; images in my posts, I didn&#39;t want to break that functionality. I&#39;m using the Node &lt;code&gt;path&lt;/code&gt; library to piece together the paths needed for the two different use cases. Additionally, I&#39;ve noticed it&#39;s sometimes necessary to clean the previous build for the image plugin to work correctly. This is likely since it attempts to avoid regenerating images that already exist. If you encounter weird problems building your site after changing the image configuration, try cleaning the site by deleting the &lt;code&gt;_site&lt;/code&gt; directory, and then rebuild it.&lt;/p&gt;
&lt;h3&gt;Modern file formats not used&lt;/h3&gt;
&lt;p&gt;With everything else working, it was perplexing to see that all browsers consistently favored the original PNG or JPEG format over of the significantly smaller modern alternatives. What gives! Like so many times before, an assumption turned out to be the source of the problem. My image format specification was set as &lt;code&gt;[&#39;auto&#39;, &#39;webp&#39;, &#39;avif&#39;]&lt;/code&gt;, with &lt;code&gt;auto&lt;/code&gt; representing the original format of the source image. I had presumed that browsers would automatically select the &lt;em&gt;best&lt;/em&gt; format they supported, but this feels somewhat naïve in retrospect. In reality, browsers simply pick the &lt;em&gt;first&lt;/em&gt; format they support. While I had subconsiosly arranged the formats based on their likelyhood of support, I should in fact have used the exact opposite: &lt;code&gt;[&#39;avif&#39;, &#39;webp&#39;, &#39;auto&#39;]&lt;/code&gt;. With that little change, Safari, Firefox and Chrome all picked the AVIF file, and there was much rejoicing.&lt;/p&gt;
&lt;h2&gt;Further improvements&lt;/h2&gt;
&lt;p&gt;Finally, I was poised to bask in the accolades of the Lighthouse analyzer for my post featuring that large image ... right? Unfortunately, not quite. The post in question is pretty long, and the Lighthouse report complained I should defer loading of the images until the user scrolled down a bit. Fair enough. I skimmed through the official &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/offscreen-images&quot;&gt;Lighthouse documentation of the issue&lt;/a&gt;, which was rather unhelpful. The linked &lt;a href=&quot;https://web.dev/articles/codelab-use-lazysizes-to-lazyload-images&quot;&gt;codelab on lazy loading images&lt;/a&gt; quickly delved into JavaScript solutions, which I very much wanted to avoid (on the client side).&lt;/p&gt;
&lt;p&gt;Fortunately, there are better options. Modern browsers support the &lt;code&gt;loading=&amp;quot;lazy&amp;quot;&lt;/code&gt; property for images, as well as &lt;code&gt;decoding=&amp;quot;async&amp;quot;&lt;/code&gt; to further optimize for rapid page rendering. To my delight, the image plugin allows for any arguments to the image tag to be specified in the &lt;code&gt;globalAttributes&lt;/code&gt; configuration option:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;amendLibrary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;md&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;mdLib&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; mdLib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdownEleventyImg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;imgOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;globalAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;100vw&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;lazy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;decoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;async&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;resolvePath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s so satisfying to observe the network panel in the browser development tools and see the images load automatically as you scroll down the page. &lt;em&gt;Finally&lt;/em&gt; that post got a perfect 100/100/100/100 score in Lighthouse, just like the rest of this site!&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;After all the modifications above, my final &lt;code&gt;markdown-it-eleventy-img&lt;/code&gt; configuration in &lt;code&gt;.eleventy.js&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;amendLibrary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;md&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;mdLib&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; mdLib&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;markdownEleventyImg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;imgOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;widths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;368&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;736&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;avif&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;webp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;auto&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;urlPath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/assets/images/scaled&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;outputDir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;_site/assets/images/scaled&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;globalAttributes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;100vw&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;lazy&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;decoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;async&#39;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;resolvePath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;filepath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; isPostImage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isPostImage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Resolve path to post-relative images&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// Resolve path to global images&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;_site&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filepath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this configuraion, everything has been functioning beautifully so far. Since I don&#39;t frequently use images in my posts, it remains to be seen if any issues will aries in the future. I&#39;m very happy that I found the &lt;code&gt;markdown-it-eleventy-img&lt;/code&gt; plugin, and that it&#39;s been so flexible to work with. Kudos to the author, &lt;a href=&quot;https://github.com/solution-loisir&quot;&gt;Mathieu Huot&lt;/a&gt;, very impressive work!&lt;/p&gt;
&lt;p&gt;For those who want to read more about Eleventy customization, I&#39;ve created a page listing &lt;a href=&quot;https://www.martingunnarsson.com/tags/eleventy&quot;&gt;all my Eleventy posts&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Fri, 29 Dec 2023 08:02:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/eleventy-automatic-image-pre-processing/</guid>
    </item>
    <item>
      <title>Customizing external links with Eleventy</title>
      <link>https://www.martingunnarsson.com/posts/eleventy-customizing-external-links/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;I&#39;ve recently written a couple of posts about the different ways I&#39;ve customized the &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, the static site generator used for this site. In the previous post I described modifying the HTML rendered by Eleventy to &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-maintaining-working-local-links/index.md&quot;&gt;maintain working internal links&lt;/a&gt; between &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; files. This modification makes Eleventy treat local links the way I believe it should by default. Next on my improvement wishlist was addressing the lack of visual distinction between internal and external links in my posts. With my successful link-transformation effort fresh in mind, I immediately set out to add some additional HTML post-processing.&lt;/p&gt;
&lt;h2&gt;Post-processing&lt;/h2&gt;
&lt;p&gt;In the post linked above I introduced a small class that iterates over all links in every page, modifying the internal ones, so in my case it was very straightforward to add another snippet to the same iteration. Here’s a standalone version of the code dealing only with the external links, which you can include in your &lt;code&gt;.eleventy.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cheerio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cheerio&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; externalLinkSvg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;svg&gt;...&amp;lt;/svg&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTransform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;updateLinks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Only apply this to HTML files&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;outputPath &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;outputPath&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; $ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cheerio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;a[href^=&quot;http&quot;]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;externalLinkSvg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addClass&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;external&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;target&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;_blank&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;rel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;noopener&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code utilizes &lt;a href=&quot;https://cheerio.js.org/&quot;&gt;Cheerio&lt;/a&gt; to parse the HTML content of the generated post. It specifically searches for all &lt;code&gt;a&lt;/code&gt; elements with a &lt;code&gt;href&lt;/code&gt; attribute beginning with &lt;code&gt;http&lt;/code&gt;, as a crude way of identifying external links. Since I just want to modify text links (e.g. not image links, like the contact icons in the page footer), the matching &lt;code&gt;a&lt;/code&gt; elements are then filtered to exclude those without text content. The remaining collection is iterated over using &lt;code&gt;each()&lt;/code&gt;, and within this loop any desired customizations can be made. In this example, I append a small snippet of SVG right into the link content, add a CSS class, set the &lt;code&gt;target&lt;/code&gt; attribute to &lt;code&gt;_blank&lt;/code&gt; and &lt;code&gt;rel&lt;/code&gt; to &lt;code&gt;noopener&lt;/code&gt; just for good measure.&lt;/p&gt;
&lt;p&gt;At the end, the modified DOM is returned in the form of HTML, and the work of the link transformer is complete.&lt;/p&gt;
&lt;h2&gt;An alternate approach&lt;/h2&gt;
&lt;p&gt;Instead of modifying the HTML produced by Eleventy, &lt;a href=&quot;https://sheetsj.com/&quot;&gt;Jeff Sheets&lt;/a&gt; suggested I could use a plugin for the Markdown parser used by Eleventy, &lt;a href=&quot;https://github.com/markdown-it/markdown-it&quot;&gt;markdown-it&lt;/a&gt;, to generate the links the way I want them right from the start. This feels like a more refined solution, but I haven&#39;t been able to achieve the result I want using this approach.&lt;/p&gt;
&lt;p&gt;There are multiple plugins modifying links in various ways, some (like &lt;a href=&quot;https://github.com/trunkcode/markdown-it-external-link&quot;&gt;markdown-it-external-link&lt;/a&gt; and &lt;a href=&quot;https://github.com/rotorz/markdown-it-external-links&quot;&gt;markdown-it-external-links&lt;/a&gt;) are even specifically made to add additional attributes to external links. However, I haven&#39;t found any plugin that can both add attributes and append content, like the inline SVG icon I&#39;m using, to the links.&lt;/p&gt;
&lt;p&gt;There are several plugins that can add CSS classes to external links, and using CSS I could instead insert my SVG icon into the external links using the &lt;code&gt;::after&lt;/code&gt; pseudo-element like this:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;a.external::after&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token url&quot;&gt;&lt;span class=&quot;token function&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/assets/external.svg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But unfortunately this still won&#39;t work the way I want. The problem is that this site has a light and a dark mode, and the link color varies slightly depending on the mode. To ensure the external link icon matches the color of the link itself, I insert it into the link content as inline SVG. This in combination with the property &lt;code&gt;fill=&amp;quot;currentColor&amp;quot;&lt;/code&gt; in the SVG code makes the icon inherit the color of the parent element. Sadly this doesn&#39;t work when the SVG is inserted using the &lt;code&gt;::after&lt;/code&gt; pseudo-element. In fact, I haven&#39;t found any way to set the color of the SVG inserted this way using CSS, it seems like the only option is to set the color in the SVG file.&lt;/p&gt;
&lt;p&gt;I&#39;m tempted to add support for modifying the link content to one of the plugins mentioned above, but I&#39;d have to check if the authors are interested in this first. Another option would be to build my own plugin, but this would merely move my custom code to a different file, and I don&#39;t feel &lt;em&gt;that&lt;/em&gt; bad about my post-processing approach.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While modifying the output of the Markdown processing itself would be a better solution, adding transforms to modify the HTML produced by Eleventy is a good way to ensure your site comes out just the way you want. Using Cheerio for this task does bring back some memories of ... erm, &lt;em&gt;less elegant&lt;/em&gt; &lt;a href=&quot;https://jquery.com/&quot;&gt;jQuery&lt;/a&gt; hacks from the past, but it feels much better to have this type of code as part of a build step rather than live on a site.&lt;/p&gt;
&lt;p&gt;For those who want to follow along on my Eleventy adventure, I&#39;ve got a page listing &lt;a href=&quot;https://www.martingunnarsson.com/tags/eleventy&quot;&gt;all my Eleventy posts&lt;/a&gt;. I&#39;m still learning Eleventy myself, but hopefully I can share some insights and lessons learned from setting up this site.&lt;/p&gt;
&lt;p&gt;Happy tinkering!&lt;/p&gt;
</description>
      <pubDate>Tue, 26 Dec 2023 19:23:12 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/eleventy-customizing-external-links/</guid>
    </item>
    <item>
      <title>Maintaining working local links in Eleventy</title>
      <link>https://www.martingunnarsson.com/posts/eleventy-maintaining-working-local-links/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;This is the second post detailing the different customizations I&#39;ve implemented in &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; for this site. In the first post I briefly described setting up &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-syntax-highlighting.md&quot;&gt;code syntax highlighting&lt;/a&gt;, which primarily involved installing and configuring an existing plugin. Another tweak I really enjoy is the way links are handled, and for that I had to write some code off my own! I&#39;ve actually customized both internal and external links, each in different ways, but let&#39;s focus on the internal ones first.&lt;/p&gt;
&lt;h2&gt;Internal links&lt;/h2&gt;
&lt;p&gt;As I wrote in my earlier &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-brings-me-joy.md&quot;&gt;introduction to Eleventy&lt;/a&gt;, I very much appreciate the clean separation of content and presentation that Eleventy and many other static site generators provide. I write all content for this site in &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; files, and ideally I would like to keep the contet free of any &amp;quot;knowledge&amp;quot; about the site. I&#39;ve even considered managing it completely separately from the site&#39;s codebase, perhaps in &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt;? This is mostly out of principle, but it also ensures that the content can easily be transferred to a different tool in the future, should the need arise. Having page metadata in the content front matter is very convenient however, and for convenience I&#39;m willing to bend my principles slightly. I guess I&#39;m more pragmatic than idealistic in the end.&lt;/p&gt;
&lt;p&gt;As I was getting to know Eleventy, the only thing I was slightly disappointed by was the fact that local links between Markdown files aren&#39;t automatically transformed into their &amp;quot;online&amp;quot; format during page rendering. For example, a post called &lt;code&gt;/posts/frogs.md&lt;/code&gt; will be rendered into a HTML page in the file &lt;code&gt;/posts/frogs/index.html&lt;/code&gt; by default, and should be linked to as &lt;code&gt;/posts/frogs/&lt;/code&gt;, but any Markdown links to this file remains unchanged as &lt;code&gt;/posts/frogs.md&lt;/code&gt;. Consequently, for the links to function correctly on the rendered site, they must use the online format in the source files. This introduces precisely the kind of website-specific knowledge into the content that I want to avoid. It also means I have to make a choice between having functional links on the live site or within the Markdown files. It&#39;s an easy choice of course, but ideally such a compromize shouldn&#39;t have to be made. Additionally, having proper local links in the source files means text editors can help with autocompletion while writing them, which can be very helpful for long paths.&lt;/p&gt;
&lt;p&gt;I decided to try to do something about this before filling up the site with too much content. First I reverted all the local links back to their original form, i.e. &lt;code&gt;/posts/frogs.md&lt;/code&gt; in the example above. I then implemented an Eleventy transform to convert these into the appropriate online format. In Eleventy, a transform is a function that gets called after each page is rendered, allowing for post-processing modifications. They are typically configured in &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; LinkTransformer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./_scripts/linktransformer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addTransform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;updateLocalLinks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkTransformer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transformLinks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;LinkTransformer&lt;/code&gt; is a small class I wrote for this task. It utilizes &lt;a href=&quot;https://cheerio.js.org/&quot;&gt;Cheerio&lt;/a&gt; to traverse and modify the rendered HTML content, and replaces all local links ending in &lt;code&gt;.md&lt;/code&gt; with the ones ending in &lt;code&gt;/&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cheerio &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;cheerio&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LinkTransformer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;transformLinks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;content&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; outputPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// Only transform HTML pages&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;outputPath&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; $ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cheerio&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Iterate over all &amp;lt;a&gt; tags&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; link&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;href&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            &lt;span class=&quot;token comment&quot;&gt;// Replace &#39;.md&#39; with &#39;/&#39; at the end of the href&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;href&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; href&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;.md$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Being relatively new to Eleventy, I&#39;m not entirely sure if this approach is the most straightforward, but no one would be happier than me if it turns out I&#39;ve simply overlooked an obvious, built-in solution. In any case, I&#39;m quite pleased with the results. Now I can follow links between my posts both locally in my text editor and online on the published site. It would be interesting to hear what the author of Eleventy, &lt;a href=&quot;https://www.zachleat.com/&quot;&gt;Zach Leatherman&lt;/a&gt;, would think about this modification. From a design principle standpoint, I believe maintaining functional local links is a better approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;&lt;br /&gt;
&lt;a href=&quot;https://fediverse.zachleat.com/@zachleat/111603476120229529&quot;&gt;Zach replied&lt;/a&gt; to me on Mastodon pointing out that this is &lt;a href=&quot;https://github.com/11ty/eleventy/issues/84&quot;&gt;an old issue in Eleventy&lt;/a&gt; and that he&#39;ll get it resolved in the upcoming 3.0.0 release 🤩&lt;/p&gt;
&lt;p&gt;Next up I&#39;ll describe how I modified the tiny LinkTransformer class to also deal with external links, so stay tuned for that. I&#39;ve also set up a page with &lt;a href=&quot;https://www.martingunnarsson.com/tags/eleventy&quot;&gt;all my Eleventy posts&lt;/a&gt; for those who want an easy overview of my Eleventy journey.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Another update&lt;/strong&gt;&lt;br /&gt;
Zach has now implemented the solution to the aforementioned issue, and as of Eleventy 3.0.0-alpha5 you get this functionality as part of the package. I&#39;ve written &lt;a href=&quot;https://www.martingunnarsson.com/posts/local-links-in-eleventy-part-2.md&quot;&gt;a follow-up post on how to use it&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Addendum&lt;/h2&gt;
&lt;p&gt;Out of curiosity I tried opening my &lt;code&gt;posts&lt;/code&gt; directory in Obsidian, and to my surprise it presents the front matter in a very pleasant way. I just played around with it very briefly, so I can&#39;t be a hundred percent sure, but unfortunately it seems Obsidian only wants to autocomplete local links while creating special Obsidian links, so perhaps I&#39;ll stick to &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; for now. Still, it made me even more curious about working with content fully separated from the code in the future.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.martingunnarsson.com/posts/eleventy-maintaining-working-local-links/eleventy-content-in-obsidian.png&quot; alt=&quot;This post being edited in Obsidian&quot; /&gt;&lt;/p&gt;
</description>
      <pubDate>Mon, 18 Dec 2023 16:10:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/eleventy-maintaining-working-local-links/</guid>
    </item>
    <item>
      <title>Syntax highlighting code in Eleventy posts</title>
      <link>https://www.martingunnarsson.com/posts/eleventy-syntax-highlighting/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;This site is built using &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, a very nice little static site generator. I&#39;ve previously written a post about &lt;a href=&quot;https://www.martingunnarsson.com/posts/eleventy-brings-me-joy.md&quot;&gt;how and why I ended up using it&lt;/a&gt;, but that mostly covers how to get up and running with generic content. This is the first post in a little series outlining the different customizations I&#39;ve made in order for Eleventy to produce just the result I want.&lt;/p&gt;
&lt;h2&gt;Syntax highlighting&lt;/h2&gt;
&lt;p&gt;One of the few features outside the basics I was looking for when I compared static site generators was syntax highlighting of code blocks. I often write about programming-related topics here, and having syntax highlighting makes any code snippets included much more pleasant to read. Luckily there&#39;s an official &lt;a href=&quot;https://www.11ty.dev/docs/plugins/syntaxhighlight/&quot;&gt;Eleventy plugin&lt;/a&gt; for that, which is using &lt;a href=&quot;https://prismjs.com/&quot;&gt;Prism&lt;/a&gt; under the hood. Prism has support for &lt;a href=&quot;https://prismjs.com/#supported-languages&quot;&gt;a long long list of languages&lt;/a&gt; (although I&#39;m not sure whether all of them are included in the Eleventy plugin), and it seems to be both realizable and really fast as well.&lt;/p&gt;
&lt;p&gt;There are three steps involved in setting up syntax highlighting:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Installing the plugin&lt;/li&gt;
&lt;li&gt;Adding CSS for the code color scheme&lt;/li&gt;
&lt;li&gt;Tagging your code blocks with the language used&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;The plugin is installed using &lt;code&gt;npm&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @11ty/eleventy-plugin-syntaxhighlight --save-dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once installed it&#39;s available in the local Node environment, and can be added to Eleventy with just a couple of lines in &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; syntaxHighlight &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy-plugin-syntaxhighlight&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;syntaxHighlight&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Further configuration ...&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you just want to get up and running,  that&#39;s all there is to the installation step. You can also pass an options object to the &lt;code&gt;addPlugin&lt;/code&gt; function to adjust the settings for the plugin as well, but I&#39;m guessing the defaults are fine for most people.&lt;/p&gt;
&lt;h2&gt;Adding CSS&lt;/h2&gt;
&lt;p&gt;Now Prism will run, and automatically introduce some additional markup (a lot of &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; tags, to be precise) to the HTML produced from the code blocks, but to actually see any visible syntax highlighting you also need to include a Prism theme in your site. This is a small CSS file dictating the color and style of different elements of the code. There are &lt;a href=&quot;https://github.com/PrismJS/prism-themes&quot;&gt;plenty of themes to choose from&lt;/a&gt;, and if none of them suit your needs you can of course modify one, or create your own from scratch. Some of the more popular themes are available from various CDNs, but I decided to include mine as part of the site CSS.&lt;/p&gt;
&lt;h2&gt;Languages&lt;/h2&gt;
&lt;p&gt;After adding the CSS you&#39;re technically all set, but at this point I &lt;em&gt;still&lt;/em&gt; wasn&#39;t getting any syntax highlighting. I&#39;m not sure whether this is theme specific, but to finally see the colorized code in all its glory I also had to tag the code blocks with the corresponding programming language:&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;html&lt;/span&gt;
&lt;span class=&quot;token code-block language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Spatula City!&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;css&lt;/span&gt;
&lt;span class=&quot;token code-block language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; lime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token code&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;span class=&quot;token code-language&quot;&gt;js&lt;/span&gt;
&lt;span class=&quot;token code-block language-js&quot;&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please refer to the &lt;a href=&quot;https://prismjs.com/#supported-languages&quot;&gt;list of supported languages&lt;/a&gt; to see the what tag to use for different languages. If you&#39;re using a template language other than Markdown, please refer to the &lt;a href=&quot;https://www.11ty.dev/docs/plugins/syntaxhighlight/&quot;&gt;plugin documentation&lt;/a&gt; for how to utilize syntax highlighting.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I really like the syntax-highlighted code blocks and I&#39;m very happy with the way this plugin works. I am however a little bit worried that the highlighting actually makes the code much less user-friendly for readers using text-to-speech devices, screen readers, and similar technologies. I&#39;ll have to investigate further to understand if this actually is a problem. Perhaps the fact that Prism only seems to inject &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; tags means it doesn&#39;t interfere with assistive technology. If it indeed is a problem I would really like to find an elegant solution to it, so if you happen to know more about this, please let me know.&lt;/p&gt;
</description>
      <pubDate>Sat, 16 Dec 2023 20:30:42 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/eleventy-syntax-highlighting/</guid>
    </item>
    <item>
      <title>Eleventy brings me joy</title>
      <link>https://www.martingunnarsson.com/posts/eleventy-brings-me-joy/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;In the olden days, I had a completely custom site written in classic ASP and kept it up for many years, until social media platforms became my primary outlet for sharing thoughs online. I still built the occasional website for various endeavors, but moved gradually to more ready-made systems like &lt;a href=&quot;https://wordpress.org/&quot;&gt;Wordpress&lt;/a&gt; and &lt;a href=&quot;https://www.tumblr.com/&quot;&gt;Tumblr&lt;/a&gt;. It had been a very long time since I last had a personal website when a few toots on &lt;a href=&quot;https://mastodon.social/@gunnarsson&quot;&gt;Mastodon&lt;/a&gt; inspired me to start blogging again.&lt;/p&gt;
&lt;h2&gt;A false start&lt;/h2&gt;
&lt;p&gt;A few years ago I built a very basic site for documenting my home brewing using &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt;, and liked the concept. Hugo is a static site generator - a tool that transforms content such as &lt;a href=&quot;https://en.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; files into fully-fledged web pages by merging them with templates dictating layout and design. In certain generators the content can be any form of structured data, even sourced from APIs or databases on the fly, which makes maintaining a large number of pages very manageable without a lot of tedious manual work.&lt;/p&gt;
&lt;p&gt;In my opinion the biggest benefit of static site generators lies in the separation of content and presentation. This allows for updating and improving (or even fully replacing) the design and layout of a site without the need for a messy content migration. Using an basic, open format like markdown also means there&#39;s no content lock-in effect. If you&#39;re unhappy with one tool you can easily move to another.&lt;/p&gt;
&lt;p&gt;I like the simplicity of Markdown and find the concept of static site generators appealing, so I decided to use Hugo for my new blog. I quickly installed it, created a new site using the included scaffolding tool, added what seemed to be the default theme, and started building my site.&lt;/p&gt;
&lt;p&gt;I made some minor tweaks to the theme by adding a small CSS file of my own and customized a few of the theme files to better suit my needs. Then, I began blogging. I had a lot of fun; writing was easy, Markdown suits me well, and I received some positive feedback on Mastodon that encouraged me to keep going.&lt;/p&gt;
&lt;p&gt;However, alongside this growing joy of writing, I found myself increasingly dissatisfied with almost every aspect of the site&#39;s design, and visiting it just made me disappointed.&lt;/p&gt;
&lt;p&gt;Since the writing was so enjoyable, the logical next step was to create my own theme to make sure the output was equally pleasing. Hugo comes with a scaffolding tool for creating new themes as well, but all these structures and tools were starting to get to me. When I saw the large default file structure for a new theme I simply had enough and started looking for a better way of doing things.&lt;/p&gt;
&lt;h2&gt;Hello Eleventy&lt;/h2&gt;
&lt;p&gt;There are a whole bunch of different static site generators out there, but I kept seeing recommendations for &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, which sparked my interest, and after reading a bit about it it certainly seemed like a good choice for me as well. It&#39;s popular and actively being developed, it&#39;s simple and flexible, and it&#39;s &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node&lt;/a&gt;-based. A popular tool increases chances of finding both good information and technical assets like plugins. Its simplicity and flexibility particularly appealed to me after my falling-out with Hugo. Plus, I&#39;m already very comfortable with the Node environment.&lt;/p&gt;
&lt;p&gt;Eleventy is perhaps the least opinionated tool I&#39;ve ever used; it doesn&#39;t require any particular file structure, it supports a long list of templating formats out of the box (HTML, Markdown, WebC, JavaScript, Liquid, Nunjucks, Handlebars, Mustache, EJS, Haml and Pug, to be precise), and you can add more if needed. You can even mix and match formats within the same site to suit your needs.&lt;/p&gt;
&lt;p&gt;When you create a site with Eleventy, it doesn&#39;t come with any pre-installed themes. In fact, themes aren&#39;t really part of the Eleventy vocabulary, which feels like a big relief for me. Instead of themes Eleventy has layout templates. They&#39;re special templates used to display information from the other templates in the system, and can use any format you like, but a common convention seems to be using Markdown for content and &lt;a href=&quot;https://mozilla.github.io/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; for layout. Layouts can be &lt;em&gt;chained&lt;/em&gt;, which means a layout can have a parent layout, for example like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;posts/post.md&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-Markdown&quot;&gt;&lt;code class=&quot;language-Markdown&quot;&gt;---
title: My Post
layout: post
---

This is my content.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_includes/post.njk&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;---
layout: main
---

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;{{ title }}&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
{{ content }}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_includes/main.njk&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
{{ content }}
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First the content template &lt;code&gt;post.md&lt;/code&gt; points to &lt;code&gt;post.njk&lt;/code&gt; and get wrapped in that. That layout template then points to &lt;code&gt;main.njk&lt;/code&gt; and the output from the first step becomes the content for the second step, and so on. If you only have one type of content you can of course use a single layout template for that, but it&#39;s a very nice way of dividing a page structure into layers. Anyways, the end result of the above example would look like this:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;My Post&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;This is my content.&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; element surrounding the innermost content comes from the built in conversion from Markdown to HTML, but the rest is just a combination of the two layout templates and the content. As you may have noticed there&#39;s also a header block surrounded by &lt;code&gt;---&lt;/code&gt;. This is called the front matter, and it&#39;s the place to put metadata about the content. You can put any information here, and it will automatically be available to any layouts used etc. This data is also merged &amp;quot;upwards&amp;quot; in the layout chain, so that partial layouts like the ones above can add data to the input from the underlaying template. Very convenient.&lt;/p&gt;
&lt;h2&gt;New beginnings&lt;/h2&gt;
&lt;p&gt;So, armed with a new, lightweight technical setup, I moved all my Markdown files over from Hugo and started working on a brand-new version of this site. The default front matter formats differ slightly between Hugo and Eleventy, but that was easily adjusted. Now that I&#39;ve learned a little bit about Eleventy, I know I  could have adapted the front matter parsing (which uses &lt;a href=&quot;https://www.npmjs.com/package/gray-matter&quot;&gt;gray-matter&lt;/a&gt; under the hood and allows for a lot of customization) to fit the Hugo format instead, but it really wasn&#39;t a big deal. The rest of the content was usable, but Hugo&#39;s link syntax had to be replaced with regular paths.&lt;/p&gt;
&lt;p&gt;I also decided to write my own layouts rather than using a starter project or copy-pasting some &amp;quot;best practice&amp;quot; template. Before I started, I made up some half-conscious principles for the site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use modern HTML and CSS, ignore old browsers, let the good times roll!&lt;/li&gt;
&lt;li&gt;Use a minimalistic approach, less is more&lt;/li&gt;
&lt;li&gt;Use good semantic markup that clearly describes the structure of the content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wrote a minimal structure for the framework around the content, the &amp;quot;page chrome&amp;quot; if you will, using as descriptive markup as possible:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;...&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;...&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;header&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Title&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Content goes here.&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;article&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;footer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;...&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;footer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s clean, it&#39;s clear, and to my delight there&#39;s not a single &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; on the entire site. I don&#39;t like &lt;code&gt;div&lt;/code&gt;s, and I&#39;m very happy to just use semantic markup and a touch of CSS to style it. Some other things that this site lacks are JavaScript, analytics/tracking,  CSS frameworks, responsive design, and custom fonts (actually, I specify &lt;code&gt;sans-serif&lt;/code&gt; as font in my CSS and just &lt;em&gt;let go&lt;/em&gt;). It wasn&#39;t until I realized I wanted a favicon that I added a funny picture of myself to the site, and other than the HTML document itself that&#39;s the only file that is loaded (unless you&#39;re reading a post that has images of its own, that is). This typically leads to loading times of around 100 ms, which I&#39;m very happy with.&lt;/p&gt;
&lt;p&gt;It turns out that when you write clean and descriptive markup like this, you score really high on the &lt;a href=&quot;https://github.com/GoogleChrome/lighthouse&quot;&gt;Lighthouse&lt;/a&gt; tests as a bonus. A few minor tweaks here and there, and I found myself getting a perfect 100/100 score for all four main categories (Performance, Accessibility, Best Practices, and SEO) for both Desktop and Mobile. I&#39;m well aware of the fact that not all websites can be this basic and/or minimalistic, so I&#39;m not too smug about it, but it feels great to get a perfect score like that. The lack of &amp;quot;deliberate&amp;quot; responsive design is also satisfying. Again, with a basic layout like this, everything just works. I added a small padding to the body tag (which also benefits larger screens) and that was it for my mobile adaptation.&lt;/p&gt;
&lt;p&gt;I&#39;ve got the above markup divided into two layout templates: one for posts and one for pages, and one additional main layout that wraps around either type of content. There really isn&#39;t anything else to it; just write a few text files and you&#39;re good to go.&lt;/p&gt;
&lt;h2&gt;Tooling&lt;/h2&gt;
&lt;p&gt;The main Eleventy command &lt;code&gt;npx @11ty/eleventy&lt;/code&gt; performs the actual static site generation. It also accepts a &lt;code&gt;--serve&lt;/code&gt; argument, which starts a small development server that rebuilds the site and reloads the browser on relevant file changes. I added a few shortcuts to my &lt;code&gt;package.json&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;...
&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;dev&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npx @11ty/eleventy --serve&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npx @11ty/eleventy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;clean&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rm -r _site&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I sit down to write or work on the site I just run &lt;code&gt;npm run dev&lt;/code&gt; and the development server is automatically started. The live reload is very convenient both for tinkering with the site and for writing text. Even though many editors have live preview for Markdown, it&#39;s nice to be able to switch over to the actual site and see what your work in progress will look like. I store the entire site in a Git repository both for backup and for deployment.&lt;/p&gt;
&lt;h2&gt;Publishing&lt;/h2&gt;
&lt;p&gt;Due to unforseen circumstances with my regular go-to hosting service &lt;a href=&quot;https://www.digitalocean.com/products/app-platform&quot;&gt;DigitalOcean App Platform&lt;/a&gt; I decided to publish my site on &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;. They&#39;re similar in that they both provide hosting solutions with continuous deployment support. This means you can connect your service of choice to your Git repository, and it will build and deploy a new version of your site for every commit you make to a specific branch. Netlify is a bit more advanced than DigitalOcean in that they can also deploy a private version of your site for essentially any commit or pull request, so that you can preview the result before going live. I&#39;ve configured it to deploy to a private URL for my main branch, while a separate &amp;quot;production&amp;quot; branch updates the public site.&lt;/p&gt;
&lt;p&gt;Configuring the site on Netlify was a breeze. I granted Netlify (limited) access to my blog repository, and it automatically picked up both the &lt;code&gt;build&lt;/code&gt; command from my &lt;code&gt;package.json&lt;/code&gt; and the default &lt;code&gt;_site&lt;/code&gt; output folder used by Eleventy. Sweet. A few seconds later I could access my site online, albeit through a temporary hostname. Once I was happy with the site, I pointed my domain name to Netlify as per their instructions, waited for DNS records to propagate and then for a SSL certificate to be generated, and then it all just worked.&lt;/p&gt;
&lt;p&gt;Having a Continuous Deployment service like this ties in really well with my Git-based workflow, and even allows me to push a small change to the live site right from my phone (using &lt;a href=&quot;https://workingcopyapp.com/&quot;&gt;Working copy&lt;/a&gt; on iOS as my Git client) if I want to.&lt;/p&gt;
&lt;h2&gt;Joy&lt;/h2&gt;
&lt;p&gt;Now that this new site is live, I can happily say that it genuinely puts a smile on my face. I sometimes open it on my phone just because it feels good. I&#39;m glad that I took the time to write the entire layout properly from scratch, and I&#39;m pleasantly surprised by how little time it actually took. Eleventy has been fantastic to work with. Despite handling some pretty advanced tasks, it has never felt complicated to me. The &lt;a href=&quot;https://www.11ty.dev/docs/community/&quot;&gt;Eleventy community&lt;/a&gt; seems to be a very friendly and helpful one, and it&#39;s been easy to find answers to my questions. I&#39;m eager to give back, and I guess this post is my first small step in doing so. I have another Eleventy-themed post coming, describing some of the customizations I made to have things just the way I like them. It&#39;s these small, yet significant, details that ensure the site truly brings me joy.&lt;/p&gt;
</description>
      <pubDate>Thu, 14 Dec 2023 21:13:14 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/eleventy-brings-me-joy/</guid>
    </item>
    <item>
      <title>ATtiny85 Fuses Explained</title>
      <link>https://www.martingunnarsson.com/posts/attiny85-fuses-explained/</link>
      <description>&lt;p&gt;In the world of microcontrollers, a fuse is not only a hardware device that protects circuitry from electrical overload, it&#39;s also a concept of low-level configuration settings stored in non-volatile memory. These fuses are typically used to set hardware configuration parameters like clock sources and boot sequences, things that are essential when initializing the microcontroller.&lt;/p&gt;
&lt;p&gt;Once set, these fuses remain set until explicitly changed, even if new firmware is flashed into the program memory.&lt;/p&gt;
&lt;h2&gt;Fuses in the ATtiny85&lt;/h2&gt;
&lt;p&gt;The ATtiny85 works exactly like this. It has a number of fuses to control essential hardware configuration. The parameters are stored in configuration bits grouped into fuse bytes. Instead of talking about the various bits being &amp;quot;set&amp;quot;, the word &amp;quot;programmed&amp;quot; is often used. The reason for this is that a set bit typically refers to a value or 1, while a programmed bit actually has a value of 0. This is slightly unintuitive, but has to do with the fact that the type of memory used is filled with ones when erased, so that the default, &amp;quot;empty&amp;quot; state of a bit is 1. With that out of the way, let&#39;s go through the different fuse bytes and their configuration bits.&lt;/p&gt;
&lt;h3&gt;Low fuse byte&lt;/h3&gt;
&lt;p&gt;The low fuse byte determines basic settings mostly related to timing. The different bits represent the following parameters:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Bit(s)&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Name&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;0-3&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;CKSEL&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Clock source select. These bits define the clock source to be used by the microcontroller.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;4-5&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;SUT&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Startup time. These two bits define the startup time of the microcontroller. A short startup time is great under optimal circumstances, but with a shaky power supply or an external clock source requiring time to stabilize, it might be necessary to take it easy. The exact timing of the startup depends on the clock source selected.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;CKOUT&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Clock out. If programmed, the system clock signal will be output on the CLKO pin, which is PB2 on the ATtiny85. This overrides any other use of that pin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;CKDIV8&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Clock divide. If programmed, a clock divider will be activated, dividing the speed of the system clock by 8. To read more about the clock speed, please see my post &lt;a href=&quot;https://www.martingunnarsson.com/posts/setting-the-attiny85-clock-speed.md&quot;&gt;Setting the ATtiny85 clock speed&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;High fuse byte&lt;/h3&gt;
&lt;p&gt;The high fuse byte contains more advanced configuration parameters than the low one. The parameters are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Bit(s)&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Name&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;0-2&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;BODLEVEL&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Brown-out detection level. These bits control the voltage level threshold for the brown-out reset.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;EESAVE&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;If programmed, the EEPROM will remain intact when the chip erase command is run, and only wiping out the Flash (program) memory and lock bits (see Lock bits below).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;WDTON&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Watchdog timer always on. If programmed the watchdog timer will be running at all times. If not programmed, the watchdog timer will be disabled on startup and can be enabled, configured, and disabled by the firmware at runtime.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;SPIEN&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Serial programming and data downloading enable. When programmed, the SPI interface for programming the ATtiny is enabled. Luckily this is the default state. Unprogramming the bit disables SPI, making it impossible to flash new code into the ATtiny.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;DWEN&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Debug wire enable. If programmed, it enables the DebugWIRE interface for debugging, if you&#39;re lucky enough to have access to the hardware and software needed to do so.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;RSTDISBL&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;External reset disable. If programmed, the external reset functionality is disabled, making the reset pin available for other uses. On an eight-pin microcontroller that normally has five pins available for GPIO, getting an extra one can be very tempting. Before tossing the reset functionality out the window, please note that it&#39;s used during SPI programming, which means new code can&#39;t be flashed into the ATtiny once this bit is programmed. The only way to make it receptive to new code again is using a high voltage programmer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Extended fuse byte&lt;/h3&gt;
&lt;p&gt;In addition to the low and the high fuse bytes there&#39;s the &lt;em&gt;extended&lt;/em&gt; fuse byte, which only defines a single parameter:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Bit(s)&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Name&lt;/th&gt;
&lt;th style=&quot;text-align:left&quot;&gt;Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:left&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;SELFPRGEN&lt;/td&gt;
&lt;td style=&quot;text-align:left&quot;&gt;Self-programming enable. When programmed, the flash (program) memory will be writable by the firmware in the ATtiny during operation, offering endless opportunities to shoot yourself in the foot.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Lock bits&lt;/h3&gt;
&lt;p&gt;The lock bits aren&#39;t strictly fuses, but share some similarities. They are used to enable security features in the ATtiny85. There are two lock bits that can represent one of three lock bit protection modes together:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;No lock functionality enabled&lt;/li&gt;
&lt;li&gt;Disable writing to the program memory and EEPROM in both serial and high-voltage programming modes, and prevent the fuses from being modified.&lt;/li&gt;
&lt;li&gt;Like mode 2, but also prevents reading the program memory and EEPROM in both serial and high-voltage programming modes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since the lock bits can only be reset using the chip erase command, which also erases the program memory and (depending on the setting of EESAVE, see above) the EEPROM, this effectively protects the code and data in the ATtiny85 from ending up in the wrong hands even if they have access to the chip.&lt;/p&gt;
&lt;h2&gt;Writing fuses&lt;/h2&gt;
&lt;p&gt;The fuses related to clock speed (CKSEL and CKDIV8) can be &lt;a href=&quot;https://www.martingunnarsson.com/posts/setting-the-attiny85-clock-speed.md&quot;&gt;set using the Arduino IDE&lt;/a&gt;. Doing so utilizes the &lt;a href=&quot;https://github.com/avrdudes/avrdude&quot;&gt;avrdude&lt;/a&gt; command line tool under the hood, and to set the other fuses you have to use this tool manually.&lt;/p&gt;
&lt;p&gt;To calculate the values for the different fuse arguments to avrdude you can use a &lt;a href=&quot;http://eleccelerator.com/fusecalc/fusecalc.php?chip=attiny85&quot;&gt;fuse calculator&lt;/a&gt;. They don&#39;t just calculate the byte values for you, but also present them as full arguments ready to be passed to avrdude. Here&#39;s an example of one such set of arguments, which represents the default ATtiny85 configuration:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; lfuse:w:0x62:m &lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; hfuse:w:0xDF:m &lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; efuse:w:0xFF:m &lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; lock:w:0xFF:m&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-U&lt;/code&gt; command, which is the memory operation specification, is included once for each fuse byte (low, high and extended) and once for the lock byte. The other parts of the argument consists of &lt;code&gt;w&lt;/code&gt; for write, the value to be written, and &lt;code&gt;m&lt;/code&gt; for immediate mode, which means the value to be written is specified in the argument itself.&lt;/p&gt;
&lt;p&gt;The only additional arguments needed are the ones specifying programmer and chip settings. A nice little shortcut is to peek at the exact avrdude command issued by the Arduino IDE. That way you know you&#39;re getting programmer parameters and a chip configuration that works. To do this, go into Preferences and enable verbose output for the compile command, then &lt;a href=&quot;https://www.martingunnarsson.com/posts/setting-the-attiny85-clock-speed.md&quot;&gt;set the clock speed&lt;/a&gt; and look for the avrdude command in the rather lengthy output. In my case this looked like this:&lt;/p&gt;
&lt;pre class=&quot;language-plain&quot;&gt;&lt;code class=&quot;language-plain&quot;&gt;&quot;/Users/martin/Library/Arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/bin/avrdude&quot; &quot;-C/Users/martin/Library/Arduino15/packages/arduino/tools/avrdude/6.3.0-arduino17/etc/avrdude.conf&quot; -v -v -v -v -pattiny85 -cusbtiny 
CMD: &#92;[50 08 00 00] &#92;[00 50 08 ff]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now combine the relevant  parts from the two sources into a full command that does what we want:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[...]/avrdude&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;-C[...]/avrdude.conf&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-pattiny85&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-cusbtiny&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; lfuse:w:0x62:m &lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; hfuse:w:0xDF:m &lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; efuse:w:0xFF:m &lt;span class=&quot;token parameter variable&quot;&gt;-U&lt;/span&gt; lock:w:0xFF:m&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it, and hopefully you&#39;re good to go.&lt;/p&gt;
&lt;h2&gt;Reading fuses&lt;/h2&gt;
&lt;p&gt;If you just want to check the configuration of an ATtiny you can remove all the write instructions from the command above and run something like this:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;/avrdude -C&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;/avrdude.conf&quot; &lt;span class=&quot;token parameter variable&quot;&gt;-pattiny85&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-cusbtiny&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which will spit out a nice little overview:&lt;/p&gt;
&lt;pre class=&quot;language-plain&quot;&gt;&lt;code class=&quot;language-plain&quot;&gt;avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e930b (probably t85)

avrdude: safemode: Fuses OK (E:FF, H:DF, L:62)

avrdude done.  Thank you.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here you see both the device signature as well as the values for the low, high and extended fuse bytes.&lt;/p&gt;
&lt;h2&gt;Getting out of trouble&lt;/h2&gt;
&lt;p&gt;As mentioned repeatedly above, some fuse settings puts the ATtiny85 in a state where it can&#39;t be re-programmed and/or reached again without a high-voltage programmer. This is sort of a back door built into the ATtiny, whereby applying 12V to the reset pin with precise timing and then sending serial commands through a few other pins chips can be brought back from the dead. I&#39;ll write more about it in the future, but for now I&#39;ll refer to &lt;a href=&quot;https://ericdraken.com/hvsp/&quot;&gt;this excellent blog post&lt;/a&gt; by Eric Draken.&lt;/p&gt;
&lt;p&gt;Happy tinkering!&lt;/p&gt;
</description>
      <pubDate>Mon, 20 Nov 2023 18:19:56 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/attiny85-fuses-explained/</guid>
    </item>
    <item>
      <title>Setting the ATtiny85 Clock Speed</title>
      <link>https://www.martingunnarsson.com/posts/setting-the-attiny85-clock-speed/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The clock speed of the ATtiny85 can be set using one of several internal clock sources, or an external one (like a crystal oscillator) for full flexibility.&lt;/p&gt;
&lt;p&gt;The default setting utilizes the internal 8 MHz oscillator combined with a clock divider of 8, resulting in a 1 MHz clock speed. This clock divider can be disabled to run at the native 8 MHz. Additionally, there&#39;s another clock source called the Phase-Locked Loop (PLL), which doubles the internal frequency to achieve a 16 MHz clock speed.&lt;/p&gt;
&lt;p&gt;When getting started with the ATtiny85 the most common choices are running the internal clock source at either 1 or 8 MHz. Selecting the appropriate clock speed is very important, especially for time-sensitive code, and some libraries require a specific speed to function correctly.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve moved into more complex projects, there are also ways of adjusting the clock speed at run-time using code, but that&#39;s a story for another day.&lt;/p&gt;
&lt;h2&gt;Setting the speed&lt;/h2&gt;
&lt;p&gt;To set the clock speed of the ATtiny in the Arduino IDE, first ensure you have installed a suitable board definition for ATtiny support. You also need access to an Arduino or a dedicated programmer for flashing it.&lt;/p&gt;
&lt;p&gt;Begin by selecting the correct board in the Tools &amp;gt; Board menu. Then look in the Tools &amp;gt; Clock menu and select Internal 1, 8, or 16 MHz. Do &lt;em&gt;not&lt;/em&gt; select an external clock source unless you have one properly set up, as it could make flashing the chip again impossible without the use of a high voltage programmer.&lt;/p&gt;
&lt;p&gt;After selecting the correct board and clock speed, ensure the appropriate programmer is chosen in the Tools &amp;gt; Programmer menu. Connect the ATtiny to the programmer and select Burn Bootloader from the Tools menu. The operation only takes a second, then you should be good to go.&lt;/p&gt;
&lt;h2&gt;Under the hood&lt;/h2&gt;
&lt;p&gt;Despite its name, the Burn Bootloader command actually doesn&#39;t burn a bootloader into the ATtiny; it just changes the value of certain &lt;em&gt;fuses&lt;/em&gt; to match the chosen clock configuration. Fuses are a common way of storing essential configuration parameters in microcontrollers. They are typically stored in non-volitile memory and remains set even if new content is flashed into the program memory. I&#39;ve written an &lt;a href=&quot;https://www.martingunnarsson.com/posts/attiny85-fuses-explained.md&quot;&gt;introduction to ATtiny85 fuses&lt;/a&gt; that explains this in more detail, as well as listing all the different configuration bits that can be set.&lt;/p&gt;
&lt;p&gt;Happy tinkering!&lt;/p&gt;
</description>
      <pubDate>Sat, 11 Nov 2023 17:26:15 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/setting-the-attiny85-clock-speed/</guid>
    </item>
    <item>
      <title>Serial debugging on the ATtiny85</title>
      <link>https://www.martingunnarsson.com/posts/serial-debugging-on-attiny85/</link>
      <description>&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.microchip.com/en-us/product/ATtiny85&quot;&gt;ATtiny85&lt;/a&gt; is part of the ATtiny family of microcontrollers, originally from Atmel, now Microchip Technology. It is very small, with only eight pins, six of which are available for I/O. Despite its limitations, it&#39;s surprisingly accessible to beginners (myself included) thanks to its compatibility with the &lt;a href=&quot;https://www.arduino.cc/&quot;&gt;Arduino IDE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The ATtiny85 lacks many features that make &amp;quot;real&amp;quot; Arduinos so easy to work with for hobbyists, such as a proper serial port that makes it easy to send debug messages to a computer. Since most people don&#39;t have access to specialized debugging hardware and software, many tinkerers resort to turning LEDs on and off in order to indicate various states of a program. I usually develop the code on an Arduino, where debugging is slightly more straightforward, until I&#39;m happy with it and then transfer it to the ATtiny85. This approach works quite well up to a certain point, but sometimes you simply need to understand what&#39;s going on inside the ATtiny85 itself.&lt;/p&gt;
&lt;h2&gt;Serial communication&lt;/h2&gt;
&lt;p&gt;As mentioned, the ATtiny85 doesn&#39;t have a hardware serial port, and even if it did those pins would probably get used for other things most of the time. Luckily there are other options. For one of my hobby projects I really wanted to know what value I was receiving from an external chip, and decided to give &lt;a href=&quot;https://github.com/ArminJo/ATtinySerialOut/tree/master&quot;&gt;ATtinySerialOut&lt;/a&gt; a go. It&#39;s a super compact &amp;quot;software serial&amp;quot; library written by Armin Joachimsmeyer specifically for ATtiny microcontrollers.&lt;/p&gt;
&lt;p&gt;The library can be installed through the Library manager within the Arduino IDE by simply searching for its name and clicking Install.&lt;/p&gt;
&lt;h3&gt;A very basic example&lt;/h3&gt;
&lt;p&gt;Here&#39;s a short snippet to get started with ATtinySerialOut. The default output pin is PB2 on ATtiny85.&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&amp;lt;ATtinySerialOut.hpp&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;initTXPin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Actually, the above code didn&#39;t work for me, since the ATtiny core I&#39;m using in the Arduino IDE doesn&#39;t define pin constants like PB2. If you run into the same problem, or want to specify a different pin, you can define the &lt;code&gt;TX_PIN&lt;/code&gt; macro before including the library, like this:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// The pin must be defined before the library is included&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;token macro-name&quot;&gt;TX_PIN&lt;/span&gt; &lt;span class=&quot;token expression&quot;&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&amp;lt;ATtinySerialOut.hpp&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;token function&quot;&gt;initTXPin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The library implements a whole bunch of nice methods for sending various kinds of data, very convenient.&lt;/p&gt;
&lt;h3&gt;Speed&lt;/h3&gt;
&lt;p&gt;The default baud rate of ATtinySerialOut is 115200. In order to change that, a special macro can be defined:&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// The macros must be defined before the library is included&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;token macro-name&quot;&gt;TINY_SERIAL_DO_NOT_USE_115200BAUD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;token macro-name&quot;&gt;TX_PIN&lt;/span&gt; &lt;span class=&quot;token expression&quot;&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&amp;lt;ATtinySerialOut.hpp&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;initTXPin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the above macro, the baud rate now depends on the clock frequency of the ATtiny. For 1 MHz it&#39;s 38400 and for 8/16 MHz it&#39;s 230400. You can change the clock frequency in the Tools menu of the Arduino IDE and write it to the ATtiny using the &lt;em&gt;Burn bootloader&lt;/em&gt; option in the same menu. There are some caveats to this, for more details please see my post &lt;a href=&quot;https://www.martingunnarsson.com/posts/setting-the-attiny85-clock-speed.md&quot;&gt;Setting the ATtiny85 clock speed&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Proxying&lt;/h2&gt;
&lt;p&gt;Great, we can now transmit data using just a single pin, but where to receive it? In my case, I just wanted to read the value in plain text, so I decided to receive it on a regular Arduino and re-transmit it to my laptop over USB, so that the Arduino acts like a small proxy between the ATtiny and the laptop. Using a serial to USB-adapter I guess it would be possible to receive the data right into the computer, but in my case I just wanted to get on with trying to solve the real problem I was stuck on.&lt;/p&gt;
&lt;p&gt;Here&#39;s the code for the Arduino serial proxy using the built in SoftwareSerial library.&lt;/p&gt;
&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&amp;lt;SoftwareSerial.h&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; rxPin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; txPin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// This can be any other free pin if you don&#39;t need to send data back to ATtiny&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Set up a new SoftwareSerial port&lt;/span&gt;
SoftwareSerial &lt;span class=&quot;token function&quot;&gt;softSerial&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rxPin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; txPin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// This is the serial interface between the Arduino and the laptop&lt;/span&gt;
    Serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;57600&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    Serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Ready&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// This is the serial interface between the Arduino and the ATtiny&lt;/span&gt;
    softSerial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;38400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;softSerial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;available&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;char&lt;/span&gt; received &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; softSerial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    Serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;received&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see I&#39;ve set the SoftwareSerial port to 38000 bps, which was needed in order to achieve reliable data transfer. Remember that whatever is receivng the transmission must share ground with the transmitting ATtiny&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;If you just want to get this all working, here&#39;s what you have to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the ATtinySerialOut library from within the Arduino IDE.&lt;/li&gt;
&lt;li&gt;Set the clock frequency of your ATtiny to 1 MHz.&lt;/li&gt;
&lt;li&gt;Flash the following code snippet to your ATtiny:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-cpp&quot;&gt;&lt;code class=&quot;language-cpp&quot;&gt;&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;token macro-name&quot;&gt;TINY_SERIAL_DO_NOT_USE_115200BAUD&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;token macro-name&quot;&gt;TX_PIN&lt;/span&gt; &lt;span class=&quot;token expression&quot;&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token macro property&quot;&gt;&lt;span class=&quot;token directive-hash&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;token directive keyword&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&amp;lt;ATtinySerialOut.hpp&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;initTXPin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Serial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Flash the simple proxy code above to your Arduino.&lt;/li&gt;
&lt;li&gt;Connect your Arduino to your laptop and power up your ATtiny.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Once they&#39;re both running&lt;/strong&gt;, connect pin 2 from the ATtiny to pin 10 on the Arduino, and make sure the two circuits share a common ground.&lt;/li&gt;
&lt;li&gt;Open the Serial monitor in the Arduino IDE and watch the messages come rolling in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://www.martingunnarsson.com/posts/serial-debugging-on-attiny85/attiny85-arduino.jpeg&quot; alt=&quot;An Arduino connected to an ATtiny85 using a single wire&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.martingunnarsson.com/posts/serial-debugging-on-attiny85/hello-worlds.png&quot; alt=&quot;The Serial monitor in the Arduino IDE displaying a long list of &amp;quot;Hello world!&amp;quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Happy tinkering!&lt;/p&gt;
</description>
      <pubDate>Fri, 03 Nov 2023 08:46:12 +0000</pubDate>
      <dc:creator>Martin Gunnarsson</dc:creator>
      <guid>https://www.martingunnarsson.com/posts/serial-debugging-on-attiny85/</guid>
    </item>
  </channel>
</rss>