Zach Leatherman A web development blog written by @zachleat. 2025-08-19T05:00:00Z https://www.zachleat.com/ Zach Leatherman A note from my late Grandmother about Eleventy 2025-08-19T05:00:00Z https://www.zachleat.com/web/history-of-eleventy-name/ <p>Our basement flooded this week, so I’ve been cleaning up the mess. In that process, I found a 22-year-old letter from my grandmother (who passed away Christmas 2023) to my mom that had a little Eleventy naming history that some folks might enjoy.</p> <p>I first talked about the significance and history of the Eleventy name back in a <a href="https://www.zachleat.com/web/eleventy-birthday/#project-naming">blog post from 2018 celebrating the project’s first birthday</a>:</p> <blockquote> <p>I chose it because of a story my grandma Nonnie loved to tell about how I learned to count. Rather than move from ten to eleven like a normal child, I felt it appropriate to use the teen suffix for the numbers eleven and twelve, counting “ten, eleventy-teen, twelvety-teen, thirteen, …”</p> </blockquote> <p>The discovery of this letter was very surprising to me as I hadn’t reviewed the contents of this box in almost 20 years (long before Eleventy). I’ll include a photo of the letter and of the newspaper clipping of the Dennis the Menace comic she had included below (with some redacted unrelated personal details for brevity).</p> <p><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/3Xe8HDSwbE-1016.avif 1016w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/3Xe8HDSwbE-1016.jpeg" alt="Dennis the Menace really hit a soft spot with me this week, so I am sending it to share with you. When Zach was about three or four, he was counting for me and after ten he said “leventy teen.” When I said, “But Zach, we don't say ‘leventy teen,” I got this reply. “But I do. I do say ‘leventy teen.” That has always been one of my favorite memories. So now we know there really is a “leventy teen.” He was right all the time. No more news, I must get back to work, I think the floor is dry. So there is no excuse for not getting the clothes folded. Thanks again for everything. Love, Mom" width="1016" height="1237"></picture></p> <blockquote> <p>Dennis the Menace really hit a soft spot with me this week, so I am sending it to share with you. When Zach was about three or four, he was counting for me and after ten he said “leventy teen.” When I said, “But Zach, we don't say 'leventy teen,” I got this reply. “But I do. I do say 'leventy teen.” That has always been one of my favorite memories. So now we know there really is a “leventy teen.” He was right all the time.</p> </blockquote> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/pVizryAM4e-580.avif 580w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/pVizryAM4e-580.jpeg" alt="DENNIS the MENACE, Numbers Racket: Doesn’t this game have any rules? Sure it has plenty!" width="580" height="184"></picture> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/WzaP-8PgUR-608.avif 608w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/WzaP-8PgUR-608.jpeg" alt="Too bad we can’t read yet. That’s fourelve for me and fiftyteen for you! Fiftyteen? And another Ninety Twelve for me! That makes Eleventy Teen!" width="608" height="183"></picture> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/9v4IJYvp7Q-594.avif 594w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/9v4IJYvp7Q-594.jpeg" alt="There’s no such number as Eleventy Teen!!! Sure there is! It’s halfway between twoteen an’ leventy seventy! And how much is leventy seventy? It’s a hunnerd times twixty!" width="594" height="190"></picture> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Dd0DcKkXs9-602.avif 602w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Dd0DcKkXs9-602.jpeg" alt="You’re absolutely hopeless!! What’s her problem? She knows when she’s outnumbered." width="602" height="191"></picture> <p><em>Miss you, Nonnie <svg class="z-icon"><use href="#fas-fa-heart" xlink:href="#fas-fa-heart"></use></svg></em></p> One weird trick to reduce Eleventy Image Build Times by 60% 2025-08-01T05:00:00Z https://www.zachleat.com/web/faster-builds-with-eleventy-img/ <p>This web site does a fair bit of build-time Image Optimization using the <a href="https://www.11ty.dev/docs/plugins/image/#html-transform">HTML Transform method provided by the Eleventy Image plugin</a>. I took a bit of a set-it-and-forget-it approach with the plugin on this site, mostly for my own convenience. My usage looked something like this:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> eleventyImageTransformPlugin <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/eleventy-img"</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addPlugin</span><span class="token punctuation">(</span>eleventyImageTransformPlugin<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">failOnError</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token literal-property property">formats</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"svg"</span><span class="token punctuation">,</span> <span class="token string">"avif"</span><span class="token punctuation">,</span> <span class="token string">"jpeg"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token literal-property property">svgShortCircuit</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>Eleventy Image includes a <a href="https://www.11ty.dev/docs/plugins/image/#build-performance">fair number of caches and performance optimizations</a> to make this default behavior pretty fast.</p> <ul> <li>It uses a memory cache to de-duplicate multiple requests to optimize the same input image.</li> <li>It uses an on-request image processing engine to avoid processing images during local development.</li> <li>It uses a hash for output file name and checks the target file location to avoid reprocessing images.</li> </ul> <p>The biggest drawback with the third method is that when you’re building on a deployment server, these environments start with an empty output folder (and thus, an empty disk cache). What can we do to re-use the disk cache and avoid reprocessing unchanged images on each new deploy?</p> <p>With just a <a href="https://github.com/zachleat/zachleat.com/commit/eead25b5b38431d3b61bfb318a186d3214c45260">few lines of configuration code</a> to create an intermediate output folder in a location that is persisted between builds (<code>.cache</code>), I was able to drop my site’s build time from an embarrasing <code>9:40</code> down to a respectable <code>3:56</code> <svg class="z-icon" aria-labelledby="fa11-text-W7_B7iBsOlxj5iZU6oQwm" role="img"><use href="#fas-fa-trophy" xlink:href="#fas-fa-trophy"></use><title id="fa11-text-W7_B7iBsOlxj5iZU6oQwm">(trophy icon)</title></svg> (for throughput info, the output folder has 7,779 files weighing 492.7 MB).</p> <p>I think this approach is <strong>very reusable</strong> and we’ll likely bundle it into a future version of Eleventy Image. Until then, you can use it yourself by adding the following lines of configuration:</p> <pre class="language-diff-js"><code class="language-diff-js"><span class="token inserted-sign inserted language-js"><span class="token prefix inserted">+</span><span class="token keyword">import</span> path <span class="token keyword">from</span> <span class="token string">"node:path"</span><span class="token punctuation">;</span> <span class="token prefix inserted">+</span><span class="token keyword">import</span> fs <span class="token keyword">from</span> <span class="token string">"node:fs"</span><span class="token punctuation">;</span> </span><span class="token unchanged language-js"><span class="token prefix unchanged"> </span><span class="token keyword">import</span> <span class="token punctuation">{</span> eleventyImageTransformPlugin <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/eleventy-img"</span><span class="token punctuation">;</span> </span> <span class="token unchanged language-js"><span class="token prefix unchanged"> </span><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token prefix unchanged"> </span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addPlugin</span><span class="token punctuation">(</span>eleventyImageTransformPlugin<span class="token punctuation">,</span> <span class="token punctuation">{</span> </span><span class="token inserted-sign inserted language-js"><span class="token prefix inserted">+</span> <span class="token literal-property property">urlPath</span><span class="token operator">:</span> <span class="token string">"/img/built/"</span><span class="token punctuation">,</span> <span class="token prefix inserted">+</span> <span class="token literal-property property">outputDir</span><span class="token operator">:</span> <span class="token string">".cache/@11ty/img/"</span><span class="token punctuation">,</span> </span><span class="token unchanged language-js"><span class="token prefix unchanged"> </span> <span class="token literal-property property">failOnError</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token prefix unchanged"> </span> <span class="token literal-property property">formats</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"svg"</span><span class="token punctuation">,</span> <span class="token string">"avif"</span><span class="token punctuation">,</span> <span class="token string">"jpeg"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token prefix unchanged"> </span> <span class="token literal-property property">svgShortCircuit</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token prefix unchanged"> </span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span> <span class="token inserted-sign inserted language-js"><span class="token prefix inserted">+</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">"eleventy.after"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token prefix inserted">+</span> fs<span class="token punctuation">.</span><span class="token function">cpSync</span><span class="token punctuation">(</span><span class="token string">".cache/@11ty/img/"</span><span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>eleventyConfig<span class="token punctuation">.</span>directories<span class="token punctuation">.</span>output<span class="token punctuation">,</span> <span class="token string">"/img/built/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token prefix inserted">+</span> <span class="token literal-property property">recursive</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token prefix inserted">+</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token prefix inserted">+</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span><span class="token unchanged language-js"><span class="token prefix unchanged"> </span><span class="token punctuation">}</span><span class="token punctuation">;</span> </span></code></pre> <p>You could improve the above example by restricting the copy step using the <a href="https://www.11ty.dev/docs/environment-vars/#eleventy-supplied"><code>process.env.ELEVENTY_RUN_MODE</code> environment variable</a>.</p> <p>You may also need to <a href="https://www.11ty.dev/docs/deployment/#persisting-cache">persist <code>.cache</code> on your web host</a>. This behavior is provided for-free if you’re using Vercel or Cloudflare Pages.</p> <p><em>Subscribe to <a href="https://github.com/11ty/eleventy-img/issues/285"><code>github.com/11ty/eleventy-img/issues/285</code></a> for future updates.</em></p> Never write your own Date Parsing Library 2025-07-23T05:00:00Z https://www.zachleat.com/web/adventures-in-date-parsing/ <p><em><strong>Never</strong> write your own date parsing library.</em></p> <p><em><strong>Never</strong>.</em> No exceptions.</p> <p><s>Never have I ever…</s></p> <p>So… I’ve written my own date parsing library.</p> <p><em>Why?</em> Our story begins seven years ago in the year 2018. I made the very sensible choice to adopt <code>luxon</code> as the Date Parsing library for Eleventy. This parsing behavior is used when Eleventy finds a String for the <a href="https://www.11ty.dev/docs/dates/"><code>date</code> value in the Data Cascade</a> (though YAML front matter will bypass this behavior when encountering a YAML-compatible date).</p> <p>This choice was good for Eleventy’s Node.js-only requirements at the time: accurate and not <em>too</em> big (relatively speaking). Eleventy has <a href="https://github.com/11ty/eleventy/commit/4272311dab203d2b217ebd4f6b597eb0e816006b">used <code>luxon</code> since <code>@0.2.12</code></a> and has grown with the dependency all the way through <code>@3.7.1</code>. Now that’s what I call a high quality dependency!</p> <p>As we move Eleventy to run in more JavaScript environments and runtimes (including on the client) we’ve had to take a hard look at our use of Luxon, currently <a href="https://github.com/11ty/eleventy/issues/3587">our <em>largest</em> dependency</a>:</p> <ul> <li>4.7 MB of 21.3 MB (22%) of <code>@11ty/eleventy</code> node_modules</li> <li>229 kB of 806 kB (28%) of <code>@11ty/client</code> (<em>not yet released!</em>) bundle size (unminified)</li> </ul> <p>Given that our use of Luxon is strictly limited to the <a href="https://moment.github.io/luxon/#/parsing?id=iso-8601"><code>DateTime.fromISO</code> function for ISO 8601 date parsing</a> (not formatting or display), it would have been nice to enable tree-shaking on the Luxon library to reduce its size in the bundle (though that wouldn’t have helped the <code>node_modules</code> size, I might have settled for that trade-off). Unfortunately, <a href="https://github.com/moment/luxon/issues/854">Luxon does not yet support tree-shaking</a> so it’s an all or nothing for the bundle.</p> <h2 id="the-search-begins">The Search Begins</h2> <p>I did the next sensible thing and looked at a few alternatives:</p> <table> <thead> <tr> <th>Package</th> <th>Type</th> <th>Disk Size</th> <th><a href="https://bundlephobia.com/">Bundle Size</a></th> </tr> </thead> <tbody> <tr> <td><code><a href="https://www.npmjs.com/package/luxon">luxon</a>@3.7.1</code></td> <td>Dual</td> <td><code>4.59 MB</code> <svg class="z-icon"><use href="#fas-fa-triangle-exclamation" xlink:href="#fas-fa-triangle-exclamation"></use></svg></td> <td><code>81.6 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/moment">moment</a>@2.30.1</code></td> <td>CJS</td> <td><code>4.35 MB</code> <svg class="z-icon"><use href="#fas-fa-triangle-exclamation" xlink:href="#fas-fa-triangle-exclamation"></use></svg></td> <td><code>294.9 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/dayjs">dayjs</a>@1.11.13</code></td> <td>CJS</td> <td><code>670 kB</code></td> <td><code>6.9 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/date-fns">date-fns</a>@4.1.0</code></td> <td>Dual</td> <td><code>22.6 MB</code> <svg class="z-icon"><use href="#fas-fa-triangle-exclamation" xlink:href="#fas-fa-triangle-exclamation"></use></svg></td> <td><code>77.2 kB</code> (min)</td> </tr> </tbody> </table> <p>The next in line to the throne was clearly <code>dayjs</code>, which is small on disk and in bundle size. Unfortunately I found it to be inaccurate: <code>dayjs</code> fails about 80 of the 228 tests in the test suite I’m using moving forward.</p> <div class="callout"><p>As an aside, this search has made me tempted to ask: do we need to keep Dual publishing packages? I prefer ESM over CJS but maybe just pick one?</p></div> <h2 id="breaking-changes">Breaking Changes</h2> <p>Most date parsing woes (in my opinion) come from ambiguity: from supporting <em>too many</em> formats or attempting maximum flexibility in parsing. And guess what: ISO 8601 is a big ’ol standard with a lot of subformats. There is a maintenance freedom and simplicity in strict parsing requirements (don’t let XHTML hear me say that).</p> <p>Consider <code>&quot;200&quot;</code>. Is this the year 200? Is this the 200th day of the current year? Surprise, in ISO 8601 it’s neither — it’s <a href="https://iso8601aas.ijmacd.com/?input=200">a decade, spanning from the year 2000 to the year 2010</a>. And <a href="https://iso8601aas.ijmacd.com/?input=20"><code>&quot;20&quot;</code></a> is the century from the year 2000 to the year 2100.</p> <p>Moving forward, we’re tightening up the default date parsing in Eleventy (this is <a href="https://www.11ty.dev/docs/dates/#custom-date-parsing-example">configurable</a> — keep using Luxon if you want!).</p> <p>Luckily we have a north star date format: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant#rfc_9557_format">RFC 9557</a>, billed as <em>“an extension to the ISO 8601 / RFC 3339”</em> formats and already in use by the upcoming Temporal web standard APIs for date and time parsing coming to a JavaScript runtime near you.</p> <p>There are a few notable differences:</p> <table> <thead> <tr class="nowrap"> <th>Format</th> <th>ISO 8601</th> <th><code>Date.parse</code>*</th> <th><code>luxon</code></th> <th>RFC 9557</th> </tr> </thead> <tbody> <tr> <td class="no"><code class="nowrap">YYYY</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-N_4ow_FB_oSkSGqXgc4V4" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-N_4ow_FB_oSkSGqXgc4V4">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-bTF2oYlH-wB57RAMqZnOB" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-bTF2oYlH-wB57RAMqZnOB">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-tlC3Ni7UVJPgw6xv8a6j7" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-tlC3Ni7UVJPgw6xv8a6j7">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-TVYbcY7WYcmH8SM52OAtf" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-TVYbcY7WYcmH8SM52OAtf">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">YYYY-MM</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Rfgi57PROPg0PCt20lfod" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Rfgi57PROPg0PCt20lfod">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-BUp9sJHjocG3s7IhyvPB6" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-BUp9sJHjocG3s7IhyvPB6">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-SeEYni3LhhEJ26jZ6tl99" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-SeEYni3LhhEJ26jZ6tl99">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-9gs0YvLzBWwM97r8Tsemz" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-9gs0YvLzBWwM97r8Tsemz">Unsupported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DD</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-7aK0t8NeqqkK3ExgzEVnb" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-7aK0t8NeqqkK3ExgzEVnb">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-jZlHzh9V0wJs-VuU_2iej" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-jZlHzh9V0wJs-VuU_2iej">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-iYpvMhf5VkRVuIFdwDppm" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-iYpvMhf5VkRVuIFdwDppm">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-vHV1ygCmzpnmHPs8hOo9b" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-vHV1ygCmzpnmHPs8hOo9b">Supported</title></svg></td> </tr> <tr> <td class="yes"><code class="nowrap">±YYYYYY-MM-DD</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-soMWk_bCm_q9oawPRflcT" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-soMWk_bCm_q9oawPRflcT">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-3c2H3PSvZFyDQQWtZXyfS" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-3c2H3PSvZFyDQQWtZXyfS">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-8Ch6tkIWNHwDT916wlI3t" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-8Ch6tkIWNHwDT916wlI3t">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Wbm8eBkeuZnPsNvLatuOm" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Wbm8eBkeuZnPsNvLatuOm">Supported</title></svg></td> </tr> <tr> <td>Optional <code>-</code> delimiters in dates</td> <td><svg class="z-icon" aria-labelledby="fa11-text-crx7jIqxZXUBfOaGaXaT9" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-crx7jIqxZXUBfOaGaXaT9">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text--3BcEI71tndwNSXuhIJN8" role="img"><use href="#fas-fa-square-xmark" xlink:href="#fas-fa-square-xmark"></use><title id="fa11-text--3BcEI71tndwNSXuhIJN8">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-pMh4Ku4OvIsMU1_1L4yz7" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-pMh4Ku4OvIsMU1_1L4yz7">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-QrwEFDMXdsz9bniLjf-0J" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-QrwEFDMXdsz9bniLjf-0J">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-F9CASz0qBev0AzxSVkaDO" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-F9CASz0qBev0AzxSVkaDO">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-kCw9oiSyiMk0t9nxvLy3q" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-kCw9oiSyiMk0t9nxvLy3q">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-EzxEJjTt2GzKlNJyojjpV" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-EzxEJjTt2GzKlNJyojjpV">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-T5Oelzz1D8p77aA_4pv8Q" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-T5Oelzz1D8p77aA_4pv8Q">Supported</title></svg></td> </tr> <tr> <td class="yes"><code class="nowrap">YYYY-MM-DD HH</code> (space delimiter)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-cZYqdBMySzAHchTnpekNu" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-cZYqdBMySzAHchTnpekNu">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-BSI5JygJXm4wdccmg0MqE" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-BSI5JygJXm4wdccmg0MqE">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-DcFnbUTZEdTaktUBFTL4Q" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-DcFnbUTZEdTaktUBFTL4Q">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-4W3xvA58nSQC-fVpwYscg" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-4W3xvA58nSQC-fVpwYscg">Supported</title></svg></td> </tr> <tr> <td class="yes"><code class="nowrap">YYYY-MM-DDtHH</code> (lowercase delimiter)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-EVPQ3ycH3JTAwm9c8hFmN" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-EVPQ3ycH3JTAwm9c8hFmN">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-M1YX1atDPInonrOgAIJmY" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-M1YX1atDPInonrOgAIJmY">Supported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-pqIyHuuLovnv35LzAb-Rq" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-pqIyHuuLovnv35LzAb-Rq">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-izkQucPkMvTcHAYzsTpQi" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-izkQucPkMvTcHAYzsTpQi">Supported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-hb00TlPjqzp6kZJElPRQg" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-hb00TlPjqzp6kZJElPRQg">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-xUfcK9_Aie4wprYpT-Sc9" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-xUfcK9_Aie4wprYpT-Sc9">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-j3f29FVTW5atD-fEhdxNY" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-j3f29FVTW5atD-fEhdxNY">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-XZMf2v5mD6PYO5EXgJIFa" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-XZMf2v5mD6PYO5EXgJIFa">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-3hZnjB_VgFVtC1hjxYZUC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-3hZnjB_VgFVtC1hjxYZUC">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-EmFN6JPbBQ2FWyIHK6dzL" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-EmFN6JPbBQ2FWyIHK6dzL">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-TlNTLRqPalu75pITn3rt2" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-TlNTLRqPalu75pITn3rt2">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-qKrX57N5KwLKPgcpKdOQF" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-qKrX57N5KwLKPgcpKdOQF">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-mrKOT_6now_ETMSAZUwQ5" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-mrKOT_6now_ETMSAZUwQ5">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-k2Ext8WANWf7Fmk6H6KFC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-k2Ext8WANWf7Fmk6H6KFC">Supported</title></svg></td> </tr> <tr> <td>Optional <code>:</code> delimiters in time</td> <td><svg class="z-icon" aria-labelledby="fa11-text-iZN0vcscF0jAHNef2cSw-" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-iZN0vcscF0jAHNef2cSw-">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-pPzGkr12c4zA6s8huGNWn" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-pPzGkr12c4zA6s8huGNWn">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-g8al7YcEJlpLHKZR6j9tr" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-g8al7YcEJlpLHKZR6j9tr">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-TsAr47NhyNxjGnLMfGdZT" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-TsAr47NhyNxjGnLMfGdZT">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS.SSS</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-_puZH1ZlDhX62qtmu5QYp" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-_puZH1ZlDhX62qtmu5QYp">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-yfZiupgqdFwkmM1fe7oXC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-yfZiupgqdFwkmM1fe7oXC">Supported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-FE5OwZzNl8KUt1xv8qMtr" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-FE5OwZzNl8KUt1xv8qMtr">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-PUram9Hxc1mWnTkA4UAwE" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-PUram9Hxc1mWnTkA4UAwE">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-qdKUgbU2OzUgb0uIw8lKY" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-qdKUgbU2OzUgb0uIw8lKY">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS,SSS</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-kjolRN97IfXAgP94fy5S0" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-kjolRN97IfXAgP94fy5S0">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-ug7BMp5XI7UM5acusS2YC" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-ug7BMp5XI7UM5acusS2YC">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-KB1bQ-Aa8qTq-FklBViYD" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-KB1bQ-Aa8qTq-FklBViYD">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-vtiUn0duyig2p2NBND90S" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-vtiUn0duyig2p2NBND90S">Supported</title></svg></td> </tr> <tr> <td>Microseconds (6 digit precision)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-jsVa3UY2NWSMelmTQeRNg" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-jsVa3UY2NWSMelmTQeRNg">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text--L75FOBzSrSIsDCmAYu61" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text--L75FOBzSrSIsDCmAYu61">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text--EX6UaXaW1v0ipAvCD1D6" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text--EX6UaXaW1v0ipAvCD1D6">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-HemdvwouA6COhaF1Kw1yQ" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-HemdvwouA6COhaF1Kw1yQ">Supported</title></svg></td> </tr> <tr> <td>Nanoseconds (9 digit precision)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-ulv9l90I6lFDR37hZFsGX" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-ulv9l90I6lFDR37hZFsGX">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-1zLLFwfA6HDDELH24ICgw" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-1zLLFwfA6HDDELH24ICgw">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Ppj8BcLKXmu4JHg_oHLDr" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Ppj8BcLKXmu4JHg_oHLDr">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-GRH-zLezWuEXppyh_nvqU" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-GRH-zLezWuEXppyh_nvqU">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH.H</code> Fractional hours</td> <td><svg class="z-icon" aria-labelledby="fa11-text-B40_fIwoIIZ7lDhXbHIHf" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-B40_fIwoIIZ7lDhXbHIHf">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-97B4TctXJJ0IkRKBseYk-" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-97B4TctXJJ0IkRKBseYk-">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-NbIXOBuTABreIj7dLu0FU" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-NbIXOBuTABreIj7dLu0FU">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-tIwL29x4CUM7NLshhpNi1" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-tIwL29x4CUM7NLshhpNi1">Unsupported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II.I</code> Fractional minutes</td> <td><svg class="z-icon" aria-labelledby="fa11-text-myB9ORATy6pVA_turVwBT" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-myB9ORATy6pVA_turVwBT">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-4Hv1OJ5Z5RET3brjCUVqU" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-4Hv1OJ5Z5RET3brjCUVqU">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-BnwvzlKTooFW27IUIQ9Js" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-BnwvzlKTooFW27IUIQ9Js">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-gQraZ_jhtrYc2bOr892Xc" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-gQraZ_jhtrYc2bOr892Xc">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">YYYY-W01</code> ISO Week Date</td> <td><svg class="z-icon" aria-labelledby="fa11-text-RCwdd7BWa6RX6zTzAHhnC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-RCwdd7BWa6RX6zTzAHhnC">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-lcWIPUmOAzqLutVABLhPw" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-lcWIPUmOAzqLutVABLhPw">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-dIGc_1vPL5puhY2GCmiRo" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-dIGc_1vPL5puhY2GCmiRo">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-7vhfq9aZsLXsm-qo1CVzj" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-7vhfq9aZsLXsm-qo1CVzj">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">YYYY-DDD</code> Year Day</td> <td><svg class="z-icon" aria-labelledby="fa11-text-MQ1ThqhOpK6XwFl0zgv-u" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-MQ1ThqhOpK6XwFl0zgv-u">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Oh-YORmzoeB3VybcyJ8aE" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-Oh-YORmzoeB3VybcyJ8aE">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Ap4L2Pp6NULx_bJ69Ef3P" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Ap4L2Pp6NULx_bJ69Ef3P">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-XuqEq2P5gIzsAWGVeBTUK" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-XuqEq2P5gIzsAWGVeBTUK">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">HH:II</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-o96W1HyOF5nqYSZ4ZhwcX" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-o96W1HyOF5nqYSZ4ZhwcX">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-zTx7UNNDwcKnqoZxmUJR8" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-zTx7UNNDwcKnqoZxmUJR8">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-s6evZzt9ZHSbg0bk-9xRa" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-s6evZzt9ZHSbg0bk-9xRa">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-fG1UcqYDZCYROIGd3iVZy" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-fG1UcqYDZCYROIGd3iVZy">Unsupported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SSZ</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-4oM4oqtowusYsV0QIkW9k" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-4oM4oqtowusYsV0QIkW9k">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-EquzECqMTzP2QEmLumHRz" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-EquzECqMTzP2QEmLumHRz">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-_sfnvS97y3o5pqCTA4wQH" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-_sfnvS97y3o5pqCTA4wQH">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-cGiMoPyYcfLHUABLBpspG" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-cGiMoPyYcfLHUABLBpspG">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS±00</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-m3N96kYOFBs50jRna6ByT" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-m3N96kYOFBs50jRna6ByT">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-hBeIC_ehSTlRan82GS6N6" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-hBeIC_ehSTlRan82GS6N6">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-fjeLS3jCnvneflNpJNuG1" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-fjeLS3jCnvneflNpJNuG1">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Wo2EUrSMGxtIxBT5ZH_6k" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Wo2EUrSMGxtIxBT5ZH_6k">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS±00:00</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-9Q4u1N9o7PLCi2PBpdRo3" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-9Q4u1N9o7PLCi2PBpdRo3">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-VHv-QWKgSVi9Cb3Rv-CP_" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-VHv-QWKgSVi9Cb3Rv-CP_">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-w4Iu7RZFjw9CbRw4_mqG1" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-w4Iu7RZFjw9CbRw4_mqG1">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-GdheqH4n90wllmb7u0wOD" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-GdheqH4n90wllmb7u0wOD">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS±0000</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-bOqIINMEFfc1vRBOXDcEb" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-bOqIINMEFfc1vRBOXDcEb">Unsupported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-h93px9Wx7rnr0wp7Iy4pU" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-h93px9Wx7rnr0wp7Iy4pU">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-ZrLUItPWn4bvA8idZDbmk" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-ZrLUItPWn4bvA8idZDbmk">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-SUF_wNBZW-nOl6g-VD0uI" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-SUF_wNBZW-nOl6g-VD0uI">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-kJEfvI7jkopB1-UFrnLs0" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-kJEfvI7jkopB1-UFrnLs0">Supported</title></svg></td> </tr> </tbody> </table> <dl class="flex"> <dt><svg class="z-icon"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use></svg></dt> <dd>Unsupported</dd> <dt><svg class="z-icon"><use href="#fas-fa-square-xmark" xlink:href="#fas-fa-square-xmark"></use></svg></dt> <dd>Inaccurate parsing</dd> <dt><svg class="z-icon"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use></svg><span class="a11y-only">Face looking surprised</span></dt> <dd>Surprising (to me)</dd> </dl> <p><em>* Note that <code>Date.parse</code> results may be browser/runtime dependent. The results above were generated from Node.js.</em></p> <h2 id="a-new-challenger-appears">A new challenger appears</h2> <p>It is with a little trepidation that I have shipped <code>@11ty/parse-date-strings</code>, a new RFC 9557 compatible date parsing library that Eleventy will use moving forward.</p> <p>The support table of this library matches the RFC 9557 column documented above. It’s focused on <em>parsing only</em> and our full test suite compares outputs with both the upcoming Temporal API and existing Luxon output.</p> <p>While there are a few breaking changes when compared with Luxon output (noted above), this swap will ultimately prepare us for native Temporal support <em>without breaking changes later</em>!</p> <table> <thead> <tr> <th>Package</th> <th>Type</th> <th>Disk Size</th> <th><a href="https://bundlephobia.com/">Bundle Size</a></th> </tr> </thead> <tbody> <tr> <td><code><a href="https://www.npmjs.com/package/@11ty/parse-date-strings">@11ty/parse-date-strings</a>@2.0.4</code></td> <td>ESM</td> <td><code>6.69 kB</code></td> <td><code>2.3 kB</code> (min)</td> </tr> </tbody> </table> <p>This library saves ~230 kB in the upcoming <code>@11ty/client</code> bundle. It should also allow <code>@11ty/eleventy</code> <code>node_modules</code> install weight to drop from 21.3 MB to 16.6 MB. <em>(Some folks might remember when <a href="https://github.com/11ty/eleventy/releases/tag/v2.0.0"><code>@11ty/eleventy@1</code> weighed in at 155 MB</a>!)</em></p> <h2 id="late-additions">Late Additions</h2> <p>For posterity, here are a few other alternative date libraries / Temporal polyfills that I think are worth mentioning (and might help you in different ways on your own date parsing journey):</p> <table> <thead> <tr> <th>Package</th> <th>Type</th> <th>Disk Size</th> <th><a href="https://bundlephobia.com/">Bundle Size</a></th> </tr> </thead> <tbody> <tr> <td><code><a href="https://www.npmjs.com/package/@js-temporal/polyfill">@js-temporal/polyfill</a>@0.3.0</code></td> <td>Dual</td> <td><code>2.98 MB</code></td> <td><code>186.5 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/temporal-polyfill">temporal-polyfill</a>@0.3.0</code></td> <td>Dual</td> <td><code>551 kB</code></td> <td><code>56.3 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/@formkit/tempo">@formkit/tempo</a>@0.1.2</code></td> <td>Dual</td> <td><code>501 kB</code></td> <td><code>17.3 kB</code> (min)</td> </tr> </tbody> </table> How to import() a JavaScript String 2025-06-09T05:00:00Z https://www.zachleat.com/web/dynamic-import/ <p>You can use arbitrary <a href="https://www.11ty.dev/docs/data-frontmatter/#java-script-front-matter">JavaScript in front matter in Eleventy project files</a> (via the <code>js</code> type).</p> <p>Historically Eleventy has made use of the <code>node-retrieve-globals</code> package to accomplish this, which was a nightmarish conglomeration of a few different Node.js approaches (each with different advantages and drawbacks).</p> <p><em>Related research: <a href="https://github.com/zachleat/javascript-eval-modules">Dynamic Script Evaluation in JavaScript</a></em></p> <p>The biggest drawbacks to <code>node-retrieve-globals</code> include:</p> <ul> <li>CommonJS code only (even in <a href="https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/">a <code>require(esm)</code> world</a>). While dynamic <code>import()</code> works, <code>import</code> and <code>export</code> do not. Top level <code>await</code> is emulated typically by wrapping your code with an <code>async</code> function wrapper.</li> <li>It uses Node.js only approaches not viable as Eleventy works to deliver <a href="https://fediverse.zachleat.com/@zachleat/114434795493653605">a library</a> that is <a href="https://neighborhood.11ty.dev/@11ty/114519676689929120">browser-friendly</a>.</li> </ul> <p>Regardless, this abomination was a necessary evil due to the experimental status of Node.js’ <a href="https://nodejs.org/docs/latest/api/vm.html#class-vmmodule"><code>vm.Module</code></a> (since Node v12, ~2019), the ESM-friendly partner to CommonJS-compatible <code>vm.Script</code>. I’d still love to see <code>vm.Module</code> achieve a level of stability, but I digress.</p> <h2 id="new-best-friend-is-import">New Best Friend is <code>import()</code></h2> <p>Moving forward, I’ve been having success from a much lighter approach using <code>import()</code>, described in <a href="https://2ality.com/2019/10/eval-via-import.html"><em>Evaluating JavaScript code via import()</em> by Dr. Axel Rauschmayer</a>. It looks something like this:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">let</span> code <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">export default function() {}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">let</span> u <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data:text/javascript;charset=utf-8,</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">let</span> mod <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Newer runtimes with <code>Blob</code> support might look like this (<a href="https://github.com/dbushell/dinossr/blob/f555a4231c230aebc563194fc88778eb58270879/src/bundle/import.ts#L13-L16">example from David Bushell</a>):</p> <pre class="language-js"><code class="language-js"><span class="token keyword">let</span> code <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">export default function() {}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">let</span> blob <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span>code<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"text/javascript"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> u <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span>blob<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> mod <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">revokeObjectURL</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <h3 id="limitations">Limitations</h3> <ol> <li>Importing a Blob of code does <em>not</em> work in Node.js (as of v24), despite Node having support for Blob in v18 and newer. <blockquote> <p>Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. Received protocol 'blob:'</p> </blockquote> </li> <li><code>import.meta.url</code> just points to the parent <code>data:</code> or <code>blob:</code>, which isn’t super helpful in script.</li> <li>No import of relative references, even if you’ve mapped it to a full path via an Import Map. <ul> <li>e.g. <code>import './relative.js'</code> <blockquote> <p>TypeError: Failed to resolve module specifier ./relative.js: Invalid relative url or base scheme isn't hierarchical.</p> </blockquote> </li> </ul> </li> <li>No import of bare references. These <em>can</em> be remapped via Import Maps. <ul> <li>e.g. <code>import 'barename'</code> <blockquote> <p>TypeError: Failed to resolve module specifier &quot;barename&quot;. Relative references must start with either &quot;/&quot;, &quot;./&quot;, or &quot;../&quot;.</p> </blockquote> </li> </ul> </li> </ol> <p>Though interestingly, Node.js <em>will</em> let you import builtins e.g. <code>import 'node:fs'</code>.</p> <h2 id="enter-import-module-string">Enter <code>import-module-string</code></h2> <p>I’ve worked around the above limitations and packaged this code up into <code>import-module-string</code>, a package that <em>could</em> be described as a super lightweight runtime-independent (server or client) JavaScript bundler.</p> <ul> <li><a href="https://github.com/zachleat/import-module-string"><code>import-module-string</code> on GitHub</a></li> <li><a href="https://www.npmjs.com/package/import-module-string"><code>import-module-string</code> on npm</a></li> </ul> <p>I was able to repurpose <a href="https://www.zachleat.com/web/esm-import-transformer/">a package I created in June 2022</a> to help here: <a href="https://github.com/zachleat/esm-import-transformer"><code>esm-import-transformer</code></a> recursively preprocesses and transform imports to remap them to <code>Blob</code> URLs (falling back to <code>data:</code> when a feature test determines Blob doesn’t work).</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> importFromString <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"import-module-string"</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">importFromString</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">import num from "./relative.js"; export const c = num;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Where <code>relative.js</code> contains <code>export default 3;</code>, the above code becomes (example from Node.js):</p> <pre class="language-js"><code class="language-js"><span class="token keyword">await</span> <span class="token function">importFromString</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">import num from "data:text/javascript;charset=utf-8,export%20default%203%3B"; export const c = num;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Which returns:</p> <pre class="language-js"><code class="language-js"><span class="token punctuation">{</span> <span class="token literal-property property">c</span><span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span></code></pre> <p>This transformation happens recursively for all imports (even imports in imports) with very little ceremony.</p> <p>When you’ve added a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap"><code>&lt;script type=&quot;importmap&quot;&gt;</code> Import Map</a> to your HTML, the script will use <code>import.meta.resolve</code> to use the Import Map when resolving module targets.</p> <h3 id="even-more-features">Even more features</h3> <p>A few more features for this new package:</p> <ul> <li>Extremely limited dependency footprint, only 3 dependencies total: <code>acorn</code>, <code>acorn-walk</code>, and <code>esm-import-transformer</code>.</li> <li>Multi-runtime: tested with Node (18+), some limited testing in Deno, Chromium, Firefox, and WebKit. <ul> <li>This was my first time using <a href="https://vitest.dev/">Vitest</a> and it worked pretty well! I only hit one snag trying to <a href="https://github.com/vitest-dev/vitest/issues/6953">test <code>import.meta.resolve</code></a>.</li> </ul> </li> <li>Supports top-level <code>async</code>/<code>await</code> (as expected in ES modules)</li> <li>If you use <code>export</code>, the package uses your exports to determine what it returns. If there is no <code>export</code> in play, it implicitly exports all globals (via <code>var</code>, <code>let</code>, <code>const</code>, <code>function</code>, <code>Array</code> or <code>Object</code> destructuring assignment, <code>import</code> specifiers, etc), emulating the behavior in <code>node-retrieve-globals</code>. You can disable implicit exports using <code>implicitExports: false</code>.</li> <li>Emulates <code>import.meta.url</code> when the <code>filePath</code> option is supplied</li> <li><code>addRequire</code> option adds support for <code>require()</code> (this feature is exclusive to server runtimes)</li> <li>Supports a <code>data</code> object to pass in your own global variables to the script. These must be <code>JSON.stringify</code> friendly, though this restriction could be relaxed with more serialization options later.</li> <li>When running in-browser, each script is subject to URL content size maximums: Chrome <code>512MB</code>, Safari <code>2048MB</code>, Firefox <code>512MB</code>, Firefox prior to v137 <code>32MB</code>.</li> </ul> <p>As always with dynamic script execution, do not use this mechanism to run code that is untrusted (<em>especially</em> when running in-browser on a domain with privileged access to secure information like authentication tokens). Make sure you sandbox appropriately!</p> line-numbers Web Component 2025-06-05T05:00:00Z https://www.zachleat.com/web/line-numbers/ <p><code>&lt;line-numbers&gt;</code> is a web component to add line numbers alongside <code>&lt;pre&gt;</code> or <code>&lt;textarea&gt;</code> elements.</p> <ul> <li><a href="https://zachleat.github.io/line-numbers/demo.html"><strong>Demo</strong></a></li> <li><a href="https://www.npmjs.com/package/@zachleat/line-numbers">npm package</a></li> <li><a href="https://github.com/zachleat/line-numbers">GitHub repository</a></li> </ul> <p>Install:</p> <pre class="language-sh"><code class="language-sh"><span class="token function">npm</span> <span class="token function">install</span> @zachleat/line-numbers</code></pre> <h2 id="features">Features</h2> <ul> <li><code>&lt;pre&gt;</code> supported</li> <li><code>&lt;textarea&gt;</code> supported (even when adding or removing lines)</li> <li>CSS <code>overflow</code> supported (with obtrusive/visible or nonobtrusive scrollbars)</li> <li>Numbers are excluded from content flow (not selectable, important for copy paste components!)</li> <li>Use <em>any</em> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/counter#counter-style">CSS counter style</a> via custom property <code>--uln-number-type</code></li> <li>Change the starting index for counter via (<code>&lt;line-numbers start=&quot;999&quot;&gt;</code>)</li> <li>Numbers are unobtrusive by default to reduce layout shift (opt-in to obtrusive behavior via <code>&lt;line-numbers obtrusive&gt;</code>)</li> </ul> <h3 id="limitations">Limitations</h3> <p>Trying to keep this one as simple as possible, so please note the following:</p> <ul> <li>Line wrapping is <strong>not</strong> supported (<code>white-space: pre</code> or <code>white-space: nowrap</code> only, and this is enforced by the component)</li> <li>Elements using <code>contenteditable</code> are <strong>not</strong> supported</li> </ul> Check the speedometer on the brand new Blog Awesome (now with 11ty) 2025-03-28T05:00:00Z https://www.zachleat.com/web/blog-awesome-move/ <h2 id="related">Related</h2> <ul> <li><a href="https://www.zachleat.com/web/blog-awesome/">Video: <em>11ty Meetup: Blog Awesome from WordPress to Eleventy</em></a></li> </ul> 11ty Meetup: Blog Awesome from WordPress to Eleventy 2025-03-20T05:00:00Z https://www.zachleat.com/web/blog-awesome/ <div><youtube-lite-player> <script type="module" src="https://www.zachleat.com/static/browser-window.js"></script> <browser-window mode="dark" flush="" icon="" url="https://youtube.com/watch?v=O89QIruTink" shadow=""> <is-land on:visible="" class="fluid-width-video-wrapper"> <lite-youtube videoid="O89QIruTink" js-api="" playlabel="Play: 11ty Meetup: Blog Awesome from WordPress to Eleventy" style="background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DO89QIruTink/auto/jpeg/')"></lite-youtube> <template data-island="once"> <style> lite-youtube { max-width: 100% !important; background-size: cover; } /* Plugin bug: clicking the red youtube play icon in the center would navigate to youtube.com */ lite-youtube:defined .lty-playbtn { pointer-events: none; } </style> <link rel="stylesheet" href="https://www.zachleat.com/static/lite-yt-embed.css"> <script type="module" src="https://www.zachleat.com/static/lite-yt-embed.js"></script> </template> </is-land> </browser-window> <youtube-link label="11ty Meetup: Blog Awesome from WordPress to Eleventy" href="https://youtube.com/watch?v=O89QIruTink"> <a href="https://youtube.com/watch?v=O89QIruTink" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>11ty Meetup: Blog Awesome from WordPress to Eleventy</em></a></youtube-link></youtube-lite-player></div> <ul> <li><a href="https://11tymeetup.dev/events/ep-22-umami-analytics-and-blog-awesome/">Event page on 11tymeetup.dev</a></li> </ul> Extract Colors from an Image for CSS Themes 2025-02-27T06:00:00Z https://www.zachleat.com/web/extract-colors/ <p>Working through the migration of Blog Awesome from WordPress to Eleventy, I encountered an interesting problem: each blog post was seeded with a featured image at the top, though the image did not cover the entirety of the header.</p> <script type="module" src="https://www.zachleat.com/static/browser-window.js"></script> <browser-window shadow="" mode="dark" flush="" style="--bw-background: #62e6be"> <img src="https://www.zachleat.com/img/built/426uQoZ9zm-2042.avif" alt="Screenshot of blog.fontawesome.com with a featured image that says Font Awesome + 11ty" loading="lazy" decoding="async" width="2042" height="1052"> </browser-window> <p>This required a manual process (a WordPress custom field) to specify a theme color to match the background of the image. We can do better!</p> <p>Using a similar layout structure, this what the new design looks like (with a bit better automatic dark/light mode contrast on the flag and text color, too):</p> <browser-window shadow="" mode="dark" flush="" style="--bw-background: #62e6be"> <img src="https://www.zachleat.com/img/built/ZugJXkjVZw-2050.avif" alt="Screenshot of the new blog.fontawesome.com with a featured image that says Font Awesome + 11ty" loading="lazy" decoding="async" width="2050" height="1112"> </browser-window> <p>To accomplish this automation, I turned to a lovely zero-dependency package called <a href="https://www.npmjs.com/package/extract-colors"><code>extract-colors</code></a> from <a href="https://namide.com/"><code>namide.com</code></a>.</p> <p>In all of the eligible blog posts tested, the last color returned in the result from the <code>extract-colors</code> package always matched the background color of the image being sampled.</p> <p><code>extract-colors</code> recommends the use of another package (<a href="https://www.npmjs.com/package/get-pixels"><code>get-pixels</code></a>) to extract pixel data from images but it was no longer maintained so I <a href="https://fediverse.zachleat.com/@zachleat/113947026720491470">forked, updated, and released a Node.js only version of the package</a> to fix some upstream issues.</p> <h2 id="new-11ty-image-color-package">New <code>@11ty/image-color</code> Package</h2> <p>I wired this up with a <a href="https://www.npmjs.com/package/memoize">memoization layer</a>, a <a href="https://www.11ty.dev/docs/plugins/fetch/#manually-store-your-own-data-in-the-cache">disk cache</a>, a <a href="https://www.npmjs.com/package/p-queue">concurrency queue</a>, and integrated it with existing overlapping functionality provided by Eleventy <a href="https://www.11ty.dev/docs/plugins/fetch/">Fetch</a> and <a href="https://www.11ty.dev/docs/plugins/image/">Image</a> utilities for build performance (as well as adding <a href="https://colorjs.io/">Color.js</a> for some color filtering) and packaged this all up for anyone to use at <a href="https://github.com/11ty/image-color"><code>@11ty/image-color</code></a>:</p> <script type="module" src="https://www.zachleat.com/static/browser-window.js"></script> <div><browser-window mode="dark" icon="" url="https://github.com/11ty/image-color" shadow="" flush="" style="--bw-background: oklch(27.806% 0.01169 248.25)"><a href="https://github.com/11ty/image-color" class="favicon-optout"><picture><source type="image/webp" srcset="https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/small/webp/_20250802/ 375w, https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/medium/webp/_20250802/ 650w" sizes="(min-width: 64em) 50vw, 100vw"><img alt="" loading="lazy" decoding="async" class="" eleventy:ignore="" src="https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/small/jpeg/_20250802/" width="650" height="341" srcset="https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/small/jpeg/_20250802/ 375w, https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/medium/jpeg/_20250802/ 650w" sizes="(min-width: 64em) 50vw, 100vw"></picture></a></browser-window></div> <p>To get colors from a local or remote Image in my Eleventy project, I added the following configuration code to my project’s <code>eleventy.config.js</code> file:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> getImageColors <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/image-color"</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">"getImageColors"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">imageSrc</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">getImageColors</span><span class="token punctuation">(</span>imageSrc<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The above Blog Awesome example above made good use of the <code>getImageColors</code> filter in a Nunjucks template:</p> <pre class="language-njk"><code class="language-njk"><span class="token delimiter punctuation">{%-</span> <span class="token tag keyword">set</span> <span class="token variable">lastColor</span> <span class="token operator">=</span> <span class="token variable">media</span><span class="token punctuation">.</span><span class="token variable">featuredImage</span> <span class="token operator">|</span> <span class="token variable">getImageColors</span> <span class="token operator">|</span> <span class="token variable">last</span> <span class="token operator">%</span><span class="token punctuation">}</span> <span class="token punctuation">{</span><span class="token operator">%</span> <span class="token keyword">if</span> <span class="token variable">lastColor</span> <span class="token operator">%</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token variable">style</span><span class="token operator">></span> <span class="token variable">header</span> <span class="token punctuation">{</span> <span class="token variable">background</span><span class="token operator">-</span><span class="token variable">color</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token variable">lastColor</span><span class="token punctuation">.</span><span class="token variable">background</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token variable">color</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token variable">lastColor</span><span class="token punctuation">.</span><span class="token variable">foreground</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">style</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token operator">%</span> <span class="token variable">endif</span> <span class="token operator">%</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token variable">header</span><span class="token operator">></span>…<span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">header</span><span class="token operator">></span></code></pre> <h2 id="screenshot-borders">Screenshot Borders</h2> <p>For extra funsies I also made use of this functionality on 11ty.dev (<a href="https://www.11ty.dev/#built-with-eleventy">now live on the site</a>) to provide an extra accent border color on screenshot images:</p> <p><img src="https://www.zachleat.com/img/built/dQ1NIyWHpE-1312.avif" alt="A 4×4 matrix of small screenshots of the Built With Eleventy section on the 11ty.dev home page. Each screenshot has a border color that matches the favicon image" loading="lazy" decoding="async" width="1312" height="1458"></p> <p>This example works a little differently: it samples colors from the <a href="https://www.11ty.dev/docs/services/indieweb-avatar/">favicon images</a> of each site as an easy way to guess the site’s theme colors.</p> <p>This package doesn’t take a hard stance on the validity of colors but I did make use of additional filtering to select a nice border color from the list of colors available in each favicon, with the code looking something like this (again, <code>eleventy.config.js</code> Configuration code):</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> getImageColors <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/image-color"</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addShortcode</span><span class="token punctuation">(</span><span class="token string">"getColorsForUrl"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> avatarUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://v1.indieweb-avatar.11ty.dev/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">getImageColors</span><span class="token punctuation">(</span>avatarUrl<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">colors</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Note the map to colorjs props here</span> <span class="token keyword">return</span> colors<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> c<span class="token punctuation">.</span>colorjs<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Not too dark, not too light</span> <span class="token keyword">return</span> c<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">></span> <span class="token number">.4</span> <span class="token operator">&amp;&amp;</span> c<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">&lt;=</span> <span class="token number">.95</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>b<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">+</span> b<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>c<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token punctuation">(</span>a<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">+</span> a<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>…which subsequently wound up in a WebC template a little like this:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name"><span class="token namespace">webc:</span>setup</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getPrimaryColorStyle</span><span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> colors <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getColorsForUrl</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span>colors<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">--card-border-color: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>colors<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">"hex"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">:href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span> <span class="token attr-name">:style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>getPrimaryColorStyle(url)<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <h2 id="more-open-source">More Open Source</h2> <p>This Blog Awesome migration project (launching soon!) has yielded a few more useful open source utilities to the Eleventy ecosystem, which I encourage you to try out!</p> <ul> <li><a href="https://github.com/11ty/image-color"><code>@11ty/image-color</code></a> (you already read about this here)</li> <li><a href="https://github.com/11ty/eleventy-import"><code>@11ty/import</code></a> (import WordPress posts as Markdown files)</li> <li><a href="https://github.com/11ty/eleventy-plugin-font-awesome"><code>@11ty/font-awesome</code></a> (use zero-JavaScript SVG icons)</li> </ul> ?nodefine — a pattern to skip Custom Element definitions 2025-02-14T06:00:00Z https://www.zachleat.com/web/nodefine/ <p>The following code is a minimum viable custom element:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"nim-ble"</span><span class="token punctuation">,</span> Nimble<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>define</code> call is typically packaged up in the component code (for ease of use) and not in your application code. I typically adapt this into a static function like so:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">define</span><span class="token punctuation">(</span><span class="token parameter">tagName</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tagName <span class="token operator">||</span> <span class="token string">"nim-ble"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>On first glance, this might not offer much benefit, but depending on the platform features I’m using I may also include a <a href="https://responsivenews.co.uk/post/18948466399/cutting-the-mustard">Cut-the-Mustard style feature test</a> in there:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">define</span><span class="token punctuation">(</span><span class="token parameter">tagName</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Baseline 2020: Chrome 71 Safari 12.1 Firefox 65</span> <span class="token comment">// (extended browser support on top of ESM and Custom Elements)</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token keyword">typeof</span> globalThis <span class="token operator">!==</span> <span class="token string">"undefined"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tagName <span class="token operator">||</span> <span class="token string">"nim-ble"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <h2 id="automatic-definition">Automatic Definition</h2> <p>The biggest drawback I’m struggling with in these patterns is how to allow folks to opt-out of the <code>customElements.define</code> call when they import or bundle the script. Perhaps they might want to use a different tag name (you can only define a <code>class</code> once in the registry), or run some additional advanced configuration before definition.</p> <p>Here’s how you would typically import the script code (or <code>import &quot;./nimble.js&quot;</code> works too):</p> <pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- &lt;nim-ble> is defined for you --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>nimble.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>Here’s a new idea I’m experimenting with to allow opt-out of <code>define()</code> by adding a <code>?nodefine</code> query parameter to the script URL:</p> <pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- &lt;nim-ble> is *not* defined for you --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>nimble.js?nodefine<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"some-other-name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>And then in your component code you’ll check for <code>?nodefine</code> before auto defining the custom element:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">define</span><span class="token punctuation">(</span><span class="token parameter">tagName</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tagName <span class="token operator">||</span> <span class="token string">"nim-ble"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// This line is the magic:</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span><span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span><span class="token string">"nodefine"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> window<span class="token punctuation">.</span>Nimble <span class="token operator">=</span> Nimble<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token punctuation">{</span> Nimble <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>This pattern works with <code>import</code> too:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> <span class="token comment">// &lt;nim-ble> is *not* defined for you</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> Nimble <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./nimble.js?nodefine"</span><span class="token punctuation">;</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"some-other-name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Or directly:</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"nim-ble"</span><span class="token punctuation">,</span> Nimble<span class="token punctuation">)</span><span class="token punctuation">;</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>This would also work on any Custom Element code adopting this pattern nestled inside a larger bundle:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>bundle-398720.js?nodefine<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>What do y’all think?</p> <h2 id="community-addendums">Community Addendums</h2> <p><em>Updated February 14, 2025</em> I somehow missed these excellent links which cover similar ground:</p> <ul> <li><a href="https://knowler.dev/blog/to-define-custom-elements-or-not-when-distributing-them">Nathan Knowler’s To define custom elements or not when distributing them</a></li> <li><a href="https://til.jakelazaroff.com/html/define-a-custom-element/">Jake Lazaroff’s Define a custom element</a></li> <li><a href="https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/on-demand-definitions.md">Doug Parker’s On-Demand Definitions Protocol in the Web Components Community Group</a></li> </ul> Blog Questions Challenge 2025 2025-02-01T06:00:00Z https://www.zachleat.com/web/blogging/ <p>I was tagged to answer these questions by <a href="https://thoresson.social/@anders/113850471891430310">Anders Thoresson</a> as part of the larger Blog Questions Challenge originally started by <a href="https://blog.avas.space/bear-blog-challenge/">ava</a>.</p> <p>I’ll pass the baton (and a very optional tag to) to <a href="https://lynnandtonic.com/">Lynn Fisher</a>, <a href="https://ryanmulligan.dev/">Ryan Mulligan</a>, and <a href="https://sarajoy.dev/">Sara Joy</a>!</p> <p>Here are some friends that have already participated:</p> <ul> <li><a href="https://ethanmarcotte.com/wrote/blog-questions-challenge/">Ethan Marcotte</a></li> <li><a href="https://bobmonsour.com/blog/blog-questions-challenge/">Bob Monsour</a></li> <li><em>Maybe you?</em> (If you want me to link to your post, send me a <a href="https://www.zachleat.com/about/">DM</a>)</li> </ul> <h2 id="why-did-you-start-blogging">Why did you start blogging?</h2> <p>I originally started blogging on WordPress during the original era of Blogs, circa 2007. Blogging was a thing often mentioned in popular media — everyone had a blog and was writing on their own space, Google was invested in driving traffic to blogs (via search and a quaint little product known as Google Reader). It was a really great time for independent folks to get started and invest in a platform that would pay off both in the short and long-term.</p> <p>I also am staunchly of the belief that people should <em>share what they know</em> — it doesn’t have to be something that is novel or exclusive or even new. Share what you know and we all benefit from it.</p> <p>Unfortunately I also vividly remember a vibe shift when Twitter originally started to get popular and Google Reader was shuttered to make way for <a href="https://en.wikipedia.org/wiki/Google%2B">Google+</a>. Search feels as though it has been (mostly) conquered by bad actors and social media platforms. A similar shift seems to be happening with how information is consumed via Large Language Models (for the worse).</p> <h2 id="what-platform-are-you-using-to-manage-your-blog-and-why">What platform are you using to manage your blog and why?</h2> <p>I created the <a href="https://www.11ty.dev/">Eleventy project</a> to help others create and easily manage content web sites (like blogs), and that’s what I’m using for this site (since 2018). You can read about the early origins of Eleventy on <a href="https://www.zachleat.com/web/introducing-eleventy/"><em>Eleventy, a new Static Site Generator</em></a>.</p> <h2 id="have-you-blogged-on-other-platforms-before">Have you blogged on other platforms before?</h2> <p>I do maintain a <a href="https://www.zachleat.com/about/#site-history">little table of platform history</a> on my about page. I moved from WordPress to Jekyll in 2013 after going through some dark times with WordPress security vulnerabilities and database scaling issues. Updates needed to be applied on WordPress at approximately the same cadence as early Microsoft Windows and it became cumbersome to manage. I did have plenty of experience with PHP and MySQL at the time but didn’t feel as though that work was worth it for what I was getting out of it.</p> <h2 id="how-do-you-write-your-posts">How do you write your posts?</h2> <p>I write my blog posts in my text editor on my computer (currently VS Code). I don’t write posts on my phone, though I do capture ideas in the Apple Notes app for later iteration. I then commit the posts to the git repository (currently on GitHub) and the site is built on Netlify.</p> <h2 id="when-do-you-feel-most-inspired-to-write">When do you feel most inspired to write?</h2> <p>When I’ve built something new, learned something new, or read something new. I love doing deep dives on new topics but those can be very time consuming to write.</p> <h2 id="do-you-publish-immediately-after-writing-or-do-you-let-it-simmer-a-bit-as-a-draft">Do you publish immediately after writing, or do you let it simmer a bit as a draft?</h2> <p>I usually publish immediately and then iterate for a day or two before I share it on socials. If you’re subscribed to my feed you’ll see some of those iterations happen in your reader as I obsess over grammar and copy. This step can only happen after publishing (as issues of this nature are usually invisible to me before publishing).</p> <h2 id="whats-your-favorite-post-on-your-blog">What's your favorite post on your blog?</h2> <p>The most successful blog post I’ve ever done is <a href="https://www.zachleat.com/web/comprehensive-webfonts/"><em>A Comprehensive Guide to Font Loading Strategies</em></a> but it definitely isn’t my <em>favorite</em>.</p> <p>The first one that jumped out at me was this 2017 blog post about anti-aliasing fonts (also known as font smoothing) titled <a href="https://www.zachleat.com/web/font-smooth/"><em>Laissez-faire Font Smoothing and Anti-aliasing</em></a>. I learned a lot writing that one and it was a lot of fun.</p> <h2 id="any-future-plans-for-your-blog">Any future plans for your blog?</h2> <p>Where do I see my blog in 5 years? Hopefully continuing my current speed of ~30 posts per year and going strong — and still on Eleventy, too <svg class="z-icon"><use href="#far-fa-face-kiss" xlink:href="#far-fa-face-kiss"></use></svg>.</p>
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title>Zach Leatherman</title>
<subtitle>A web development blog written by @zachleat.</subtitle>
<link href="https://www.zachleat.com/web/feed/atom.xml" rel="self"/>
<link href="https://www.zachleat.com/"/>
<updated>2025-08-19T05:00:00Z</updated>
<id>https://www.zachleat.com/</id>
<author>
<name>Zach Leatherman</name>
</author>
<entry>
<title>A note from my late Grandmother about Eleventy</title>
<link href="https://www.zachleat.com/web/history-of-eleventy-name/"/>
<updated>2025-08-19T05:00:00Z</updated>
<id>https://www.zachleat.com/web/history-of-eleventy-name/</id>
<content type="html"><p>Our basement flooded this week, so I’ve been cleaning up the mess. In that process, I found a 22-year-old letter from my grandmother (who passed away Christmas 2023) to my mom that had a little Eleventy naming history that some folks might enjoy.</p> <p>I first talked about the significance and history of the Eleventy name back in a <a href="https://www.zachleat.com/web/eleventy-birthday/#project-naming">blog post from 2018 celebrating the project’s first birthday</a>:</p> <blockquote> <p>I chose it because of a story my grandma Nonnie loved to tell about how I learned to count. Rather than move from ten to eleven like a normal child, I felt it appropriate to use the teen suffix for the numbers eleven and twelve, counting “ten, eleventy-teen, twelvety-teen, thirteen, …”</p> </blockquote> <p>The discovery of this letter was very surprising to me as I hadn’t reviewed the contents of this box in almost 20 years (long before Eleventy). I’ll include a photo of the letter and of the newspaper clipping of the Dennis the Menace comic she had included below (with some redacted unrelated personal details for brevity).</p> <p><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/3Xe8HDSwbE-1016.avif 1016w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/3Xe8HDSwbE-1016.jpeg" alt="Dennis the Menace really hit a soft spot with me this week, so I am sending it to share with you. When Zach was about three or four, he was counting for me and after ten he said “leventy teen.” When I said, “But Zach, we don't say ‘leventy teen,” I got this reply. “But I do. I do say ‘leventy teen.” That has always been one of my favorite memories. So now we know there really is a “leventy teen.” He was right all the time. No more news, I must get back to work, I think the floor is dry. So there is no excuse for not getting the clothes folded. Thanks again for everything. Love, Mom" width="1016" height="1237"></picture></p> <blockquote> <p>Dennis the Menace really hit a soft spot with me this week, so I am sending it to share with you. When Zach was about three or four, he was counting for me and after ten he said “leventy teen.” When I said, “But Zach, we don't say 'leventy teen,” I got this reply. “But I do. I do say 'leventy teen.” That has always been one of my favorite memories. So now we know there really is a “leventy teen.” He was right all the time.</p> </blockquote> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/pVizryAM4e-580.avif 580w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/pVizryAM4e-580.jpeg" alt="DENNIS the MENACE, Numbers Racket: Doesn’t this game have any rules? Sure it has plenty!" width="580" height="184"></picture> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/WzaP-8PgUR-608.avif 608w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/WzaP-8PgUR-608.jpeg" alt="Too bad we can’t read yet. That’s fourelve for me and fiftyteen for you! Fiftyteen? And another Ninety Twelve for me! That makes Eleventy Teen!" width="608" height="183"></picture> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/9v4IJYvp7Q-594.avif 594w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/9v4IJYvp7Q-594.jpeg" alt="There’s no such number as Eleventy Teen!!! Sure there is! It’s halfway between twoteen an’ leventy seventy! And how much is leventy seventy? It’s a hunnerd times twixty!" width="594" height="190"></picture> <picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Dd0DcKkXs9-602.avif 602w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw"><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Dd0DcKkXs9-602.jpeg" alt="You’re absolutely hopeless!! What’s her problem? She knows when she’s outnumbered." width="602" height="191"></picture> <p><em>Miss you, Nonnie <svg class="z-icon"><use href="#fas-fa-heart" xlink:href="#fas-fa-heart"></use></svg></em></p> </content>
</entry>
<entry>
<title>One weird trick to reduce Eleventy Image Build Times by 60%</title>
<link href="https://www.zachleat.com/web/faster-builds-with-eleventy-img/"/>
<updated>2025-08-01T05:00:00Z</updated>
<id>https://www.zachleat.com/web/faster-builds-with-eleventy-img/</id>
<content type="html"><p>This web site does a fair bit of build-time Image Optimization using the <a href="https://www.11ty.dev/docs/plugins/image/#html-transform">HTML Transform method provided by the Eleventy Image plugin</a>. I took a bit of a set-it-and-forget-it approach with the plugin on this site, mostly for my own convenience. My usage looked something like this:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> eleventyImageTransformPlugin <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/eleventy-img"</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addPlugin</span><span class="token punctuation">(</span>eleventyImageTransformPlugin<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">failOnError</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token literal-property property">formats</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"svg"</span><span class="token punctuation">,</span> <span class="token string">"avif"</span><span class="token punctuation">,</span> <span class="token string">"jpeg"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token literal-property property">svgShortCircuit</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>Eleventy Image includes a <a href="https://www.11ty.dev/docs/plugins/image/#build-performance">fair number of caches and performance optimizations</a> to make this default behavior pretty fast.</p> <ul> <li>It uses a memory cache to de-duplicate multiple requests to optimize the same input image.</li> <li>It uses an on-request image processing engine to avoid processing images during local development.</li> <li>It uses a hash for output file name and checks the target file location to avoid reprocessing images.</li> </ul> <p>The biggest drawback with the third method is that when you’re building on a deployment server, these environments start with an empty output folder (and thus, an empty disk cache). What can we do to re-use the disk cache and avoid reprocessing unchanged images on each new deploy?</p> <p>With just a <a href="https://github.com/zachleat/zachleat.com/commit/eead25b5b38431d3b61bfb318a186d3214c45260">few lines of configuration code</a> to create an intermediate output folder in a location that is persisted between builds (<code>.cache</code>), I was able to drop my site’s build time from an embarrasing <code>9:40</code> down to a respectable <code>3:56</code> <svg class="z-icon" aria-labelledby="fa11-text-W7_B7iBsOlxj5iZU6oQwm" role="img"><use href="#fas-fa-trophy" xlink:href="#fas-fa-trophy"></use><title id="fa11-text-W7_B7iBsOlxj5iZU6oQwm">(trophy icon)</title></svg> (for throughput info, the output folder has 7,779 files weighing 492.7 MB).</p> <p>I think this approach is <strong>very reusable</strong> and we’ll likely bundle it into a future version of Eleventy Image. Until then, you can use it yourself by adding the following lines of configuration:</p> <pre class="language-diff-js"><code class="language-diff-js"><span class="token inserted-sign inserted language-js"><span class="token prefix inserted">+</span><span class="token keyword">import</span> path <span class="token keyword">from</span> <span class="token string">"node:path"</span><span class="token punctuation">;</span> <span class="token prefix inserted">+</span><span class="token keyword">import</span> fs <span class="token keyword">from</span> <span class="token string">"node:fs"</span><span class="token punctuation">;</span> </span><span class="token unchanged language-js"><span class="token prefix unchanged"> </span><span class="token keyword">import</span> <span class="token punctuation">{</span> eleventyImageTransformPlugin <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/eleventy-img"</span><span class="token punctuation">;</span> </span> <span class="token unchanged language-js"><span class="token prefix unchanged"> </span><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token prefix unchanged"> </span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addPlugin</span><span class="token punctuation">(</span>eleventyImageTransformPlugin<span class="token punctuation">,</span> <span class="token punctuation">{</span> </span><span class="token inserted-sign inserted language-js"><span class="token prefix inserted">+</span> <span class="token literal-property property">urlPath</span><span class="token operator">:</span> <span class="token string">"/img/built/"</span><span class="token punctuation">,</span> <span class="token prefix inserted">+</span> <span class="token literal-property property">outputDir</span><span class="token operator">:</span> <span class="token string">".cache/@11ty/img/"</span><span class="token punctuation">,</span> </span><span class="token unchanged language-js"><span class="token prefix unchanged"> </span> <span class="token literal-property property">failOnError</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token prefix unchanged"> </span> <span class="token literal-property property">formats</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"svg"</span><span class="token punctuation">,</span> <span class="token string">"avif"</span><span class="token punctuation">,</span> <span class="token string">"jpeg"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token prefix unchanged"> </span> <span class="token literal-property property">svgShortCircuit</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token prefix unchanged"> </span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span> <span class="token inserted-sign inserted language-js"><span class="token prefix inserted">+</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">"eleventy.after"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token prefix inserted">+</span> fs<span class="token punctuation">.</span><span class="token function">cpSync</span><span class="token punctuation">(</span><span class="token string">".cache/@11ty/img/"</span><span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span>eleventyConfig<span class="token punctuation">.</span>directories<span class="token punctuation">.</span>output<span class="token punctuation">,</span> <span class="token string">"/img/built/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token prefix inserted">+</span> <span class="token literal-property property">recursive</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token prefix inserted">+</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token prefix inserted">+</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span><span class="token unchanged language-js"><span class="token prefix unchanged"> </span><span class="token punctuation">}</span><span class="token punctuation">;</span> </span></code></pre> <p>You could improve the above example by restricting the copy step using the <a href="https://www.11ty.dev/docs/environment-vars/#eleventy-supplied"><code>process.env.ELEVENTY_RUN_MODE</code> environment variable</a>.</p> <p>You may also need to <a href="https://www.11ty.dev/docs/deployment/#persisting-cache">persist <code>.cache</code> on your web host</a>. This behavior is provided for-free if you’re using Vercel or Cloudflare Pages.</p> <p><em>Subscribe to <a href="https://github.com/11ty/eleventy-img/issues/285"><code>github.com/11ty/eleventy-img/issues/285</code></a> for future updates.</em></p> </content>
</entry>
<entry>
<title>Never write your own Date Parsing Library</title>
<link href="https://www.zachleat.com/web/adventures-in-date-parsing/"/>
<updated>2025-07-23T05:00:00Z</updated>
<id>https://www.zachleat.com/web/adventures-in-date-parsing/</id>
<content type="html"><p><em><strong>Never</strong> write your own date parsing library.</em></p> <p><em><strong>Never</strong>.</em> No exceptions.</p> <p><s>Never have I ever…</s></p> <p>So… I’ve written my own date parsing library.</p> <p><em>Why?</em> Our story begins seven years ago in the year 2018. I made the very sensible choice to adopt <code>luxon</code> as the Date Parsing library for Eleventy. This parsing behavior is used when Eleventy finds a String for the <a href="https://www.11ty.dev/docs/dates/"><code>date</code> value in the Data Cascade</a> (though YAML front matter will bypass this behavior when encountering a YAML-compatible date).</p> <p>This choice was good for Eleventy’s Node.js-only requirements at the time: accurate and not <em>too</em> big (relatively speaking). Eleventy has <a href="https://github.com/11ty/eleventy/commit/4272311dab203d2b217ebd4f6b597eb0e816006b">used <code>luxon</code> since <code>@0.2.12</code></a> and has grown with the dependency all the way through <code>@3.7.1</code>. Now that’s what I call a high quality dependency!</p> <p>As we move Eleventy to run in more JavaScript environments and runtimes (including on the client) we’ve had to take a hard look at our use of Luxon, currently <a href="https://github.com/11ty/eleventy/issues/3587">our <em>largest</em> dependency</a>:</p> <ul> <li>4.7 MB of 21.3 MB (22%) of <code>@11ty/eleventy</code> node_modules</li> <li>229 kB of 806 kB (28%) of <code>@11ty/client</code> (<em>not yet released!</em>) bundle size (unminified)</li> </ul> <p>Given that our use of Luxon is strictly limited to the <a href="https://moment.github.io/luxon/#/parsing?id=iso-8601"><code>DateTime.fromISO</code> function for ISO 8601 date parsing</a> (not formatting or display), it would have been nice to enable tree-shaking on the Luxon library to reduce its size in the bundle (though that wouldn’t have helped the <code>node_modules</code> size, I might have settled for that trade-off). Unfortunately, <a href="https://github.com/moment/luxon/issues/854">Luxon does not yet support tree-shaking</a> so it’s an all or nothing for the bundle.</p> <h2 id="the-search-begins">The Search Begins</h2> <p>I did the next sensible thing and looked at a few alternatives:</p> <table> <thead> <tr> <th>Package</th> <th>Type</th> <th>Disk Size</th> <th><a href="https://bundlephobia.com/">Bundle Size</a></th> </tr> </thead> <tbody> <tr> <td><code><a href="https://www.npmjs.com/package/luxon">luxon</a>@3.7.1</code></td> <td>Dual</td> <td><code>4.59 MB</code> <svg class="z-icon"><use href="#fas-fa-triangle-exclamation" xlink:href="#fas-fa-triangle-exclamation"></use></svg></td> <td><code>81.6 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/moment">moment</a>@2.30.1</code></td> <td>CJS</td> <td><code>4.35 MB</code> <svg class="z-icon"><use href="#fas-fa-triangle-exclamation" xlink:href="#fas-fa-triangle-exclamation"></use></svg></td> <td><code>294.9 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/dayjs">dayjs</a>@1.11.13</code></td> <td>CJS</td> <td><code>670 kB</code></td> <td><code>6.9 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/date-fns">date-fns</a>@4.1.0</code></td> <td>Dual</td> <td><code>22.6 MB</code> <svg class="z-icon"><use href="#fas-fa-triangle-exclamation" xlink:href="#fas-fa-triangle-exclamation"></use></svg></td> <td><code>77.2 kB</code> (min)</td> </tr> </tbody> </table> <p>The next in line to the throne was clearly <code>dayjs</code>, which is small on disk and in bundle size. Unfortunately I found it to be inaccurate: <code>dayjs</code> fails about 80 of the 228 tests in the test suite I’m using moving forward.</p> <div class="callout"><p>As an aside, this search has made me tempted to ask: do we need to keep Dual publishing packages? I prefer ESM over CJS but maybe just pick one?</p></div> <h2 id="breaking-changes">Breaking Changes</h2> <p>Most date parsing woes (in my opinion) come from ambiguity: from supporting <em>too many</em> formats or attempting maximum flexibility in parsing. And guess what: ISO 8601 is a big ’ol standard with a lot of subformats. There is a maintenance freedom and simplicity in strict parsing requirements (don’t let XHTML hear me say that).</p> <p>Consider <code>&quot;200&quot;</code>. Is this the year 200? Is this the 200th day of the current year? Surprise, in ISO 8601 it’s neither — it’s <a href="https://iso8601aas.ijmacd.com/?input=200">a decade, spanning from the year 2000 to the year 2010</a>. And <a href="https://iso8601aas.ijmacd.com/?input=20"><code>&quot;20&quot;</code></a> is the century from the year 2000 to the year 2100.</p> <p>Moving forward, we’re tightening up the default date parsing in Eleventy (this is <a href="https://www.11ty.dev/docs/dates/#custom-date-parsing-example">configurable</a> — keep using Luxon if you want!).</p> <p>Luckily we have a north star date format: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Instant#rfc_9557_format">RFC 9557</a>, billed as <em>“an extension to the ISO 8601 / RFC 3339”</em> formats and already in use by the upcoming Temporal web standard APIs for date and time parsing coming to a JavaScript runtime near you.</p> <p>There are a few notable differences:</p> <table> <thead> <tr class="nowrap"> <th>Format</th> <th>ISO 8601</th> <th><code>Date.parse</code>*</th> <th><code>luxon</code></th> <th>RFC 9557</th> </tr> </thead> <tbody> <tr> <td class="no"><code class="nowrap">YYYY</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-N_4ow_FB_oSkSGqXgc4V4" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-N_4ow_FB_oSkSGqXgc4V4">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-bTF2oYlH-wB57RAMqZnOB" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-bTF2oYlH-wB57RAMqZnOB">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-tlC3Ni7UVJPgw6xv8a6j7" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-tlC3Ni7UVJPgw6xv8a6j7">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-TVYbcY7WYcmH8SM52OAtf" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-TVYbcY7WYcmH8SM52OAtf">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">YYYY-MM</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Rfgi57PROPg0PCt20lfod" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Rfgi57PROPg0PCt20lfod">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-BUp9sJHjocG3s7IhyvPB6" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-BUp9sJHjocG3s7IhyvPB6">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-SeEYni3LhhEJ26jZ6tl99" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-SeEYni3LhhEJ26jZ6tl99">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-9gs0YvLzBWwM97r8Tsemz" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-9gs0YvLzBWwM97r8Tsemz">Unsupported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DD</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-7aK0t8NeqqkK3ExgzEVnb" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-7aK0t8NeqqkK3ExgzEVnb">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-jZlHzh9V0wJs-VuU_2iej" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-jZlHzh9V0wJs-VuU_2iej">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-iYpvMhf5VkRVuIFdwDppm" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-iYpvMhf5VkRVuIFdwDppm">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-vHV1ygCmzpnmHPs8hOo9b" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-vHV1ygCmzpnmHPs8hOo9b">Supported</title></svg></td> </tr> <tr> <td class="yes"><code class="nowrap">±YYYYYY-MM-DD</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-soMWk_bCm_q9oawPRflcT" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-soMWk_bCm_q9oawPRflcT">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-3c2H3PSvZFyDQQWtZXyfS" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-3c2H3PSvZFyDQQWtZXyfS">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-8Ch6tkIWNHwDT916wlI3t" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-8Ch6tkIWNHwDT916wlI3t">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Wbm8eBkeuZnPsNvLatuOm" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Wbm8eBkeuZnPsNvLatuOm">Supported</title></svg></td> </tr> <tr> <td>Optional <code>-</code> delimiters in dates</td> <td><svg class="z-icon" aria-labelledby="fa11-text-crx7jIqxZXUBfOaGaXaT9" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-crx7jIqxZXUBfOaGaXaT9">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text--3BcEI71tndwNSXuhIJN8" role="img"><use href="#fas-fa-square-xmark" xlink:href="#fas-fa-square-xmark"></use><title id="fa11-text--3BcEI71tndwNSXuhIJN8">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-pMh4Ku4OvIsMU1_1L4yz7" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-pMh4Ku4OvIsMU1_1L4yz7">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-QrwEFDMXdsz9bniLjf-0J" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-QrwEFDMXdsz9bniLjf-0J">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-F9CASz0qBev0AzxSVkaDO" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-F9CASz0qBev0AzxSVkaDO">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-kCw9oiSyiMk0t9nxvLy3q" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-kCw9oiSyiMk0t9nxvLy3q">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-EzxEJjTt2GzKlNJyojjpV" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-EzxEJjTt2GzKlNJyojjpV">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-T5Oelzz1D8p77aA_4pv8Q" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-T5Oelzz1D8p77aA_4pv8Q">Supported</title></svg></td> </tr> <tr> <td class="yes"><code class="nowrap">YYYY-MM-DD HH</code> (space delimiter)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-cZYqdBMySzAHchTnpekNu" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-cZYqdBMySzAHchTnpekNu">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-BSI5JygJXm4wdccmg0MqE" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-BSI5JygJXm4wdccmg0MqE">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-DcFnbUTZEdTaktUBFTL4Q" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-DcFnbUTZEdTaktUBFTL4Q">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-4W3xvA58nSQC-fVpwYscg" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-4W3xvA58nSQC-fVpwYscg">Supported</title></svg></td> </tr> <tr> <td class="yes"><code class="nowrap">YYYY-MM-DDtHH</code> (lowercase delimiter)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-EVPQ3ycH3JTAwm9c8hFmN" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-EVPQ3ycH3JTAwm9c8hFmN">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-M1YX1atDPInonrOgAIJmY" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-M1YX1atDPInonrOgAIJmY">Supported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-pqIyHuuLovnv35LzAb-Rq" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-pqIyHuuLovnv35LzAb-Rq">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-izkQucPkMvTcHAYzsTpQi" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-izkQucPkMvTcHAYzsTpQi">Supported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-hb00TlPjqzp6kZJElPRQg" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-hb00TlPjqzp6kZJElPRQg">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-xUfcK9_Aie4wprYpT-Sc9" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-xUfcK9_Aie4wprYpT-Sc9">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-j3f29FVTW5atD-fEhdxNY" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-j3f29FVTW5atD-fEhdxNY">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-XZMf2v5mD6PYO5EXgJIFa" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-XZMf2v5mD6PYO5EXgJIFa">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-3hZnjB_VgFVtC1hjxYZUC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-3hZnjB_VgFVtC1hjxYZUC">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-EmFN6JPbBQ2FWyIHK6dzL" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-EmFN6JPbBQ2FWyIHK6dzL">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-TlNTLRqPalu75pITn3rt2" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-TlNTLRqPalu75pITn3rt2">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-qKrX57N5KwLKPgcpKdOQF" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-qKrX57N5KwLKPgcpKdOQF">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-mrKOT_6now_ETMSAZUwQ5" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-mrKOT_6now_ETMSAZUwQ5">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-k2Ext8WANWf7Fmk6H6KFC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-k2Ext8WANWf7Fmk6H6KFC">Supported</title></svg></td> </tr> <tr> <td>Optional <code>:</code> delimiters in time</td> <td><svg class="z-icon" aria-labelledby="fa11-text-iZN0vcscF0jAHNef2cSw-" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-iZN0vcscF0jAHNef2cSw-">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-pPzGkr12c4zA6s8huGNWn" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-pPzGkr12c4zA6s8huGNWn">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-g8al7YcEJlpLHKZR6j9tr" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-g8al7YcEJlpLHKZR6j9tr">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-TsAr47NhyNxjGnLMfGdZT" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-TsAr47NhyNxjGnLMfGdZT">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS.SSS</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-_puZH1ZlDhX62qtmu5QYp" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-_puZH1ZlDhX62qtmu5QYp">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-yfZiupgqdFwkmM1fe7oXC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-yfZiupgqdFwkmM1fe7oXC">Supported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-FE5OwZzNl8KUt1xv8qMtr" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-FE5OwZzNl8KUt1xv8qMtr">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-PUram9Hxc1mWnTkA4UAwE" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-PUram9Hxc1mWnTkA4UAwE">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-qdKUgbU2OzUgb0uIw8lKY" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-qdKUgbU2OzUgb0uIw8lKY">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS,SSS</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-kjolRN97IfXAgP94fy5S0" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-kjolRN97IfXAgP94fy5S0">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-ug7BMp5XI7UM5acusS2YC" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-ug7BMp5XI7UM5acusS2YC">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-KB1bQ-Aa8qTq-FklBViYD" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-KB1bQ-Aa8qTq-FklBViYD">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-vtiUn0duyig2p2NBND90S" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-vtiUn0duyig2p2NBND90S">Supported</title></svg></td> </tr> <tr> <td>Microseconds (6 digit precision)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-jsVa3UY2NWSMelmTQeRNg" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-jsVa3UY2NWSMelmTQeRNg">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text--L75FOBzSrSIsDCmAYu61" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text--L75FOBzSrSIsDCmAYu61">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text--EX6UaXaW1v0ipAvCD1D6" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text--EX6UaXaW1v0ipAvCD1D6">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-HemdvwouA6COhaF1Kw1yQ" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-HemdvwouA6COhaF1Kw1yQ">Supported</title></svg></td> </tr> <tr> <td>Nanoseconds (9 digit precision)</td> <td><svg class="z-icon" aria-labelledby="fa11-text-ulv9l90I6lFDR37hZFsGX" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-ulv9l90I6lFDR37hZFsGX">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-1zLLFwfA6HDDELH24ICgw" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-1zLLFwfA6HDDELH24ICgw">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Ppj8BcLKXmu4JHg_oHLDr" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Ppj8BcLKXmu4JHg_oHLDr">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-GRH-zLezWuEXppyh_nvqU" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-GRH-zLezWuEXppyh_nvqU">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH.H</code> Fractional hours</td> <td><svg class="z-icon" aria-labelledby="fa11-text-B40_fIwoIIZ7lDhXbHIHf" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-B40_fIwoIIZ7lDhXbHIHf">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-97B4TctXJJ0IkRKBseYk-" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-97B4TctXJJ0IkRKBseYk-">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-NbIXOBuTABreIj7dLu0FU" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-NbIXOBuTABreIj7dLu0FU">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-tIwL29x4CUM7NLshhpNi1" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-tIwL29x4CUM7NLshhpNi1">Unsupported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II.I</code> Fractional minutes</td> <td><svg class="z-icon" aria-labelledby="fa11-text-myB9ORATy6pVA_turVwBT" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-myB9ORATy6pVA_turVwBT">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-4Hv1OJ5Z5RET3brjCUVqU" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-4Hv1OJ5Z5RET3brjCUVqU">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-BnwvzlKTooFW27IUIQ9Js" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-BnwvzlKTooFW27IUIQ9Js">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-gQraZ_jhtrYc2bOr892Xc" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-gQraZ_jhtrYc2bOr892Xc">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">YYYY-W01</code> ISO Week Date</td> <td><svg class="z-icon" aria-labelledby="fa11-text-RCwdd7BWa6RX6zTzAHhnC" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-RCwdd7BWa6RX6zTzAHhnC">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-lcWIPUmOAzqLutVABLhPw" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-lcWIPUmOAzqLutVABLhPw">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-dIGc_1vPL5puhY2GCmiRo" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-dIGc_1vPL5puhY2GCmiRo">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-7vhfq9aZsLXsm-qo1CVzj" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-7vhfq9aZsLXsm-qo1CVzj">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">YYYY-DDD</code> Year Day</td> <td><svg class="z-icon" aria-labelledby="fa11-text-MQ1ThqhOpK6XwFl0zgv-u" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-MQ1ThqhOpK6XwFl0zgv-u">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Oh-YORmzoeB3VybcyJ8aE" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-Oh-YORmzoeB3VybcyJ8aE">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Ap4L2Pp6NULx_bJ69Ef3P" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Ap4L2Pp6NULx_bJ69Ef3P">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-XuqEq2P5gIzsAWGVeBTUK" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-XuqEq2P5gIzsAWGVeBTUK">Unsupported</title></svg></td> </tr> <tr> <td class="no"><code class="nowrap">HH:II</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-o96W1HyOF5nqYSZ4ZhwcX" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-o96W1HyOF5nqYSZ4ZhwcX">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-zTx7UNNDwcKnqoZxmUJR8" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-zTx7UNNDwcKnqoZxmUJR8">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-s6evZzt9ZHSbg0bk-9xRa" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-s6evZzt9ZHSbg0bk-9xRa">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-fG1UcqYDZCYROIGd3iVZy" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-fG1UcqYDZCYROIGd3iVZy">Unsupported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SSZ</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-4oM4oqtowusYsV0QIkW9k" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-4oM4oqtowusYsV0QIkW9k">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-EquzECqMTzP2QEmLumHRz" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-EquzECqMTzP2QEmLumHRz">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-_sfnvS97y3o5pqCTA4wQH" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-_sfnvS97y3o5pqCTA4wQH">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-cGiMoPyYcfLHUABLBpspG" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-cGiMoPyYcfLHUABLBpspG">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS±00</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-m3N96kYOFBs50jRna6ByT" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-m3N96kYOFBs50jRna6ByT">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-hBeIC_ehSTlRan82GS6N6" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-hBeIC_ehSTlRan82GS6N6">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-fjeLS3jCnvneflNpJNuG1" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-fjeLS3jCnvneflNpJNuG1">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-Wo2EUrSMGxtIxBT5ZH_6k" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-Wo2EUrSMGxtIxBT5ZH_6k">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS±00:00</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-9Q4u1N9o7PLCi2PBpdRo3" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-9Q4u1N9o7PLCi2PBpdRo3">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-VHv-QWKgSVi9Cb3Rv-CP_" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-VHv-QWKgSVi9Cb3Rv-CP_">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-w4Iu7RZFjw9CbRw4_mqG1" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-w4Iu7RZFjw9CbRw4_mqG1">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-GdheqH4n90wllmb7u0wOD" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-GdheqH4n90wllmb7u0wOD">Supported</title></svg></td> </tr> <tr> <td><code class="nowrap">YYYY-MM-DDTHH:II:SS±0000</code></td> <td><svg class="z-icon" aria-labelledby="fa11-text-bOqIINMEFfc1vRBOXDcEb" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-bOqIINMEFfc1vRBOXDcEb">Unsupported</title></svg><svg class="z-icon" aria-labelledby="fa11-text-h93px9Wx7rnr0wp7Iy4pU" role="img"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use><title id="fa11-text-h93px9Wx7rnr0wp7Iy4pU">Face looking surprised</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-ZrLUItPWn4bvA8idZDbmk" role="img"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use><title id="fa11-text-ZrLUItPWn4bvA8idZDbmk">Unsupported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-SUF_wNBZW-nOl6g-VD0uI" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-SUF_wNBZW-nOl6g-VD0uI">Supported</title></svg></td> <td><svg class="z-icon" aria-labelledby="fa11-text-kJEfvI7jkopB1-UFrnLs0" role="img"><use href="#fas-fa-check" xlink:href="#fas-fa-check"></use><title id="fa11-text-kJEfvI7jkopB1-UFrnLs0">Supported</title></svg></td> </tr> </tbody> </table> <dl class="flex"> <dt><svg class="z-icon"><use href="#fas-fa-circle-xmark" xlink:href="#fas-fa-circle-xmark"></use></svg></dt> <dd>Unsupported</dd> <dt><svg class="z-icon"><use href="#fas-fa-square-xmark" xlink:href="#fas-fa-square-xmark"></use></svg></dt> <dd>Inaccurate parsing</dd> <dt><svg class="z-icon"><use href="#far-fa-face-surprise" xlink:href="#far-fa-face-surprise"></use></svg><span class="a11y-only">Face looking surprised</span></dt> <dd>Surprising (to me)</dd> </dl> <p><em>* Note that <code>Date.parse</code> results may be browser/runtime dependent. The results above were generated from Node.js.</em></p> <h2 id="a-new-challenger-appears">A new challenger appears</h2> <p>It is with a little trepidation that I have shipped <code>@11ty/parse-date-strings</code>, a new RFC 9557 compatible date parsing library that Eleventy will use moving forward.</p> <p>The support table of this library matches the RFC 9557 column documented above. It’s focused on <em>parsing only</em> and our full test suite compares outputs with both the upcoming Temporal API and existing Luxon output.</p> <p>While there are a few breaking changes when compared with Luxon output (noted above), this swap will ultimately prepare us for native Temporal support <em>without breaking changes later</em>!</p> <table> <thead> <tr> <th>Package</th> <th>Type</th> <th>Disk Size</th> <th><a href="https://bundlephobia.com/">Bundle Size</a></th> </tr> </thead> <tbody> <tr> <td><code><a href="https://www.npmjs.com/package/@11ty/parse-date-strings">@11ty/parse-date-strings</a>@2.0.4</code></td> <td>ESM</td> <td><code>6.69 kB</code></td> <td><code>2.3 kB</code> (min)</td> </tr> </tbody> </table> <p>This library saves ~230 kB in the upcoming <code>@11ty/client</code> bundle. It should also allow <code>@11ty/eleventy</code> <code>node_modules</code> install weight to drop from 21.3 MB to 16.6 MB. <em>(Some folks might remember when <a href="https://github.com/11ty/eleventy/releases/tag/v2.0.0"><code>@11ty/eleventy@1</code> weighed in at 155 MB</a>!)</em></p> <h2 id="late-additions">Late Additions</h2> <p>For posterity, here are a few other alternative date libraries / Temporal polyfills that I think are worth mentioning (and might help you in different ways on your own date parsing journey):</p> <table> <thead> <tr> <th>Package</th> <th>Type</th> <th>Disk Size</th> <th><a href="https://bundlephobia.com/">Bundle Size</a></th> </tr> </thead> <tbody> <tr> <td><code><a href="https://www.npmjs.com/package/@js-temporal/polyfill">@js-temporal/polyfill</a>@0.3.0</code></td> <td>Dual</td> <td><code>2.98 MB</code></td> <td><code>186.5 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/temporal-polyfill">temporal-polyfill</a>@0.3.0</code></td> <td>Dual</td> <td><code>551 kB</code></td> <td><code>56.3 kB</code> (min)</td> </tr> <tr> <td><code><a href="https://www.npmjs.com/package/@formkit/tempo">@formkit/tempo</a>@0.1.2</code></td> <td>Dual</td> <td><code>501 kB</code></td> <td><code>17.3 kB</code> (min)</td> </tr> </tbody> </table> </content>
</entry>
<entry>
<title>How to import() a JavaScript String</title>
<link href="https://www.zachleat.com/web/dynamic-import/"/>
<updated>2025-06-09T05:00:00Z</updated>
<id>https://www.zachleat.com/web/dynamic-import/</id>
<content type="html"><p>You can use arbitrary <a href="https://www.11ty.dev/docs/data-frontmatter/#java-script-front-matter">JavaScript in front matter in Eleventy project files</a> (via the <code>js</code> type).</p> <p>Historically Eleventy has made use of the <code>node-retrieve-globals</code> package to accomplish this, which was a nightmarish conglomeration of a few different Node.js approaches (each with different advantages and drawbacks).</p> <p><em>Related research: <a href="https://github.com/zachleat/javascript-eval-modules">Dynamic Script Evaluation in JavaScript</a></em></p> <p>The biggest drawbacks to <code>node-retrieve-globals</code> include:</p> <ul> <li>CommonJS code only (even in <a href="https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/">a <code>require(esm)</code> world</a>). While dynamic <code>import()</code> works, <code>import</code> and <code>export</code> do not. Top level <code>await</code> is emulated typically by wrapping your code with an <code>async</code> function wrapper.</li> <li>It uses Node.js only approaches not viable as Eleventy works to deliver <a href="https://fediverse.zachleat.com/@zachleat/114434795493653605">a library</a> that is <a href="https://neighborhood.11ty.dev/@11ty/114519676689929120">browser-friendly</a>.</li> </ul> <p>Regardless, this abomination was a necessary evil due to the experimental status of Node.js’ <a href="https://nodejs.org/docs/latest/api/vm.html#class-vmmodule"><code>vm.Module</code></a> (since Node v12, ~2019), the ESM-friendly partner to CommonJS-compatible <code>vm.Script</code>. I’d still love to see <code>vm.Module</code> achieve a level of stability, but I digress.</p> <h2 id="new-best-friend-is-import">New Best Friend is <code>import()</code></h2> <p>Moving forward, I’ve been having success from a much lighter approach using <code>import()</code>, described in <a href="https://2ality.com/2019/10/eval-via-import.html"><em>Evaluating JavaScript code via import()</em> by Dr. Axel Rauschmayer</a>. It looks something like this:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">let</span> code <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">export default function() {}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">let</span> u <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data:text/javascript;charset=utf-8,</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>code<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">let</span> mod <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Newer runtimes with <code>Blob</code> support might look like this (<a href="https://github.com/dbushell/dinossr/blob/f555a4231c230aebc563194fc88778eb58270879/src/bundle/import.ts#L13-L16">example from David Bushell</a>):</p> <pre class="language-js"><code class="language-js"><span class="token keyword">let</span> code <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">export default function() {}</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">let</span> blob <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Blob</span><span class="token punctuation">(</span><span class="token punctuation">[</span>code<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">"text/javascript"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> u <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">createObjectURL</span><span class="token punctuation">(</span>blob<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> mod <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token function">revokeObjectURL</span><span class="token punctuation">(</span>u<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <h3 id="limitations">Limitations</h3> <ol> <li>Importing a Blob of code does <em>not</em> work in Node.js (as of v24), despite Node having support for Blob in v18 and newer. <blockquote> <p>Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. Received protocol 'blob:'</p> </blockquote> </li> <li><code>import.meta.url</code> just points to the parent <code>data:</code> or <code>blob:</code>, which isn’t super helpful in script.</li> <li>No import of relative references, even if you’ve mapped it to a full path via an Import Map. <ul> <li>e.g. <code>import './relative.js'</code> <blockquote> <p>TypeError: Failed to resolve module specifier ./relative.js: Invalid relative url or base scheme isn't hierarchical.</p> </blockquote> </li> </ul> </li> <li>No import of bare references. These <em>can</em> be remapped via Import Maps. <ul> <li>e.g. <code>import 'barename'</code> <blockquote> <p>TypeError: Failed to resolve module specifier &quot;barename&quot;. Relative references must start with either &quot;/&quot;, &quot;./&quot;, or &quot;../&quot;.</p> </blockquote> </li> </ul> </li> </ol> <p>Though interestingly, Node.js <em>will</em> let you import builtins e.g. <code>import 'node:fs'</code>.</p> <h2 id="enter-import-module-string">Enter <code>import-module-string</code></h2> <p>I’ve worked around the above limitations and packaged this code up into <code>import-module-string</code>, a package that <em>could</em> be described as a super lightweight runtime-independent (server or client) JavaScript bundler.</p> <ul> <li><a href="https://github.com/zachleat/import-module-string"><code>import-module-string</code> on GitHub</a></li> <li><a href="https://www.npmjs.com/package/import-module-string"><code>import-module-string</code> on npm</a></li> </ul> <p>I was able to repurpose <a href="https://www.zachleat.com/web/esm-import-transformer/">a package I created in June 2022</a> to help here: <a href="https://github.com/zachleat/esm-import-transformer"><code>esm-import-transformer</code></a> recursively preprocesses and transform imports to remap them to <code>Blob</code> URLs (falling back to <code>data:</code> when a feature test determines Blob doesn’t work).</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> importFromString <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"import-module-string"</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">importFromString</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">import num from "./relative.js"; export const c = num;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Where <code>relative.js</code> contains <code>export default 3;</code>, the above code becomes (example from Node.js):</p> <pre class="language-js"><code class="language-js"><span class="token keyword">await</span> <span class="token function">importFromString</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">import num from "data:text/javascript;charset=utf-8,export%20default%203%3B"; export const c = num;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Which returns:</p> <pre class="language-js"><code class="language-js"><span class="token punctuation">{</span> <span class="token literal-property property">c</span><span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span></code></pre> <p>This transformation happens recursively for all imports (even imports in imports) with very little ceremony.</p> <p>When you’ve added a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap"><code>&lt;script type=&quot;importmap&quot;&gt;</code> Import Map</a> to your HTML, the script will use <code>import.meta.resolve</code> to use the Import Map when resolving module targets.</p> <h3 id="even-more-features">Even more features</h3> <p>A few more features for this new package:</p> <ul> <li>Extremely limited dependency footprint, only 3 dependencies total: <code>acorn</code>, <code>acorn-walk</code>, and <code>esm-import-transformer</code>.</li> <li>Multi-runtime: tested with Node (18+), some limited testing in Deno, Chromium, Firefox, and WebKit. <ul> <li>This was my first time using <a href="https://vitest.dev/">Vitest</a> and it worked pretty well! I only hit one snag trying to <a href="https://github.com/vitest-dev/vitest/issues/6953">test <code>import.meta.resolve</code></a>.</li> </ul> </li> <li>Supports top-level <code>async</code>/<code>await</code> (as expected in ES modules)</li> <li>If you use <code>export</code>, the package uses your exports to determine what it returns. If there is no <code>export</code> in play, it implicitly exports all globals (via <code>var</code>, <code>let</code>, <code>const</code>, <code>function</code>, <code>Array</code> or <code>Object</code> destructuring assignment, <code>import</code> specifiers, etc), emulating the behavior in <code>node-retrieve-globals</code>. You can disable implicit exports using <code>implicitExports: false</code>.</li> <li>Emulates <code>import.meta.url</code> when the <code>filePath</code> option is supplied</li> <li><code>addRequire</code> option adds support for <code>require()</code> (this feature is exclusive to server runtimes)</li> <li>Supports a <code>data</code> object to pass in your own global variables to the script. These must be <code>JSON.stringify</code> friendly, though this restriction could be relaxed with more serialization options later.</li> <li>When running in-browser, each script is subject to URL content size maximums: Chrome <code>512MB</code>, Safari <code>2048MB</code>, Firefox <code>512MB</code>, Firefox prior to v137 <code>32MB</code>.</li> </ul> <p>As always with dynamic script execution, do not use this mechanism to run code that is untrusted (<em>especially</em> when running in-browser on a domain with privileged access to secure information like authentication tokens). Make sure you sandbox appropriately!</p> </content>
</entry>
<entry>
<title>line-numbers Web Component</title>
<link href="https://www.zachleat.com/web/line-numbers/"/>
<updated>2025-06-05T05:00:00Z</updated>
<id>https://www.zachleat.com/web/line-numbers/</id>
<content type="html"><p><code>&lt;line-numbers&gt;</code> is a web component to add line numbers alongside <code>&lt;pre&gt;</code> or <code>&lt;textarea&gt;</code> elements.</p> <ul> <li><a href="https://zachleat.github.io/line-numbers/demo.html"><strong>Demo</strong></a></li> <li><a href="https://www.npmjs.com/package/@zachleat/line-numbers">npm package</a></li> <li><a href="https://github.com/zachleat/line-numbers">GitHub repository</a></li> </ul> <p>Install:</p> <pre class="language-sh"><code class="language-sh"><span class="token function">npm</span> <span class="token function">install</span> @zachleat/line-numbers</code></pre> <h2 id="features">Features</h2> <ul> <li><code>&lt;pre&gt;</code> supported</li> <li><code>&lt;textarea&gt;</code> supported (even when adding or removing lines)</li> <li>CSS <code>overflow</code> supported (with obtrusive/visible or nonobtrusive scrollbars)</li> <li>Numbers are excluded from content flow (not selectable, important for copy paste components!)</li> <li>Use <em>any</em> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/counter#counter-style">CSS counter style</a> via custom property <code>--uln-number-type</code></li> <li>Change the starting index for counter via (<code>&lt;line-numbers start=&quot;999&quot;&gt;</code>)</li> <li>Numbers are unobtrusive by default to reduce layout shift (opt-in to obtrusive behavior via <code>&lt;line-numbers obtrusive&gt;</code>)</li> </ul> <h3 id="limitations">Limitations</h3> <p>Trying to keep this one as simple as possible, so please note the following:</p> <ul> <li>Line wrapping is <strong>not</strong> supported (<code>white-space: pre</code> or <code>white-space: nowrap</code> only, and this is enforced by the component)</li> <li>Elements using <code>contenteditable</code> are <strong>not</strong> supported</li> </ul> </content>
</entry>
<entry>
<title>Check the speedometer on the brand new Blog Awesome (now with 11ty)</title>
<link href="https://www.zachleat.com/web/blog-awesome-move/"/>
<updated>2025-03-28T05:00:00Z</updated>
<id>https://www.zachleat.com/web/blog-awesome-move/</id>
<content type="html"><h2 id="related">Related</h2> <ul> <li><a href="https://www.zachleat.com/web/blog-awesome/">Video: <em>11ty Meetup: Blog Awesome from WordPress to Eleventy</em></a></li> </ul> </content>
</entry>
<entry>
<title>11ty Meetup: Blog Awesome from WordPress to Eleventy</title>
<link href="https://www.zachleat.com/web/blog-awesome/"/>
<updated>2025-03-20T05:00:00Z</updated>
<id>https://www.zachleat.com/web/blog-awesome/</id>
<content type="html"><div><youtube-lite-player> <script type="module" src="https://www.zachleat.com/static/browser-window.js"></script> <browser-window mode="dark" flush="" icon="" url="https://youtube.com/watch?v=O89QIruTink" shadow=""> <is-land on:visible="" class="fluid-width-video-wrapper"> <lite-youtube videoid="O89QIruTink" js-api="" playlabel="Play: 11ty Meetup: Blog Awesome from WordPress to Eleventy" style="background-image: var(--yt-poster-img-url); --yt-poster-img-url-lazy: url('https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DO89QIruTink/auto/jpeg/')"></lite-youtube> <template data-island="once"> <style> lite-youtube { max-width: 100% !important; background-size: cover; } /* Plugin bug: clicking the red youtube play icon in the center would navigate to youtube.com */ lite-youtube:defined .lty-playbtn { pointer-events: none; } </style> <link rel="stylesheet" href="https://www.zachleat.com/static/lite-yt-embed.css"> <script type="module" src="https://www.zachleat.com/static/lite-yt-embed.js"></script> </template> </is-land> </browser-window> <youtube-link label="11ty Meetup: Blog Awesome from WordPress to Eleventy" href="https://youtube.com/watch?v=O89QIruTink"> <a href="https://youtube.com/watch?v=O89QIruTink" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>11ty Meetup: Blog Awesome from WordPress to Eleventy</em></a></youtube-link></youtube-lite-player></div> <ul> <li><a href="https://11tymeetup.dev/events/ep-22-umami-analytics-and-blog-awesome/">Event page on 11tymeetup.dev</a></li> </ul> </content>
</entry>
<entry>
<title>Extract Colors from an Image for CSS Themes</title>
<link href="https://www.zachleat.com/web/extract-colors/"/>
<updated>2025-02-27T06:00:00Z</updated>
<id>https://www.zachleat.com/web/extract-colors/</id>
<content type="html"><p>Working through the migration of Blog Awesome from WordPress to Eleventy, I encountered an interesting problem: each blog post was seeded with a featured image at the top, though the image did not cover the entirety of the header.</p> <script type="module" src="https://www.zachleat.com/static/browser-window.js"></script> <browser-window shadow="" mode="dark" flush="" style="--bw-background: #62e6be"> <img src="https://www.zachleat.com/img/built/426uQoZ9zm-2042.avif" alt="Screenshot of blog.fontawesome.com with a featured image that says Font Awesome + 11ty" loading="lazy" decoding="async" width="2042" height="1052"> </browser-window> <p>This required a manual process (a WordPress custom field) to specify a theme color to match the background of the image. We can do better!</p> <p>Using a similar layout structure, this what the new design looks like (with a bit better automatic dark/light mode contrast on the flag and text color, too):</p> <browser-window shadow="" mode="dark" flush="" style="--bw-background: #62e6be"> <img src="https://www.zachleat.com/img/built/ZugJXkjVZw-2050.avif" alt="Screenshot of the new blog.fontawesome.com with a featured image that says Font Awesome + 11ty" loading="lazy" decoding="async" width="2050" height="1112"> </browser-window> <p>To accomplish this automation, I turned to a lovely zero-dependency package called <a href="https://www.npmjs.com/package/extract-colors"><code>extract-colors</code></a> from <a href="https://namide.com/"><code>namide.com</code></a>.</p> <p>In all of the eligible blog posts tested, the last color returned in the result from the <code>extract-colors</code> package always matched the background color of the image being sampled.</p> <p><code>extract-colors</code> recommends the use of another package (<a href="https://www.npmjs.com/package/get-pixels"><code>get-pixels</code></a>) to extract pixel data from images but it was no longer maintained so I <a href="https://fediverse.zachleat.com/@zachleat/113947026720491470">forked, updated, and released a Node.js only version of the package</a> to fix some upstream issues.</p> <h2 id="new-11ty-image-color-package">New <code>@11ty/image-color</code> Package</h2> <p>I wired this up with a <a href="https://www.npmjs.com/package/memoize">memoization layer</a>, a <a href="https://www.11ty.dev/docs/plugins/fetch/#manually-store-your-own-data-in-the-cache">disk cache</a>, a <a href="https://www.npmjs.com/package/p-queue">concurrency queue</a>, and integrated it with existing overlapping functionality provided by Eleventy <a href="https://www.11ty.dev/docs/plugins/fetch/">Fetch</a> and <a href="https://www.11ty.dev/docs/plugins/image/">Image</a> utilities for build performance (as well as adding <a href="https://colorjs.io/">Color.js</a> for some color filtering) and packaged this all up for anyone to use at <a href="https://github.com/11ty/image-color"><code>@11ty/image-color</code></a>:</p> <script type="module" src="https://www.zachleat.com/static/browser-window.js"></script> <div><browser-window mode="dark" icon="" url="https://github.com/11ty/image-color" shadow="" flush="" style="--bw-background: oklch(27.806% 0.01169 248.25)"><a href="https://github.com/11ty/image-color" class="favicon-optout"><picture><source type="image/webp" srcset="https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/small/webp/_20250802/ 375w, https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/medium/webp/_20250802/ 650w" sizes="(min-width: 64em) 50vw, 100vw"><img alt="" loading="lazy" decoding="async" class="" eleventy:ignore="" src="https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/small/jpeg/_20250802/" width="650" height="341" srcset="https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/small/jpeg/_20250802/ 375w, https://v1.opengraph.11ty.dev/https%3A%2F%2Fgithub.com%2F11ty%2Fimage-color/medium/jpeg/_20250802/ 650w" sizes="(min-width: 64em) 50vw, 100vw"></picture></a></browser-window></div> <p>To get colors from a local or remote Image in my Eleventy project, I added the following configuration code to my project’s <code>eleventy.config.js</code> file:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> getImageColors <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/image-color"</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addFilter</span><span class="token punctuation">(</span><span class="token string">"getImageColors"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">imageSrc</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">getImageColors</span><span class="token punctuation">(</span>imageSrc<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>The above Blog Awesome example above made good use of the <code>getImageColors</code> filter in a Nunjucks template:</p> <pre class="language-njk"><code class="language-njk"><span class="token delimiter punctuation">{%-</span> <span class="token tag keyword">set</span> <span class="token variable">lastColor</span> <span class="token operator">=</span> <span class="token variable">media</span><span class="token punctuation">.</span><span class="token variable">featuredImage</span> <span class="token operator">|</span> <span class="token variable">getImageColors</span> <span class="token operator">|</span> <span class="token variable">last</span> <span class="token operator">%</span><span class="token punctuation">}</span> <span class="token punctuation">{</span><span class="token operator">%</span> <span class="token keyword">if</span> <span class="token variable">lastColor</span> <span class="token operator">%</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token variable">style</span><span class="token operator">></span> <span class="token variable">header</span> <span class="token punctuation">{</span> <span class="token variable">background</span><span class="token operator">-</span><span class="token variable">color</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token variable">lastColor</span><span class="token punctuation">.</span><span class="token variable">background</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token variable">color</span><span class="token punctuation">:</span> <span class="token punctuation">{</span><span class="token punctuation">{</span> <span class="token variable">lastColor</span><span class="token punctuation">.</span><span class="token variable">foreground</span> <span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">style</span><span class="token operator">></span> <span class="token punctuation">{</span><span class="token operator">%</span> <span class="token variable">endif</span> <span class="token operator">%</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token variable">header</span><span class="token operator">></span>…<span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">header</span><span class="token operator">></span></code></pre> <h2 id="screenshot-borders">Screenshot Borders</h2> <p>For extra funsies I also made use of this functionality on 11ty.dev (<a href="https://www.11ty.dev/#built-with-eleventy">now live on the site</a>) to provide an extra accent border color on screenshot images:</p> <p><img src="https://www.zachleat.com/img/built/dQ1NIyWHpE-1312.avif" alt="A 4×4 matrix of small screenshots of the Built With Eleventy section on the 11ty.dev home page. Each screenshot has a border color that matches the favicon image" loading="lazy" decoding="async" width="1312" height="1458"></p> <p>This example works a little differently: it samples colors from the <a href="https://www.11ty.dev/docs/services/indieweb-avatar/">favicon images</a> of each site as an easy way to guess the site’s theme colors.</p> <p>This package doesn’t take a hard stance on the validity of colors but I did make use of additional filtering to select a nice border color from the list of colors available in each favicon, with the code looking something like this (again, <code>eleventy.config.js</code> Configuration code):</p> <pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> getImageColors <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@11ty/image-color"</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">eleventyConfig</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> eleventyConfig<span class="token punctuation">.</span><span class="token function">addShortcode</span><span class="token punctuation">(</span><span class="token string">"getColorsForUrl"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> avatarUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">https://v1.indieweb-avatar.11ty.dev/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">encodeURIComponent</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">getImageColors</span><span class="token punctuation">(</span>avatarUrl<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token parameter">colors</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Note the map to colorjs props here</span> <span class="token keyword">return</span> colors<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> c<span class="token punctuation">.</span>colorjs<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token parameter">c</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Not too dark, not too light</span> <span class="token keyword">return</span> c<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">></span> <span class="token number">.4</span> <span class="token operator">&amp;&amp;</span> c<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">&lt;=</span> <span class="token number">.95</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>b<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">+</span> b<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>c<span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token punctuation">(</span>a<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>l <span class="token operator">+</span> a<span class="token punctuation">.</span>oklch<span class="token punctuation">.</span>c<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>…which subsequently wound up in a WebC template a little like this:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name"><span class="token namespace">webc:</span>setup</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getPrimaryColorStyle</span><span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> colors <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getColorsForUrl</span><span class="token punctuation">(</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span>colors<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">--card-border-color: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>colors<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token literal-property property">format</span><span class="token operator">:</span> <span class="token string">"hex"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span> <span class="token attr-name">:href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>url<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span> <span class="token attr-name">:style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>getPrimaryColorStyle(url)<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>…<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">></span></span></code></pre> <h2 id="more-open-source">More Open Source</h2> <p>This Blog Awesome migration project (launching soon!) has yielded a few more useful open source utilities to the Eleventy ecosystem, which I encourage you to try out!</p> <ul> <li><a href="https://github.com/11ty/image-color"><code>@11ty/image-color</code></a> (you already read about this here)</li> <li><a href="https://github.com/11ty/eleventy-import"><code>@11ty/import</code></a> (import WordPress posts as Markdown files)</li> <li><a href="https://github.com/11ty/eleventy-plugin-font-awesome"><code>@11ty/font-awesome</code></a> (use zero-JavaScript SVG icons)</li> </ul> </content>
</entry>
<entry>
<title>?nodefine — a pattern to skip Custom Element definitions</title>
<link href="https://www.zachleat.com/web/nodefine/"/>
<updated>2025-02-14T06:00:00Z</updated>
<id>https://www.zachleat.com/web/nodefine/</id>
<content type="html"><p>The following code is a minimum viable custom element:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"nim-ble"</span><span class="token punctuation">,</span> Nimble<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>The <code>define</code> call is typically packaged up in the component code (for ease of use) and not in your application code. I typically adapt this into a static function like so:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">define</span><span class="token punctuation">(</span><span class="token parameter">tagName</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tagName <span class="token operator">||</span> <span class="token string">"nim-ble"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>On first glance, this might not offer much benefit, but depending on the platform features I’m using I may also include a <a href="https://responsivenews.co.uk/post/18948466399/cutting-the-mustard">Cut-the-Mustard style feature test</a> in there:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">define</span><span class="token punctuation">(</span><span class="token parameter">tagName</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Baseline 2020: Chrome 71 Safari 12.1 Firefox 65</span> <span class="token comment">// (extended browser support on top of ESM and Custom Elements)</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token keyword">typeof</span> globalThis <span class="token operator">!==</span> <span class="token string">"undefined"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tagName <span class="token operator">||</span> <span class="token string">"nim-ble"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <h2 id="automatic-definition">Automatic Definition</h2> <p>The biggest drawback I’m struggling with in these patterns is how to allow folks to opt-out of the <code>customElements.define</code> call when they import or bundle the script. Perhaps they might want to use a different tag name (you can only define a <code>class</code> once in the registry), or run some additional advanced configuration before definition.</p> <p>Here’s how you would typically import the script code (or <code>import &quot;./nimble.js&quot;</code> works too):</p> <pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- &lt;nim-ble> is defined for you --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>nimble.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>Here’s a new idea I’m experimenting with to allow opt-out of <code>define()</code> by adding a <code>?nodefine</code> query parameter to the script URL:</p> <pre class="language-html"><code class="language-html"><span class="token comment">&lt;!-- &lt;nim-ble> is *not* defined for you --></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>nimble.js?nodefine<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"some-other-name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>And then in your component code you’ll check for <code>?nodefine</code> before auto defining the custom element:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">class</span> <span class="token class-name">Nimble</span> <span class="token keyword">extends</span> <span class="token class-name">HTMLElement</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token function">define</span><span class="token punctuation">(</span><span class="token parameter">tagName</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span>tagName <span class="token operator">||</span> <span class="token string">"nim-ble"</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// This line is the magic:</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">URL</span><span class="token punctuation">(</span><span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>searchParams<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span><span class="token string">"nodefine"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> window<span class="token punctuation">.</span>Nimble <span class="token operator">=</span> Nimble<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token punctuation">{</span> Nimble <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>This pattern works with <code>import</code> too:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript"> <span class="token comment">// &lt;nim-ble> is *not* defined for you</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> Nimble <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"./nimble.js?nodefine"</span><span class="token punctuation">;</span> Nimble<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"some-other-name"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Or directly:</span> customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"nim-ble"</span><span class="token punctuation">,</span> Nimble<span class="token punctuation">)</span><span class="token punctuation">;</span> </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>This would also work on any Custom Element code adopting this pattern nestled inside a larger bundle:</p> <pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>module<span class="token punctuation">"</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>bundle-398720.js?nodefine<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">></span></span></code></pre> <p>What do y’all think?</p> <h2 id="community-addendums">Community Addendums</h2> <p><em>Updated February 14, 2025</em> I somehow missed these excellent links which cover similar ground:</p> <ul> <li><a href="https://knowler.dev/blog/to-define-custom-elements-or-not-when-distributing-them">Nathan Knowler’s To define custom elements or not when distributing them</a></li> <li><a href="https://til.jakelazaroff.com/html/define-a-custom-element/">Jake Lazaroff’s Define a custom element</a></li> <li><a href="https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/on-demand-definitions.md">Doug Parker’s On-Demand Definitions Protocol in the Web Components Community Group</a></li> </ul> </content>
</entry>
<entry>
<title>Blog Questions Challenge 2025</title>
<link href="https://www.zachleat.com/web/blogging/"/>
<updated>2025-02-01T06:00:00Z</updated>
<id>https://www.zachleat.com/web/blogging/</id>
<content type="html"><p>I was tagged to answer these questions by <a href="https://thoresson.social/@anders/113850471891430310">Anders Thoresson</a> as part of the larger Blog Questions Challenge originally started by <a href="https://blog.avas.space/bear-blog-challenge/">ava</a>.</p> <p>I’ll pass the baton (and a very optional tag to) to <a href="https://lynnandtonic.com/">Lynn Fisher</a>, <a href="https://ryanmulligan.dev/">Ryan Mulligan</a>, and <a href="https://sarajoy.dev/">Sara Joy</a>!</p> <p>Here are some friends that have already participated:</p> <ul> <li><a href="https://ethanmarcotte.com/wrote/blog-questions-challenge/">Ethan Marcotte</a></li> <li><a href="https://bobmonsour.com/blog/blog-questions-challenge/">Bob Monsour</a></li> <li><em>Maybe you?</em> (If you want me to link to your post, send me a <a href="https://www.zachleat.com/about/">DM</a>)</li> </ul> <h2 id="why-did-you-start-blogging">Why did you start blogging?</h2> <p>I originally started blogging on WordPress during the original era of Blogs, circa 2007. Blogging was a thing often mentioned in popular media — everyone had a blog and was writing on their own space, Google was invested in driving traffic to blogs (via search and a quaint little product known as Google Reader). It was a really great time for independent folks to get started and invest in a platform that would pay off both in the short and long-term.</p> <p>I also am staunchly of the belief that people should <em>share what they know</em> — it doesn’t have to be something that is novel or exclusive or even new. Share what you know and we all benefit from it.</p> <p>Unfortunately I also vividly remember a vibe shift when Twitter originally started to get popular and Google Reader was shuttered to make way for <a href="https://en.wikipedia.org/wiki/Google%2B">Google+</a>. Search feels as though it has been (mostly) conquered by bad actors and social media platforms. A similar shift seems to be happening with how information is consumed via Large Language Models (for the worse).</p> <h2 id="what-platform-are-you-using-to-manage-your-blog-and-why">What platform are you using to manage your blog and why?</h2> <p>I created the <a href="https://www.11ty.dev/">Eleventy project</a> to help others create and easily manage content web sites (like blogs), and that’s what I’m using for this site (since 2018). You can read about the early origins of Eleventy on <a href="https://www.zachleat.com/web/introducing-eleventy/"><em>Eleventy, a new Static Site Generator</em></a>.</p> <h2 id="have-you-blogged-on-other-platforms-before">Have you blogged on other platforms before?</h2> <p>I do maintain a <a href="https://www.zachleat.com/about/#site-history">little table of platform history</a> on my about page. I moved from WordPress to Jekyll in 2013 after going through some dark times with WordPress security vulnerabilities and database scaling issues. Updates needed to be applied on WordPress at approximately the same cadence as early Microsoft Windows and it became cumbersome to manage. I did have plenty of experience with PHP and MySQL at the time but didn’t feel as though that work was worth it for what I was getting out of it.</p> <h2 id="how-do-you-write-your-posts">How do you write your posts?</h2> <p>I write my blog posts in my text editor on my computer (currently VS Code). I don’t write posts on my phone, though I do capture ideas in the Apple Notes app for later iteration. I then commit the posts to the git repository (currently on GitHub) and the site is built on Netlify.</p> <h2 id="when-do-you-feel-most-inspired-to-write">When do you feel most inspired to write?</h2> <p>When I’ve built something new, learned something new, or read something new. I love doing deep dives on new topics but those can be very time consuming to write.</p> <h2 id="do-you-publish-immediately-after-writing-or-do-you-let-it-simmer-a-bit-as-a-draft">Do you publish immediately after writing, or do you let it simmer a bit as a draft?</h2> <p>I usually publish immediately and then iterate for a day or two before I share it on socials. If you’re subscribed to my feed you’ll see some of those iterations happen in your reader as I obsess over grammar and copy. This step can only happen after publishing (as issues of this nature are usually invisible to me before publishing).</p> <h2 id="whats-your-favorite-post-on-your-blog">What's your favorite post on your blog?</h2> <p>The most successful blog post I’ve ever done is <a href="https://www.zachleat.com/web/comprehensive-webfonts/"><em>A Comprehensive Guide to Font Loading Strategies</em></a> but it definitely isn’t my <em>favorite</em>.</p> <p>The first one that jumped out at me was this 2017 blog post about anti-aliasing fonts (also known as font smoothing) titled <a href="https://www.zachleat.com/web/font-smooth/"><em>Laissez-faire Font Smoothing and Anti-aliasing</em></a>. I learned a lot writing that one and it was a lot of fun.</p> <h2 id="any-future-plans-for-your-blog">Any future plans for your blog?</h2> <p>Where do I see my blog in 5 years? Hopefully continuing my current speed of ~30 posts per year and going strong — and still on Eleventy, too <svg class="z-icon"><use href="#far-fa-face-kiss" xlink:href="#far-fa-face-kiss"></use></svg>.</p> </content>
</entry>
</feed>