Zach LeathermanA web development blog written by @zachleat.2024-03-18T00:00:00Zhttp://www.zachleat.com/Zach Leathermanzachleat@zachleat.comMechanical Ink: Unpacking the Challenges and Opportunities in Modern Web Development2024-03-18T00:00:00Zhttp://www.zachleat.com/web/mechanical-ink/
<blockquote>
<p>In this episode of the Mechanical Ink podcast, host Schalk Neethling sits down with Zach Leatherman, the creator of #11ty (among other open-source tools) in a conversation that spans the crucial aspects of web development.<br />
They explore the intertwined relationship between web performance and accessibility, emphasizing its global implications. The discussion also highlights the vital role of open source in fostering growth opportunities for junior developers, alongside the potential of web components in the future landscape of web tech.<br />
Challenges surrounding the adoption of PWAs and the impact of tech monopolies on browser policies are thoroughly examined. Through this dialogue, the importance of community support and innovation within the open-source sphere is celebrated, offering valuable insights for both seasoned developers and newcomers to the field.</p>
</blockquote>
<h2 id="listen" tabindex="-1">Listen <a href="https://www.zachleat.com/web/mechanical-ink/#listen" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li>on <a href="https://youtu.be/JoRpztLGyx4">YouTube</a></li>
<li>on <a href="https://open.spotify.com/episode/1sVweBMT3X13cQnn67GuaK?si=daP_MGFPSfCe0G4ZLJCnJg">Spotify</a></li>
<li>on <a href="https://podcasts.apple.com/us/podcast/unpacking-the-challenges-and-opportunities-in-modern/id1657505237?i=1000648278040">Apple Podcasts</a></li>
<li>on <a href="https://podcasts.google.com/feed/aHR0cHM6Ly9hcGkuc3Vic3RhY2suY29tL2ZlZWQvcG9kY2FzdC8xMTk0MTUzL3MvOTA4OTIucnNz/episode/c3Vic3RhY2s6cG9zdDoxNDIzNzMwNDE">Google Podcasts</a></li>
</ul>
<p>or embedded on YouTube:</p>
<div><youtube-lite-player>
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=JoRpztLGyx4" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="JoRpztLGyx4" js-api="" playlabel="Play: Mechanical Ink: Unpacking the Challenges and Opportunities in Modern Web Development" 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%3DJoRpztLGyx4/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="Mechanical Ink: Unpacking the Challenges and Opportunities in Modern Web Development" href="https://youtube.com/watch?v=JoRpztLGyx4"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=JoRpztLGyx4" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Mechanical Ink: Unpacking the Challenges and Opportunities in Modern Web Development</em></a></youtube-link></youtube-lite-player></div>
<h2 id="shared" tabindex="-1">Shared <a href="https://www.zachleat.com/web/mechanical-ink/#shared" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li>on <a href="https://www.linkedin.com/posts/schalkneethling_unpacking-the-challenges-and-opportunities-activity-7171280905548808192-a1AP">LinkedIn</a></li>
<li>on <a href="https://hachyderm.io/@schalkneethling/112059911928992045">Mastodon</a></li>
</ul>
Elevating Video Transcripts as Searchable Content2024-02-27T00:00:00Zhttp://www.zachleat.com/web/transcripts/
<p>I helped work on the <a href="https://jamstack.org/tv/">Jamstack TV project</a> <em>(now dormant with the rest of the <a href="http://jamstack.org/">jamstack.org</a> site, though the feature is still up)</em>, which used a lovely Algolia integration to allow deep searching of video transcripts. It was a really cool feature.</p>
<p>In <a href="https://www.twitch.tv/cloudcannoncms/schedule">The Static Chronicles Twitch stream</a> (<a href="https://www.youtube.com/watch?v=KOyNEEmrvko">episode 1</a> and <a href="https://www.youtube.com/watch?v=MrVMewBq0jE">episode 2</a>), Mike and I explored an 11ty plugin to output the transcript of a YouTube video to allow video transcripts to be embedded into video posts, thus extending some of this feature to work with <a href="https://pagefind.app/">Pagefind</a> (the static search engine).</p>
<p>I’ve wired this up on a few video posts on my site (that I know have higher quality transcripts/captions):</p>
<ul>
<li><a href="https://www.zachleat.com/web/live-editing-cloudcannon/#searchable-transcript">Live Editing an Eleventy Project in CloudCannon with Bookshop</a></li>
<li><a href="https://www.zachleat.com/web/taylor-swift-fansite/#searchable-transcript">Building a multi-language Taylor Swift fan site (10 Minute Version) (Zach's Version)</a></li>
<li><a href="https://www.zachleat.com/web/cloudcannon/#searchable-transcript">Eleventy and CloudCannon: New Best Friends</a></li>
<li><a href="https://www.zachleat.com/web/eleventy-meetup-eleventy-v2/#searchable-transcript">The Eleventy v2.0 Release, a talk at the Eleventy Meetup</a></li>
<li><a href="https://www.zachleat.com/web/what-the-jam#searchable-transcript">Exploring the Bounds of Jamstack on What the Jam</a></li>
</ul>
<p>The really cool part of these demos is that the actively playing video highlights the currently active portion of the transcript—and you can click any portion of the transcript to skip around on the video!</p>
<h2 id="the-pieces" tabindex="-1">The Pieces <a href="https://www.zachleat.com/web/transcripts/#the-pieces" aria-hidden="true" class="direct-link">#</a></h2>
<p>There are a few web components at play here, but I don’t currently have the energy to formally publish them, so I’ll just link them up here:</p>
<ol>
<li>An asynchronous Eleventy <a href="https://github.com/zachleat/zachleat.com/blob/9710cab7726e095857c48034717b17d8015d6ff9/eleventy.config.js#L334-L344"><code>fetchYoutubeTranscript</code> filter</a> to server-fetch the transcript markup (caching it locally for 7 days).</li>
<li>A WebC component (<a href="https://github.com/zachleat/zachleat.com/blob/0204804c99726c6be2f77def7a4995464d6f3e3f/_components/youtube-deep-link.webc"><code>youtube-deep-link.webc</code></a>) enhances the caption content (fetched from the <code>fetchYoutubeTranscript</code> filter) with a <code><youtube-deep-link></code> custom element for timestamped buttons that point to a <code>lite-youtube</code> element.
<ul>
<li>Notably, <a href="https://github.com/paulirish/lite-youtube-embed/pull/164">this required upstream changes to <code>paulirish/lite-youtube-embed</code></a> to access the YouTube Iframe JavaScript API (<em>Update: these changes are available in <code>v0.3.2</code>+</em>). <s>Until those are merged, you can use this drop-in fork on npm <a href="https://www.npmjs.com/package/@zachleat/lite-youtube-embed"><code>@zachleat/lite-youtube-embed</code></a>.</s></li>
</ul>
</li>
<li>A tiny <a href="https://github.com/zachleat/zachleat.com/blob/b496bd2b0e6bba000fecdf36a82e91f97359b39d/static/js/offviewport.js"><code><off-viewport></code> web component</a> to toggle a class when an element is visible in the viewport. I use this to <a href="https://github.com/zachleat/zachleat.com/blob/24a8e346bac9df7bddaab35db303e111073b8fb2/static/defer.scss#L42-L53">put the video in the lower corner of the viewport</a> as you scroll through the transcript. This one is a little tricky to use because it can take you into a rendering loop if you absolute/fixed/sticky the position of the <code>off-viewport</code> host element directly (so, uh—don’t do that).</li>
</ol>
<h2 id="conclusion" tabindex="-1">Conclusion <a href="https://www.zachleat.com/web/transcripts/#conclusion" aria-hidden="true" class="direct-link">#</a></h2>
<p>I’ve been making more and more videos and I <em>really</em> like escalating that content as first-party text on my static site too. Investing in high quality closed captions (as a way to make the videos more accessible) elevates the content and aligns it more closely with my existing stack of tools.</p>
Eight Million npm Downloads for Eleventy2024-02-23T00:00:00Zhttp://www.zachleat.com/web/eight-million/
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script>
<div><browser-window mode="dark" icon="" url="https://www.11ty.dev/blog/eight-million/" shadow="" flush=""><a href="https://www.11ty.dev/blog/eight-million/" class="favicon-optout"><img alt="Screenshot image for https://v1.screenshot.11ty.dev/https%3A%2F%2Fwww.11ty.dev%2Fblog%2Feight-million%2F/opengraph/_x202403_4/" loading="lazy" decoding="async" src="https://v1.screenshot.11ty.dev/https%3A%2F%2Fwww.11ty.dev%2Fblog%2Feight-million%2F/opengraph/_x202403_4/" width="1200" height="630" /></a></browser-window></div>
<p>Read on the Eleventy Blog: <a href="https://www.11ty.dev/blog/eight-million/">Eight Million npm Downloads for Eleventy</a></p>
Panel Discussion: What's next for Jamstack?2024-02-22T00:00:00Zhttp://www.zachleat.com/web/future-of-jamstack-panel/
<p>I <em>really</em> enjoyed this panel. Let’s save the Jamstack community!</p>
<div><youtube-lite-player>
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=Nn0kBdwGa78" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="Nn0kBdwGa78" js-api="" playlabel="Play: Panel Discussion: What's next for Jamstack?" 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%3DNn0kBdwGa78/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="Panel Discussion: What's next for Jamstack?" href="https://youtube.com/watch?v=Nn0kBdwGa78"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=Nn0kBdwGa78" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Panel Discussion: What's next for Jamstack?</em></a></youtube-link></youtube-lite-player></div>
<blockquote>
<p>Dive deep into the heart of Jamstack with our panel discussion featuring some of the most influential voices in the industry: Salma Alam-Naylor, Cassidy Williams, Bryan Robinson, and Zach Leatherman. Recorded live on February 20th, this session explores the bounds of the term, challenges, and opportunities shaping the future of web development through Jamstack.</p>
<p>Panel:<br />
Salma Alam-Naylor: <a href="https://twitter.com/whitep4nth3r">https://twitter.com/whitep4nth3r</a><br />
Cassidy Williams: <a href="https://twitter.com/cassidoo">https://twitter.com/cassidoo</a><br />
Bryan Robinson: <a href="https://twitter.com/brob">https://twitter.com/brob</a><br />
Zach Leatherman: <a href="https://zachleat.com/@zachleat">https://zachleat.com/@zachleat</a><br />
(host) Mike Neumegen: <a href="https://fosstodon.org/@mikeneu">https://fosstodon.org/@mikeneu</a></p>
</blockquote>
<p>Learn more at <a href="https://thefutureofjamstack.org/"><code>thefutureofjamstack.org</code></a>.</p>
<h2 id="related-coverage" tabindex="-1">Related coverage <a href="https://www.zachleat.com/web/future-of-jamstack-panel/#related-coverage" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li><a href="https://thenewstack.io/developer-panel-wants-an-anti-capitalistic-jamstack/">Developer Panel Wants an ‘Anti-Capitalistic Jamstack’</a></li>
<li><a href="https://whitep4nth3r.com/blog/the-future-of-jamstack-is-anti-capitalist/">The future of Jamstack is anti-capitalist</a></li>
</ul>
hypercard Web Component2024-02-21T00:00:00Zhttp://www.zachleat.com/web/hypercard/
<p><code><hyper-card></code> is a web component that adds a three-dimensional hover effect to any arbitrary content. <strong>Full credit to this <a href="https://codepen.io/markmiro/pen/wbqMPa">3D card hover effect CodePen</a> from <a href="https://www.markmiro.com/">Mark Mironyuk</a>.</strong> Used on the registration flow for <a href="https://conf.11ty.dev/"><code>conf.11ty.dev</code></a>.</p>
<ul>
<li><a href="https://zachleat.github.io/hypercard/demo.html">Demo</a></li>
<li><a href="https://conf.11ty.dev/tickets/876cecc5531648eab7137c5f853c7539">Demo on my ticket to the 11ty Conference</a></li>
<li><a href="https://github.com/zachleat/hypercard">Source code on GitHub</a></li>
</ul>
<h2 id="features" tabindex="-1">Features <a href="https://www.zachleat.com/web/hypercard/#features" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li>Respects <code>prefers-reduced-motion</code>.</li>
<li>Customize scale with <code>--hypercard-scale</code> CSS custom property.</li>
</ul>
<h2 id="installation" tabindex="-1">Installation <a href="https://www.zachleat.com/web/hypercard/#installation" aria-hidden="true" class="direct-link">#</a></h2>
<p>You can install via <code>npm</code> (<a href="https://www.npmjs.com/package/@zachleat/hypercard"><code>@zachleat/hypercard</code></a>) or download the <code>hypercard.js</code> JavaScript file manually.</p>
<pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @zachleat/hypercard <span class="token parameter variable">--save</span></code></pre>
<p>Add <code>hypercard.js</code> to your site’s JavaScript assets.</p>
<h2 id="usage" tabindex="-1">Usage <a href="https://www.zachleat.com/web/hypercard/#usage" aria-hidden="true" class="direct-link">#</a></h2>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>hyper-card</span><span class="token punctuation">></span></span>Hello.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>hyper-card</span><span class="token punctuation">></span></span></code></pre>
<h3 id="not-quite-as-big-on-hover" tabindex="-1">Not quite as big on hover <a href="https://www.zachleat.com/web/hypercard/#not-quite-as-big-on-hover" aria-hidden="true" class="direct-link">#</a></h3>
<p>The default value is <code>1.07</code>.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>hyper-card</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--hypercard-scale</span><span class="token punctuation">:</span> 1.03</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>Hello.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>hyper-card</span><span class="token punctuation">></span></span></code></pre>
webcare-webshare Web Component2024-02-20T00:00:00Zhttp://www.zachleat.com/web/webcare-webshare/
<p><code><webcare-webshare></code> is a web component that uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API">Web Share API</a> to share a web site, falling back (on desktop usually) to a copy to clipboard workflow.</p>
<ul>
<li><a href="https://zachleat.github.io/webcare-webshare/demo.html">Demo</a></li>
<li><a href="https://github.com/zachleat/webcare-webshare">Source code on GitHub</a></li>
</ul>
<p>Used on the registration flow for <a href="https://conf.11ty.dev/"><code>conf.11ty.dev</code></a>.</p>
<h2 id="features" tabindex="-1">Features <a href="https://www.zachleat.com/web/webcare-webshare/#features" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li>Defaults to copy URL when Web Share API is not available.</li>
<li>Optionally override with your own copy-able content.</li>
<li>Custom button text when Web Share API is not available.</li>
</ul>
<h2 id="installation" tabindex="-1">Installation <a href="https://www.zachleat.com/web/webcare-webshare/#installation" aria-hidden="true" class="direct-link">#</a></h2>
<p>You can install via <code>npm</code> (<a href="https://www.npmjs.com/package/@zachleat/webcare-webshare"><code>@zachleat/webcare-webshare</code></a>) or download the <code>webcare-webshare.js</code> JavaScript file manually.</p>
<pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @zachleat/webcare-webshare <span class="token parameter variable">--save</span></code></pre>
<p>Add <code>webcare-webshare.js</code> to your site’s JavaScript assets.</p>
<h2 id="usage" tabindex="-1">Usage <a href="https://www.zachleat.com/web/webcare-webshare/#usage" aria-hidden="true" class="direct-link">#</a></h2>
<p>Use <code>share-text</code> and <code>share-url</code> per the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Share_API">Web Share API</a>. The button is un-disabled when initialized.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>webcare-webshare</span> <span class="token attr-name">share-text</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>I am going to the 11ty Conference! #11ty #11tyConf<span class="token punctuation">"</span></span> <span class="token attr-name">share-url</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://conf.11ty.dev/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">disabled</span><span class="token punctuation">></span></span>Share your ticket!<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>webcare-webshare</span><span class="token punctuation">></span></span></code></pre>
<h3 id="custom-button-text" tabindex="-1">Custom button text <a href="https://www.zachleat.com/web/webcare-webshare/#custom-button-text" aria-hidden="true" class="direct-link">#</a></h3>
<p><em>Copy to clipboard</em> workflow only. Use the <code>label-copy</code> (Before) and <code>label-after-copy</code> (After) attributes.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>webcare-webshare</span> <span class="token attr-name">share-text</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>I am going to the 11ty Conference! #11ty #11tyConf<span class="token punctuation">"</span></span> <span class="token attr-name">share-url</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://conf.11ty.dev/<span class="token punctuation">"</span></span> <span class="token attr-name">label-copy</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>📋 Copy your ticket URL<span class="token punctuation">"</span></span> <span class="token attr-name">label-after-copy</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>✅ Copied to Clipboard<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">disabled</span><span class="token punctuation">></span></span>Share your ticket!<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>webcare-webshare</span><span class="token punctuation">></span></span></code></pre>
<h3 id="set-custom-share-content" tabindex="-1">Set custom share content <a href="https://www.zachleat.com/web/webcare-webshare/#set-custom-share-content" aria-hidden="true" class="direct-link">#</a></h3>
<p><em>Copy to clipboard</em> workflow only. Use <code>copy-text</code> to override <code>share-url</code> as the default content that is copied when using Copy to Clipboard.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>webcare-webshare</span> <span class="token attr-name">share-text</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>I am going to the 11ty Conference! #11ty #11tyConf<span class="token punctuation">"</span></span> <span class="token attr-name">share-url</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://conf.11ty.dev/<span class="token punctuation">"</span></span> <span class="token attr-name">copy-text</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>Go to https://conf.11ty.dev/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">disabled</span><span class="token punctuation">></span></span>Share your ticket!<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>webcare-webshare</span><span class="token punctuation">></span></span></code></pre>
throbber Web Component2024-02-19T00:00:00Zhttp://www.zachleat.com/web/throbber/
<p><code><throb-ber></code> is a web component to add a little baby rainbow gradient overlay that shows until all of the images nested inside have finished loading.</p>
<ul>
<li><a href="https://zachleat.github.io/throbber/demo.html">Demo</a></li>
<li><a href="https://github.com/zachleat/throbber">Source code on GitHub</a></li>
</ul>
<p>Works best with expensive dynamically generated images (like from the Eleventy Image Screenshot API). Used on the registration flow for <a href="https://conf.11ty.dev/"><code>conf.11ty.dev</code></a>.</p>
<h2 id="features" tabindex="-1">Features <a href="https://www.zachleat.com/web/throbber/#features" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li>Custom minimum <code>delay</code> before the loading indicator is shown.</li>
<li>Custom loading indicator bar height.</li>
<li>Custom loading indicator gradient.</li>
</ul>
<h2 id="installation" tabindex="-1">Installation <a href="https://www.zachleat.com/web/throbber/#installation" aria-hidden="true" class="direct-link">#</a></h2>
<p>You can install via <code>npm</code> (<a href="https://www.npmjs.com/package/@zachleat/throbber"><code>@zachleat/throbber</code></a>) or download the <code>throbber.js</code> JavaScript file manually.</p>
<pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @zachleat/throbber <span class="token parameter variable">--save</span></code></pre>
<p>Add <code>throbber.js</code> to your site’s JavaScript assets.</p>
<h2 id="usage" tabindex="-1">Usage <a href="https://www.zachleat.com/web/throbber/#usage" aria-hidden="true" class="direct-link">#</a></h2>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>throb-ber</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</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>myimage.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>400<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>throb-ber</span><span class="token punctuation">></span></span></code></pre>
<h3 id="change-minimum-delay" tabindex="-1">Change minimum delay <a href="https://www.zachleat.com/web/throbber/#change-minimum-delay" aria-hidden="true" class="direct-link">#</a></h3>
<p>The minimum time before the loading indicator is shown. <code>delay</code> is in milliseconds (0.5 seconds shown).</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>throb-ber</span> <span class="token attr-name">delay</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>500<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</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>myimage.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>400<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>throb-ber</span><span class="token punctuation">></span></span></code></pre>
<h3 id="customize-appearance" tabindex="-1">Customize appearance <a href="https://www.zachleat.com/web/throbber/#customize-appearance" aria-hidden="true" class="direct-link">#</a></h3>
<p>Dark background while the image is loading, loading indicator fills up the component:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>throb-ber</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">background-color</span><span class="token punctuation">:</span> #666<span class="token punctuation">;</span> <span class="token property">--throbber-height</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span></span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</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>myimage.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>400<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>throb-ber</span><span class="token punctuation">></span></span></code></pre>
<p>Use your own custom gradient:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>throb-ber</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--throbber-image</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span>to right<span class="token punctuation">,</span> white<span class="token punctuation">,</span> rebeccapurple<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">--throbber-opacity</span><span class="token punctuation">:</span> 1</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</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>myimage.png<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token punctuation">"</span></span> <span class="token attr-name">width</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>600<span class="token punctuation">"</span></span> <span class="token attr-name">height</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>400<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>throb-ber</span><span class="token punctuation">></span></span></code></pre>
Lessons learned moving Eleventy from CommonJS to ESM2024-02-07T00:00:00Zhttp://www.zachleat.com/web/eleventy-v3-esm/
<p>This talk was given at <a href="https://www.zachleat.com/web/thejam/">TheJam.dev 2024</a>.</p>
<div><youtube-lite-player>
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=LsN6TBx9Hxo" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="LsN6TBx9Hxo" js-api="" playlabel="Play: Lessons learned moving Eleventy from CommonJS to ESM" 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%3DLsN6TBx9Hxo/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="Lessons learned moving Eleventy from CommonJS to ESM" href="https://youtube.com/watch?v=LsN6TBx9Hxo"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=LsN6TBx9Hxo" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Lessons learned moving Eleventy from CommonJS to ESM</em></a></youtube-link></youtube-lite-player></div>
<p>And the full slide deck is included below:</p>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-rLDj" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/W79kULrGy_-600.avif 600w, https://www.zachleat.com/img/built/W79kULrGy_-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/W79kULrGy_-600.jpeg 600w, https://www.zachleat.com/img/built/W79kULrGy_-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/W79kULrGy_-600.jpeg" alt="11ty v3" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/YwfJh4PnUh-600.avif 600w, https://www.zachleat.com/img/built/YwfJh4PnUh-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/YwfJh4PnUh-600.jpeg 600w, https://www.zachleat.com/img/built/YwfJh4PnUh-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/YwfJh4PnUh-600.jpeg" alt="CommonJS" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/uPtHtw8PbR-600.avif 600w, https://www.zachleat.com/img/built/uPtHtw8PbR-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/uPtHtw8PbR-600.jpeg 600w, https://www.zachleat.com/img/built/uPtHtw8PbR-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/uPtHtw8PbR-600.jpeg" alt="Common, the actor and rapper is shown alongside the JS logo" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/UYLWoh5NMU-600.avif 600w, https://www.zachleat.com/img/built/UYLWoh5NMU-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/UYLWoh5NMU-600.jpeg 600w, https://www.zachleat.com/img/built/UYLWoh5NMU-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/UYLWoh5NMU-600.jpeg" alt="CommonJS is strikethrough, ESM is added" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Rp3y8CTjA8-600.avif 600w, https://www.zachleat.com/img/built/Rp3y8CTjA8-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Rp3y8CTjA8-600.jpeg 600w, https://www.zachleat.com/img/built/Rp3y8CTjA8-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Rp3y8CTjA8-600.jpeg" alt="Regina George from Mean Girls is shown riding in a lunar module and says Get in Loser—We’re using Modules now" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/vRP84Eo5WR-600.avif 600w, https://www.zachleat.com/img/built/vRP84Eo5WR-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/vRP84Eo5WR-600.jpeg 600w, https://www.zachleat.com/img/built/vRP84Eo5WR-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/vRP84Eo5WR-600.jpeg" alt="module.exports = function() {};" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/xDW0S6ST0--600.avif 600w, https://www.zachleat.com/img/built/xDW0S6ST0--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/xDW0S6ST0--600.jpeg 600w, https://www.zachleat.com/img/built/xDW0S6ST0--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/xDW0S6ST0--600.jpeg" alt="export default function() {};" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/RSw_sJZ2n6-600.avif 600w, https://www.zachleat.com/img/built/RSw_sJZ2n6-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/RSw_sJZ2n6-600.jpeg 600w, https://www.zachleat.com/img/built/RSw_sJZ2n6-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/RSw_sJZ2n6-600.jpeg" alt="const render = function() {}; module.exports = { render };" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/-kNRlHqqrN-600.avif 600w, https://www.zachleat.com/img/built/-kNRlHqqrN-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/-kNRlHqqrN-600.jpeg 600w, https://www.zachleat.com/img/built/-kNRlHqqrN-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/-kNRlHqqrN-600.jpeg" alt="const render = function() {}; export { render };" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Nwg96eZj6z-600.avif 600w, https://www.zachleat.com/img/built/Nwg96eZj6z-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Nwg96eZj6z-600.jpeg 600w, https://www.zachleat.com/img/built/Nwg96eZj6z-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Nwg96eZj6z-600.jpeg" alt="A screenshot of the Eleventy core test suite passing on GitHub actions (1048 tests) on Mac/Linux/Windows" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/XZjfFSaYOi-600.avif 600w, https://www.zachleat.com/img/built/XZjfFSaYOi-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/XZjfFSaYOi-600.jpeg 600w, https://www.zachleat.com/img/built/XZjfFSaYOi-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/XZjfFSaYOi-600.jpeg" alt="Ryan Reynolds in medical scrubs asks Why?" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/OJtmbCueN--600.avif 600w, https://www.zachleat.com/img/built/OJtmbCueN--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/OJtmbCueN--600.jpeg 600w, https://www.zachleat.com/img/built/OJtmbCueN--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/OJtmbCueN--600.jpeg" alt="EMCAScript modules are the official standard format to package JavaScript code for reuse. (via TC39 and the node.js documentation)" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/rTAR37y52L-600.avif 600w, https://www.zachleat.com/img/built/rTAR37y52L-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/rTAR37y52L-600.jpeg 600w, https://www.zachleat.com/img/built/rTAR37y52L-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/rTAR37y52L-600.jpeg" alt="A screenshot of the web browser support for EMCAScript 2015 (all green)" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/UBlq424n7n-600.avif 600w, https://www.zachleat.com/img/built/UBlq424n7n-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/UBlq424n7n-600.jpeg 600w, https://www.zachleat.com/img/built/UBlq424n7n-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/UBlq424n7n-600.jpeg" alt="A table showing CommonJS support in Node but missing in Deno and the Browser but ESM supported in all." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/t7D0gt773P-600.avif 600w, https://www.zachleat.com/img/built/t7D0gt773P-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/t7D0gt773P-600.jpeg 600w, https://www.zachleat.com/img/built/t7D0gt773P-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/t7D0gt773P-600.jpeg" alt="Bundlers! Rollup, esbuild, Carabiner, Parcel, Webpack, SWC" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/H-Wez4Glwc-600.avif 600w, https://www.zachleat.com/img/built/H-Wez4Glwc-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/H-Wez4Glwc-600.jpeg 600w, https://www.zachleat.com/img/built/H-Wez4Glwc-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/H-Wez4Glwc-600.jpeg" alt="Actually sorry Carabiner is just the Toblerone logo" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/UBlq424n7n-600.avif 600w, https://www.zachleat.com/img/built/UBlq424n7n-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/UBlq424n7n-600.jpeg 600w, https://www.zachleat.com/img/built/UBlq424n7n-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/UBlq424n7n-600.jpeg" alt="A repeat of the CommonJS/ESM support in Node, Deno, Browser" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/ySD1IO0Uah-600.avif 600w, https://www.zachleat.com/img/built/ySD1IO0Uah-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/ySD1IO0Uah-600.jpeg 600w, https://www.zachleat.com/img/built/ySD1IO0Uah-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/ySD1IO0Uah-600.jpeg" alt="Minimum viable tech stack means we stay as close to the Build/no-build line as possible, avoiding as many dependencies as we can to stick as close to HTML/CSS/JS as we can." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/6ZvKQCGFi3-600.avif 600w, https://www.zachleat.com/img/built/6ZvKQCGFi3-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/6ZvKQCGFi3-600.jpeg 600w, https://www.zachleat.com/img/built/6ZvKQCGFi3-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/6ZvKQCGFi3-600.jpeg" alt="A fake messages conversation about web sites being easier to maintain because they do less!" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/b4lyk2NusV-600.avif 600w, https://www.zachleat.com/img/built/b4lyk2NusV-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/b4lyk2NusV-600.jpeg 600w, https://www.zachleat.com/img/built/b4lyk2NusV-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/b4lyk2NusV-600.jpeg" alt="Eleventy v1 was 72.7MB of node_modules, v2 was 35.2MB" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/cJ8cizxjBh-600.avif 600w, https://www.zachleat.com/img/built/cJ8cizxjBh-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/cJ8cizxjBh-600.jpeg 600w, https://www.zachleat.com/img/built/cJ8cizxjBh-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/cJ8cizxjBh-600.jpeg" alt="Eleventy v3 is currently 20.2MB of node_modules" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/d371MuNxO4-600.avif 600w, https://www.zachleat.com/img/built/d371MuNxO4-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/d371MuNxO4-600.jpeg 600w, https://www.zachleat.com/img/built/d371MuNxO4-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/d371MuNxO4-600.jpeg" alt="For comparison, rollup is 4.8MB, webpack is 25.9MB, parcel is 281.9MB, esbuild is 9.9MB and swc is 47MB." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/rKrywOQXlx-600.avif 600w, https://www.zachleat.com/img/built/rKrywOQXlx-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/rKrywOQXlx-600.jpeg 600w, https://www.zachleat.com/img/built/rKrywOQXlx-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/rKrywOQXlx-600.jpeg" alt="Gatsby is 523.8MB" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/X7GkEEtgHT-600.avif 600w, https://www.zachleat.com/img/built/X7GkEEtgHT-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/X7GkEEtgHT-600.jpeg 600w, https://www.zachleat.com/img/built/X7GkEEtgHT-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/X7GkEEtgHT-600.jpeg" alt="Eleventy v3 is dropping some node_modules weight through our community survey results. Handlebars, Pug, Mustache, EJS, and Haml are all unpopular template syntaxes and we’ll move these into plugin-land." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/hBP4EPmmOu-600.avif 600w, https://www.zachleat.com/img/built/hBP4EPmmOu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/hBP4EPmmOu-600.jpeg 600w, https://www.zachleat.com/img/built/hBP4EPmmOu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/hBP4EPmmOu-600.jpeg" alt="Requirements" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/SpXDS5ongP-600.avif 600w, https://www.zachleat.com/img/built/SpXDS5ongP-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/SpXDS5ongP-600.jpeg 600w, https://www.zachleat.com/img/built/SpXDS5ongP-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/SpXDS5ongP-600.jpeg" alt="Node.js 10 ESM was experimental but it’s stable in 12" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/kH0Grir0fQ-600.avif 600w, https://www.zachleat.com/img/built/kH0Grir0fQ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/kH0Grir0fQ-600.jpeg 600w, https://www.zachleat.com/img/built/kH0Grir0fQ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/kH0Grir0fQ-600.jpeg" alt="11ty v1 required Node.js 12+, v2 required 14+, v3 requires 18+" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/ud39Bzt0h8-600.avif 600w, https://www.zachleat.com/img/built/ud39Bzt0h8-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/ud39Bzt0h8-600.jpeg 600w, https://www.zachleat.com/img/built/ud39Bzt0h8-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/ud39Bzt0h8-600.jpeg" alt="We could have done this in 2022, but we didn’t 😅" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/7Bfva43YJe-600.avif 600w, https://www.zachleat.com/img/built/7Bfva43YJe-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/7Bfva43YJe-600.jpeg 600w, https://www.zachleat.com/img/built/7Bfva43YJe-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/7Bfva43YJe-600.jpeg" alt="Compatibility" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/J2hRdQpah9-600.avif 600w, https://www.zachleat.com/img/built/J2hRdQpah9-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/J2hRdQpah9-600.jpeg 600w, https://www.zachleat.com/img/built/J2hRdQpah9-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/J2hRdQpah9-600.jpeg" alt="CommonJS Project compatibility, config files, data files, template files, third party plugins." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/4WY8WF7IfW-600.avif 600w, https://www.zachleat.com/img/built/4WY8WF7IfW-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/4WY8WF7IfW-600.jpeg 600w, https://www.zachleat.com/img/built/4WY8WF7IfW-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/4WY8WF7IfW-600.jpeg" alt="The biggest limitation with a CommonJS project is that you can’t require an ESM dependency (which Eleventy is, now)" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/GPUTM3QmUI-600.avif 600w, https://www.zachleat.com/img/built/GPUTM3QmUI-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/GPUTM3QmUI-600.jpeg 600w, https://www.zachleat.com/img/built/GPUTM3QmUI-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/GPUTM3QmUI-600.jpeg" alt="Compatibility overview." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/1t-wq_elSa-600.avif 600w, https://www.zachleat.com/img/built/1t-wq_elSa-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/1t-wq_elSa-600.jpeg 600w, https://www.zachleat.com/img/built/1t-wq_elSa-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/1t-wq_elSa-600.jpeg" alt="CommonJS Upgrade Walkthrough in Two Steps" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/ky7w67bqYe-600.avif 600w, https://www.zachleat.com/img/built/ky7w67bqYe-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/ky7w67bqYe-600.jpeg 600w, https://www.zachleat.com/img/built/ky7w67bqYe-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/ky7w67bqYe-600.jpeg" alt="Step one—npm install @11ty/eleventy@canary --save-exact" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/wmdEc7bFbc-600.avif 600w, https://www.zachleat.com/img/built/wmdEc7bFbc-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/wmdEc7bFbc-600.jpeg 600w, https://www.zachleat.com/img/built/wmdEc7bFbc-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/wmdEc7bFbc-600.jpeg" alt="You cannot require("@11ty/eleventy") now so using bundled Eleventy plugins need to change." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/-wTN6oVUJR-600.avif 600w, https://www.zachleat.com/img/built/-wTN6oVUJR-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/-wTN6oVUJR-600.jpeg 600w, https://www.zachleat.com/img/built/-wTN6oVUJR-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/-wTN6oVUJR-600.jpeg" alt="Change your config callback to async and use await import("@11ty/eleventy") to include them instead." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/yXEY77z2D7-600.avif 600w, https://www.zachleat.com/img/built/yXEY77z2D7-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/yXEY77z2D7-600.jpeg 600w, https://www.zachleat.com/img/built/yXEY77z2D7-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/yXEY77z2D7-600.jpeg" alt="Done." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/TLUxQ-hScr-600.avif 600w, https://www.zachleat.com/img/built/TLUxQ-hScr-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/TLUxQ-hScr-600.jpeg 600w, https://www.zachleat.com/img/built/TLUxQ-hScr-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/TLUxQ-hScr-600.jpeg" alt="Now let’s talk about Third-party plugins" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/zMyhRuw7QK-600.avif 600w, https://www.zachleat.com/img/built/zMyhRuw7QK-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/zMyhRuw7QK-600.jpeg 600w, https://www.zachleat.com/img/built/zMyhRuw7QK-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/zMyhRuw7QK-600.jpeg" alt="Existing third-party plugins are CommonJS—keep these as-is." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/LTRcPJvYmP-600.avif 600w, https://www.zachleat.com/img/built/LTRcPJvYmP-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/LTRcPJvYmP-600.jpeg 600w, https://www.zachleat.com/img/built/LTRcPJvYmP-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/LTRcPJvYmP-600.jpeg" alt="eleventyConfig.addPlugin(async function() {}) now supported in Eleventy v3" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Nuip8Udmf2-600.avif 600w, https://www.zachleat.com/img/built/Nuip8Udmf2-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Nuip8Udmf2-600.jpeg 600w, https://www.zachleat.com/img/built/Nuip8Udmf2-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Nuip8Udmf2-600.jpeg" alt="Any new ESM Eleventy Plugins are only compatible with Eleventy v3" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Khg6ADqpi3-600.avif 600w, https://www.zachleat.com/img/built/Khg6ADqpi3-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Khg6ADqpi3-600.jpeg 600w, https://www.zachleat.com/img/built/Khg6ADqpi3-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Khg6ADqpi3-600.jpeg" alt="ESM Eleventy plugins require an `async` config callback or an ESM configuration file—both of which are only supported in Eleventy v3" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/uYBzWkDMUo-600.avif 600w, https://www.zachleat.com/img/built/uYBzWkDMUo-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/uYBzWkDMUo-600.jpeg 600w, https://www.zachleat.com/img/built/uYBzWkDMUo-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/uYBzWkDMUo-600.jpeg" alt="Our compatibility checklist for CommonJS projects is complete." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/BwEXxAUT9U-600.avif 600w, https://www.zachleat.com/img/built/BwEXxAUT9U-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/BwEXxAUT9U-600.jpeg 600w, https://www.zachleat.com/img/built/BwEXxAUT9U-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/BwEXxAUT9U-600.jpeg" alt="Next, ESM application code." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/LC6-Gl1Vda-600.avif 600w, https://www.zachleat.com/img/built/LC6-Gl1Vda-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/LC6-Gl1Vda-600.jpeg 600w, https://www.zachleat.com/img/built/LC6-Gl1Vda-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/LC6-Gl1Vda-600.jpeg" alt="A similar checklist of config files, data files, templates, and plugins." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/pkFH5YLpaT-600.avif 600w, https://www.zachleat.com/img/built/pkFH5YLpaT-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/pkFH5YLpaT-600.jpeg 600w, https://www.zachleat.com/img/built/pkFH5YLpaT-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/pkFH5YLpaT-600.jpeg" alt="ESM Upgrade Walkthrough in Three Steps" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/7BHGCxK-GI-600.avif 600w, https://www.zachleat.com/img/built/7BHGCxK-GI-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/7BHGCxK-GI-600.jpeg 600w, https://www.zachleat.com/img/built/7BHGCxK-GI-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/7BHGCxK-GI-600.jpeg" alt="Step one npm install @11ty/eleventy@canary --save-exact, Step two add type: 'module' to your package.json. All .js files in your project are now assumed to be ESM." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/eZWGFD7m1i-600.avif 600w, https://www.zachleat.com/img/built/eZWGFD7m1i-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/eZWGFD7m1i-600.jpeg 600w, https://www.zachleat.com/img/built/eZWGFD7m1i-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/eZWGFD7m1i-600.jpeg" alt="Step three rename all .js app files to .cjs (and convert these over to ESM as-needed or not at all)" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/yXEY77z2D7-600.avif 600w, https://www.zachleat.com/img/built/yXEY77z2D7-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/yXEY77z2D7-600.jpeg 600w, https://www.zachleat.com/img/built/yXEY77z2D7-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/yXEY77z2D7-600.jpeg" alt="Done." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/rgjB_W-qhD-600.avif 600w, https://www.zachleat.com/img/built/rgjB_W-qhD-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/rgjB_W-qhD-600.jpeg 600w, https://www.zachleat.com/img/built/rgjB_W-qhD-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/rgjB_W-qhD-600.jpeg" alt="Let’s convert our configuration code to ESM" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/bHqqBhB2vY-600.avif 600w, https://www.zachleat.com/img/built/bHqqBhB2vY-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/bHqqBhB2vY-600.jpeg 600w, https://www.zachleat.com/img/built/bHqqBhB2vY-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/bHqqBhB2vY-600.jpeg" alt="Use import {} from "@11ty/eleventy" as normal. Change module.exports = to export.default" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/5YjcmEjRgq-600.avif 600w, https://www.zachleat.com/img/built/5YjcmEjRgq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/5YjcmEjRgq-600.jpeg 600w, https://www.zachleat.com/img/built/5YjcmEjRgq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/5YjcmEjRgq-600.jpeg" alt="Change data files from module.exports = to export.default" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/eqDlJ9cltT-600.avif 600w, https://www.zachleat.com/img/built/eqDlJ9cltT-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/eqDlJ9cltT-600.jpeg 600w, https://www.zachleat.com/img/built/eqDlJ9cltT-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/eqDlJ9cltT-600.jpeg" alt="You can used named exports too. export { key }" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/0-Gw1Ib4hc-600.avif 600w, https://www.zachleat.com/img/built/0-Gw1Ib4hc-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/0-Gw1Ib4hc-600.jpeg 600w, https://www.zachleat.com/img/built/0-Gw1Ib4hc-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/0-Gw1Ib4hc-600.jpeg" alt="export default { data, render } in an index.11ty.js template." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/kHtPJ6qcjO-600.avif 600w, https://www.zachleat.com/img/built/kHtPJ6qcjO-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/kHtPJ6qcjO-600.jpeg 600w, https://www.zachleat.com/img/built/kHtPJ6qcjO-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/kHtPJ6qcjO-600.jpeg" alt="export { data, render } in an index.11ty.js template." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/fT4BO80vTj-600.avif 600w, https://www.zachleat.com/img/built/fT4BO80vTj-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/fT4BO80vTj-600.jpeg 600w, https://www.zachleat.com/img/built/fT4BO80vTj-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/fT4BO80vTj-600.jpeg" alt="__filename and __dirname aren’t available by default but you can use import.meta.url and fileURLToPath to recreate these." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/6_KxqsJ1_G-600.avif 600w, https://www.zachleat.com/img/built/6_KxqsJ1_G-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/6_KxqsJ1_G-600.jpeg 600w, https://www.zachleat.com/img/built/6_KxqsJ1_G-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/6_KxqsJ1_G-600.jpeg" alt="Eventually we’ll use import.meta.filename and import.meta.dirname (not yet stable as of Node 21)" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/RLw7Z92fOh-600.avif 600w, https://www.zachleat.com/img/built/RLw7Z92fOh-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/RLw7Z92fOh-600.jpeg 600w, https://www.zachleat.com/img/built/RLw7Z92fOh-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/RLw7Z92fOh-600.jpeg" alt="Let’s talk about Third-party plugins in an ESM project." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/0EuDjWV9W1-600.avif 600w, https://www.zachleat.com/img/built/0EuDjWV9W1-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/0EuDjWV9W1-600.jpeg 600w, https://www.zachleat.com/img/built/0EuDjWV9W1-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/0EuDjWV9W1-600.jpeg" alt="Everything will work as-is, you can import any ESM or CJS plugin from an ESM config file. Access to all the things!" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/4h5ZtWDKYP-600.avif 600w, https://www.zachleat.com/img/built/4h5ZtWDKYP-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/4h5ZtWDKYP-600.jpeg 600w, https://www.zachleat.com/img/built/4h5ZtWDKYP-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/4h5ZtWDKYP-600.jpeg" alt="The full checklist of compatibility is complete." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/3hQPzJDozS-600.avif 600w, https://www.zachleat.com/img/built/3hQPzJDozS-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/3hQPzJDozS-600.jpeg 600w, https://www.zachleat.com/img/built/3hQPzJDozS-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/3hQPzJDozS-600.jpeg" alt="Node Scripts, Eleventy’s Programmatic API" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/aW3xg1BSCD-600.avif 600w, https://www.zachleat.com/img/built/aW3xg1BSCD-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/aW3xg1BSCD-600.jpeg 600w, https://www.zachleat.com/img/built/aW3xg1BSCD-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/aW3xg1BSCD-600.jpeg" alt="Again, you can’t require("@11ty/eleventy");" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/2eHxu1n3pr-600.avif 600w, https://www.zachleat.com/img/built/2eHxu1n3pr-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/2eHxu1n3pr-600.jpeg 600w, https://www.zachleat.com/img/built/2eHxu1n3pr-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/2eHxu1n3pr-600.jpeg" alt="Dynamic await import("@11ty/eleventy") again." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/k2ThprKd8d-600.avif 600w, https://www.zachleat.com/img/built/k2ThprKd8d-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/k2ThprKd8d-600.jpeg 600w, https://www.zachleat.com/img/built/k2ThprKd8d-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/k2ThprKd8d-600.jpeg" alt="If your script is ESM, you can `import Eleventy from "@11ty/eleventy"` directly." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/ocwimjdLQH-600.avif 600w, https://www.zachleat.com/img/built/ocwimjdLQH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/ocwimjdLQH-600.jpeg 600w, https://www.zachleat.com/img/built/ocwimjdLQH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/ocwimjdLQH-600.jpeg" alt="Internals" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/xN8McehqdS-600.avif 600w, https://www.zachleat.com/img/built/xN8McehqdS-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/xN8McehqdS-600.jpeg 600w, https://www.zachleat.com/img/built/xN8McehqdS-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/xN8McehqdS-600.jpeg" alt="Upgrade frozen CommonJS dependencies (these went full ESM), @sindresorhus/slugify, multimatch, bcp-47-normalize" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/pinW0FpX7m-600.avif 600w, https://www.zachleat.com/img/built/pinW0FpX7m-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/pinW0FpX7m-600.jpeg 600w, https://www.zachleat.com/img/built/pinW0FpX7m-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/pinW0FpX7m-600.jpeg" alt="Reimporting without require.cache to get new content" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/xr2A9NRhMd-600.avif 600w, https://www.zachleat.com/img/built/xr2A9NRhMd-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/xr2A9NRhMd-600.jpeg 600w, https://www.zachleat.com/img/built/xr2A9NRhMd-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/xr2A9NRhMd-600.jpeg" alt="Before we would delete from require.cache and re-require the file. Now we await import with a cache buster URL parameter." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/hnxQFQ-noE-600.avif 600w, https://www.zachleat.com/img/built/hnxQFQ-noE-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/hnxQFQ-noE-600.jpeg 600w, https://www.zachleat.com/img/built/hnxQFQ-noE-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/hnxQFQ-noE-600.jpeg" alt="Parsing the dependency tree. Before this was available via require.cache. Now we parse the JS with acorn (but it only happens for --serve/--watch)" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/dTuCIreKdK-600.avif 600w, https://www.zachleat.com/img/built/dTuCIreKdK-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/dTuCIreKdK-600.jpeg 600w, https://www.zachleat.com/img/built/dTuCIreKdK-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/dTuCIreKdK-600.jpeg" alt="Import attributes, before we could require a JSON file directly. You can’t await import a JSON file now." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Qy9u4O88vu-600.avif 600w, https://www.zachleat.com/img/built/Qy9u4O88vu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Qy9u4O88vu-600.jpeg 600w, https://www.zachleat.com/img/built/Qy9u4O88vu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Qy9u4O88vu-600.jpeg" alt="You could import a JSON file: assert { type: 'json' } with a warning" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/RwByf1IG1o-600.avif 600w, https://www.zachleat.com/img/built/RwByf1IG1o-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/RwByf1IG1o-600.jpeg 600w, https://www.zachleat.com/img/built/RwByf1IG1o-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/RwByf1IG1o-600.jpeg" alt="The syntax changed to: with { type: 'json' } also with a warning" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/XZs2fHAttu-600.avif 600w, https://www.zachleat.com/img/built/XZs2fHAttu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/XZs2fHAttu-600.jpeg 600w, https://www.zachleat.com/img/built/XZs2fHAttu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/XZs2fHAttu-600.jpeg" alt="We just use fs.readFile* to avoid warnings." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/2piblOGVvx-600.avif 600w, https://www.zachleat.com/img/built/2piblOGVvx-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/2piblOGVvx-600.jpeg 600w, https://www.zachleat.com/img/built/2piblOGVvx-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/2piblOGVvx-600.jpeg" alt="Dynamic execution of JavaScript for `---node` front matter." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/kzdeedgoIx-600.avif 600w, https://www.zachleat.com/img/built/kzdeedgoIx-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/kzdeedgoIx-600.jpeg 600w, https://www.zachleat.com/img/built/kzdeedgoIx-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/kzdeedgoIx-600.jpeg" alt="Node’s vm module is only stable for CommonJS code. Fine until you try to `import` something." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/f8dgX2OB2--600.avif 600w, https://www.zachleat.com/img/built/f8dgX2OB2--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/f8dgX2OB2--600.jpeg 600w, https://www.zachleat.com/img/built/f8dgX2OB2--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/f8dgX2OB2--600.jpeg" alt="You can use node --experimental-vm-modules but we fake it by transforming `import` to dynamic `import()` until this is stable." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/1j4ceKwVJF-600.avif 600w, https://www.zachleat.com/img/built/1j4ceKwVJF-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/1j4ceKwVJF-600.jpeg 600w, https://www.zachleat.com/img/built/1j4ceKwVJF-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/1j4ceKwVJF-600.jpeg" alt="Conclusions" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/iPhxHuAPdH-600.avif 600w, https://www.zachleat.com/img/built/iPhxHuAPdH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/iPhxHuAPdH-600.jpeg 600w, https://www.zachleat.com/img/built/iPhxHuAPdH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/iPhxHuAPdH-600.jpeg" alt="ESM ~~all~~ of the things" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/RCvokoiaaS-600.avif 600w, https://www.zachleat.com/img/built/RCvokoiaaS-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/RCvokoiaaS-600.jpeg 600w, https://www.zachleat.com/img/built/RCvokoiaaS-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/RCvokoiaaS-600.jpeg" alt="ESM some of the things." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/ImypbUsDum-600.avif 600w, https://www.zachleat.com/img/built/ImypbUsDum-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/ImypbUsDum-600.jpeg 600w, https://www.zachleat.com/img/built/ImypbUsDum-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/ImypbUsDum-600.jpeg" alt="It’s better to be an ESM dependent." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/j4B6jPfBhQ-600.avif 600w, https://www.zachleat.com/img/built/j4B6jPfBhQ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/j4B6jPfBhQ-600.jpeg 600w, https://www.zachleat.com/img/built/j4B6jPfBhQ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/j4B6jPfBhQ-600.jpeg" alt="It’s better to be a CommonJS dependency." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Tydm7cK63l-600.avif 600w, https://www.zachleat.com/img/built/Tydm7cK63l-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Tydm7cK63l-600.jpeg 600w, https://www.zachleat.com/img/built/Tydm7cK63l-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Tydm7cK63l-600.jpeg" alt="ESM for me, CommonJS for thee (fancy Winnie the Pooh meme)" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/XZjZTmDZ23-600.avif 600w, https://www.zachleat.com/img/built/XZjZTmDZ23-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/XZjZTmDZ23-600.jpeg 600w, https://www.zachleat.com/img/built/XZjZTmDZ23-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/XZjZTmDZ23-600.jpeg" alt="Titus Wormer’s chart on the popularity of ESM/CJS across popular npm packages. November 2023 had 68.8% CJS, 12.9% Faux, 7.5% Dual, 10.8% ESM" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Dh6eQUtbE3-600.avif 600w, https://www.zachleat.com/img/built/Dh6eQUtbE3-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Dh6eQUtbE3-600.jpeg 600w, https://www.zachleat.com/img/built/Dh6eQUtbE3-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Dh6eQUtbE3-600.jpeg" alt="11ty and Astro are ESM first—Svelte, Vite, Nuxt are dual—Remix and Gatsby are faux—Next.js is CJS." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/ppDaJErVdl-600.avif 600w, https://www.zachleat.com/img/built/ppDaJErVdl-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/ppDaJErVdl-600.jpeg 600w, https://www.zachleat.com/img/built/ppDaJErVdl-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/ppDaJErVdl-600.jpeg" alt="For me, I will not be dual publishing packages. I don’t want the overhead." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/LllSAAN9G9-600.avif 600w, https://www.zachleat.com/img/built/LllSAAN9G9-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/LllSAAN9G9-600.jpeg 600w, https://www.zachleat.com/img/built/LllSAAN9G9-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/LllSAAN9G9-600.jpeg" alt="Looking at package compatibility—if things are going to include you, it’s tempting to use CommonJS . If you’re going to use other things, it’s better to be ESM." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/731n6ZP8kj-600.avif 600w, https://www.zachleat.com/img/built/731n6ZP8kj-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/731n6ZP8kj-600.jpeg 600w, https://www.zachleat.com/img/built/731n6ZP8kj-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/731n6ZP8kj-600.jpeg" alt="But we have to think of the wider ecosystem outside of Node.js (other runtimes and the Browser)." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/LodTkB-mjY-600.avif 600w, https://www.zachleat.com/img/built/LodTkB-mjY-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/LodTkB-mjY-600.jpeg 600w, https://www.zachleat.com/img/built/LodTkB-mjY-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/LodTkB-mjY-600.jpeg" alt="The Parasite movie poster." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/wpokFQLdEN-600.avif 600w, https://www.zachleat.com/img/built/wpokFQLdEN-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/wpokFQLdEN-600.jpeg 600w, https://www.zachleat.com/img/built/wpokFQLdEN-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/wpokFQLdEN-600.jpeg" alt="The Parasite movie poster overlaid with Parasites vs. Para-apps." width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/w1HpjhwmR--600.avif 600w, https://www.zachleat.com/img/built/w1HpjhwmR--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/w1HpjhwmR--600.jpeg 600w, https://www.zachleat.com/img/built/w1HpjhwmR--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/w1HpjhwmR--600.jpeg" alt="Thanks! (the end)" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-rLDj">< Previous</button><output data-carousel-output="carouscroll-id-rLDj"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-rLDj">Next ></button></div></is-land></browser-window></div>
<p><em>Correction: on Slide 40 the code reads <code>eleventyPlugin.addPlugin</code> when it should say <code>eleventyConfig.addPlugin</code>.</em></p>
carouscroll Web Component2024-02-02T00:00:00Zhttp://www.zachleat.com/web/carouscroll/
<p><code><carou-scroll></code> is a zero-dependency web component to add next and previous buttons to a scrollable container. I use it to share exported Keynote slides (as images) on my blog posts.</p>
<p>Some inspiration from the <a href="https://www.w3.org/WAI/tutorials/carousels/">W3C WAI Carousel Tutorial</a> (though this isn’t technically a carousel).</p>
<ul>
<li><a href="https://github.com/zachleat/carouscroll">Source code on GitHub</a></li>
<li><a href="https://zachleat.github.io/carouscroll/demo.html">Demo</a></li>
</ul>
<p>More real-world demos of Keynote slides:</p>
<ul>
<li><a href="https://www.zachleat.com/web/good-bad-web-components/">The Good, The Bad, The Web Components</a> <em>(and the <a href="https://github.com/zachleat/zachleat.com/blob/main/_posts/2024-01-31-good-bad-web-components.md">post source code</a>)</em></li>
<li><a href="https://www.zachleat.com/web/eleventy-meetup-eleventy-v2/">The Eleventy V2.0 Release, A Talk at the Eleventy Meetup</a> <em>(and the <a href="https://github.com/zachleat/zachleat.com/blob/main/_posts/2023-03-16-eleventy-meetup-eleventy-v2.md">post source code</a>)</em></li>
<li><a href="https://www.zachleat.com/web/eleventy-rendering-modes/">Eleventy: Build vs. Serverless vs. Edge</a> <em>(and the <a href="https://github.com/zachleat/zachleat.com/blob/main/_posts/2022-04-21-eleventy-rendering-modes.md">post source code</a>)</em></li>
</ul>
<h2 id="features" tabindex="-1">Features <a href="https://www.zachleat.com/web/carouscroll/#features" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li>Interaction compatible with scroll or touch.</li>
<li>No layout shift. Make sure you include the CSS snippet!</li>
<li>(Optional) Smooth scrolling with <code>scroll-behavior: smooth</code>.</li>
<li>(Optional) <code>loop</code> attribute to enable looping around from start/end.</li>
<li>(Optional) Next/Previous buttons can be placed anywhere in the document.</li>
<li>(Optional) <code><output></code> element can accessibly announce the current slide number (out of total number of slides).</li>
</ul>
<h2 id="usage" tabindex="-1">Usage <a href="https://www.zachleat.com/web/carouscroll/#usage" aria-hidden="true" class="direct-link">#</a></h2>
<h3 id="installation" tabindex="-1">Installation <a href="https://www.zachleat.com/web/carouscroll/#installation" aria-hidden="true" class="direct-link">#</a></h3>
<p>You can install via <code>npm</code> or download the <code>carouscroll.js</code> JavaScript file manually.</p>
<pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @zachleat/carouscroll <span class="token parameter variable">--save</span></code></pre>
<p>Add <code>carouscroll.js</code> to your site’s JavaScript assets.</p>
<h3 id="markup" tabindex="-1">Markup <a href="https://www.zachleat.com/web/carouscroll/#markup" aria-hidden="true" class="direct-link">#</a></h3>
<p>The CSS here is <strong>crucial</strong> to reduce Layout Shift (CLS), set the aspect ratio of the slides, and to avoid loading <code>loading="lazy"</code> images on off-screen slides.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">carou-scroll</span> <span class="token punctuation">{</span>
<span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span>
<span class="token property">overflow-x</span><span class="token punctuation">:</span> scroll<span class="token punctuation">;</span>
<span class="token property">overflow-y</span><span class="token punctuation">:</span> hidden<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token selector">carou-scroll > *</span> <span class="token punctuation">{</span>
<span class="token property">min-width</span><span class="token punctuation">:</span> 100%<span class="token punctuation">;</span>
<span class="token comment">/* Customize this as needed */</span>
<span class="token property">aspect-ratio</span><span class="token punctuation">:</span> 16/9<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</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>carouscroll.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"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>carou-scroll</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>my-scroller<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lazy<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>…<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>…<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>img</span> <span class="token attr-name">loading</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>lazy<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>…<span class="token punctuation">"</span></span> <span class="token attr-name">alt</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>…<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token comment"><!-- … --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>carou-scroll</span><span class="token punctuation">></span></span></code></pre>
<h3 id="more-examples" tabindex="-1">More examples <a href="https://www.zachleat.com/web/carouscroll/#more-examples" aria-hidden="true" class="direct-link">#</a></h3>
<h4 id="add-buttons-(optional)" tabindex="-1">Add buttons (optional) <a href="https://www.zachleat.com/web/carouscroll/#add-buttons-(optional)" aria-hidden="true" class="direct-link">#</a></h4>
<p>For maximum flexibility, these buttons can be placed anywhere in the document and are tied by an <code>id</code> back to the parent scroller.</p>
<p>Make sure you think about the before/after JavaScript experience here. This component will remove <code>disabled</code> for you but you can add additional styling via your own CSS: <code>carou-scroll:defined {}</code>.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</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>button<span class="token punctuation">"</span></span> <span class="token attr-name">disabled</span> <span class="token attr-name">data-carousel-previous</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>my-scroller<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Previous<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</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>button<span class="token punctuation">"</span></span> <span class="token attr-name">disabled</span> <span class="token attr-name">data-carousel-next</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>my-scroller<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Next<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<h4 id="add-output-(optional)" tabindex="-1">Add output (optional) <a href="https://www.zachleat.com/web/carouscroll/#add-output-(optional)" aria-hidden="true" class="direct-link">#</a></h4>
<p>This will update (and accessibly announce) a current status element with e.g. <code>Slide 1 of 10</code> text.</p>
<p>For maximum flexibility, this element can be placed anywhere in the document and is tied by an <code>id</code> back to the parent scroller.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>output</span> <span class="token attr-name">data-carousel-output</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>my-scroller<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>output</span><span class="token punctuation">></span></span>
<span class="token comment"><!-- Or customize with your own markup --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>output</span> <span class="token attr-name">data-carousel-output</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>my-scroller<span class="token punctuation">"</span></span> <span class="token attr-name">lang</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>ko<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>슬라이드 <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">data-carousel-output-current</span><span class="token punctuation">></span></span>1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>/<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">data-carousel-output-total</span><span class="token punctuation">></span></span>10<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>output</span><span class="token punctuation">></span></span></code></pre>
<h4 id="make-it-loop-around-(optional)" tabindex="-1">Make it loop around (optional) <a href="https://www.zachleat.com/web/carouscroll/#make-it-loop-around-(optional)" aria-hidden="true" class="direct-link">#</a></h4>
<p>Add the <code>loop</code> attribute.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>carou-scroll</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>my-scroller<span class="token punctuation">"</span></span> <span class="token attr-name">loop</span><span class="token punctuation">></span></span></code></pre>
<h4 id="smooth-scrolling-css-(optional)" tabindex="-1">Smooth scrolling CSS (optional) <a href="https://www.zachleat.com/web/carouscroll/#smooth-scrolling-css-(optional)" aria-hidden="true" class="direct-link">#</a></h4>
<pre class="language-css"><code class="language-css"><span class="token selector">carou-scroll</span> <span class="token punctuation">{</span>
<span class="token property">scroll-behavior</span><span class="token punctuation">:</span> smooth<span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
The Good, The Bad, The Web Components2024-01-31T00:00:00Zhttp://www.zachleat.com/web/good-bad-web-components/
<p><em>This post was created from a talk. You can watch this in video form at <a href="https://www.zachleat.com/web/jsheroes/">JSHeroes 2023</a>.</em></p>
<p>The humble component. The building block of modern web development.</p>
<pre class="language-jsx"><code class="language-jsx"><span class="token comment">// MyButton.jsx</span>
<span class="token keyword">function</span> <span class="token function">MyButton</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span><span class="token plain-text">I'm a button</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Usage</span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">MyButton</span></span><span class="token punctuation">/></span></span></code></pre>
<p>You may recognize the above example taken from the documentation of one of the most popular component libraries in use today—<a href="https://react.dev/"><span class="nowrap"><img alt="IndieWeb Avatar for https://vercel.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fvercel.com%2F/" width="60" height="60" class="z-avatar" /><img alt="IndieWeb Avatar for https://react.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Freact.dev%2F/" width="60" height="60" class="z-avatar" />Vercel.js</span></a>.</p>
<p>Folks may not know that the web platform has <em>some</em> component functionality built-in and is evolving support for new component development standards and specifications moving forward! These features are broadly known as Web Components.</p>
<p>Aside from Vercel.js, there are a variety of other popular component libraries too (of varying degree of web component friendliness):</p>
<script type="module" src="https://www.zachleat.com/static/table-saw.js"></script>
<div><table-saw>
<table>
<thead>
<tr>
<th>Library</th>
<th>Uses Native<br />Web Components</th>
<th>Custom Elements<br />as Compile Target</th>
<th>Compatibility<br />Score</th>
</tr>
</thead>
<tbody>
<tr>
<td><img alt="IndieWeb Avatar for https://alpinejs.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Falpinejs.dev%2F/" width="60" height="60" class="z-avatar" />Alpine</td>
<td class="no">No</td>
<td class="no">No</td>
<td><em>Unknown</em></td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://angularjs.org/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fangularjs.org%2F/" width="60" height="60" class="z-avatar" />Angular</td>
<td class="no">No</td>
<td class="yes">Yes</td>
<td class="yes">100%</td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://emberjs.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Femberjs.com%2F/" width="60" height="60" class="z-avatar" />Ember</td>
<td class="no">No</td>
<td class="no">No</td>
<td><em>Unknown</em></td>
</tr>
<!-- <tr>
<td><img alt="IndieWeb Avatar for https://htmx.org/" class="z-avatar" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fhtmx.org%2F/" width="60" height="60">htmx</td>
<td></td>
<td></td>
<td><em>Unknown</em></td>
</tr> -->
<tr>
<td><img alt="IndieWeb Avatar for https://lit.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Flit.dev%2F/" width="60" height="60" class="z-avatar" />Lit</td>
<td class="yes">Yes</td>
<td class="yes">Yes</td>
<td class="yes">100%</td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://preactjs.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fpreactjs.com%2F/" width="60" height="60" class="z-avatar" />Preact</td>
<td class="no">No</td>
<td class="yes">Yes</td>
<td class="yes">100%</td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://qwik.builder.io/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fqwik.builder.io%2F/" width="60" height="60" class="z-avatar" />Qwik</td>
<td class="no">No</td>
<td class="no">No</td>
<td><em>Unknown</em></td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://react.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Freact.dev%2F/" width="60" height="60" class="z-avatar" />React</td>
<td class="no">No</td>
<td class="no">No</td>
<td class="no">67%</td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://www.solidjs.com" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fwww.solidjs.com/" width="60" height="60" class="z-avatar" />Solid</td>
<td class="no">No</td>
<td class="yes">Yes</td>
<td class="maybe">94%</td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://stenciljs.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fstenciljs.com%2F/" width="60" height="60" class="z-avatar" />Stencil</td>
<td class="yes">Yes</td>
<td class="yes">Yes</td>
<td class="yes">100%</td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://svelte.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fsvelte.dev%2F/" width="60" height="60" class="z-avatar" />Svelte</td>
<td class="no">No</td>
<td class="yes">Yes</td>
<td class="maybe">94%</td>
</tr>
<tr>
<td><img alt="IndieWeb Avatar for https://vuejs.org/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fvuejs.org%2F/" width="60" height="60" class="z-avatar" />Vue</td>
<td class="no">No</td>
<td class="yes">Yes</td>
<td class="yes">100%</td>
</tr>
</tbody>
</table>
</table-saw></div>
<p><em>Compatibility score data from <a href="https://custom-elements-everywhere.com/">Custom Elements Everywhere</a>, a test suite for web component compatibility.</em></p>
<h2 id="web-components-are-already-a-success-story" tabindex="-1">Web Components are already a Success Story <a href="https://www.zachleat.com/web/good-bad-web-components/#web-components-are-already-a-success-story" aria-hidden="true" class="direct-link">#</a></h2>
<p>Despite some notable criticism, web components are already widely used across the web.</p>
<p>In August 2023, <a href="https://chromestatus.com/metrics/feature/timeline/popularity/1689">Chrome Platform Status</a> reports that 19.4% of page loads in Google Chrome were using a web component (via the <code>CustomElementRegistryDefine</code> key). For comparison <code><img loading></code> was at 15% and CSS Grid at 20%.</p>
<p>You can check out more social proof on: <a href="https://arewebcomponentsathingyet.com/"><em>Are Web Components A Thing Yet?</em></a></p>
<p>They are particularly popular in large enterprise design system implementations:</p>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-aOwa" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/bN13qqkj7a-600.avif 600w, https://www.zachleat.com/img/built/bN13qqkj7a-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/bN13qqkj7a-600.jpeg 600w, https://www.zachleat.com/img/built/bN13qqkj7a-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/bN13qqkj7a-600.jpeg" alt="Slide 19" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/ve5hpYQ_3A-600.avif 600w, https://www.zachleat.com/img/built/ve5hpYQ_3A-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/ve5hpYQ_3A-600.jpeg 600w, https://www.zachleat.com/img/built/ve5hpYQ_3A-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/ve5hpYQ_3A-600.jpeg" alt="Slide 20" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/iFpddCG3cU-600.avif 600w, https://www.zachleat.com/img/built/iFpddCG3cU-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/iFpddCG3cU-600.jpeg 600w, https://www.zachleat.com/img/built/iFpddCG3cU-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/iFpddCG3cU-600.jpeg" alt="Slide 21" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/dw3vs-6Niu-600.avif 600w, https://www.zachleat.com/img/built/dw3vs-6Niu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/dw3vs-6Niu-600.jpeg 600w, https://www.zachleat.com/img/built/dw3vs-6Niu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/dw3vs-6Niu-600.jpeg" alt="Slide 22" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Q3nnjNFc9u-600.avif 600w, https://www.zachleat.com/img/built/Q3nnjNFc9u-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Q3nnjNFc9u-600.jpeg 600w, https://www.zachleat.com/img/built/Q3nnjNFc9u-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Q3nnjNFc9u-600.jpeg" alt="Slide 23" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/k9m1Msdohq-600.avif 600w, https://www.zachleat.com/img/built/k9m1Msdohq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/k9m1Msdohq-600.jpeg 600w, https://www.zachleat.com/img/built/k9m1Msdohq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/k9m1Msdohq-600.jpeg" alt="Slide 24" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/IhurFzntot-600.avif 600w, https://www.zachleat.com/img/built/IhurFzntot-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/IhurFzntot-600.jpeg 600w, https://www.zachleat.com/img/built/IhurFzntot-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/IhurFzntot-600.jpeg" alt="Slide 25" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/LpHlxyNtQi-600.avif 600w, https://www.zachleat.com/img/built/LpHlxyNtQi-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/LpHlxyNtQi-600.jpeg 600w, https://www.zachleat.com/img/built/LpHlxyNtQi-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/LpHlxyNtQi-600.jpeg" alt="Slide 26" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-aOwa">< Previous</button><output data-carousel-output="carouscroll-id-aOwa"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-aOwa">Next ></button></div></is-land></browser-window></div>
<p>Many of the component libraries listed above (even those written by authors that have criticized web components) have an export-to-web-component feature built-in.</p>
<h2 id="butwhat-are-they" tabindex="-1">But—what are they? <a href="https://www.zachleat.com/web/good-bad-web-components/#butwhat-are-they" aria-hidden="true" class="direct-link">#</a></h2>
<p>Web Components is an umbrella term that encompasses many web platform technologies and we’ll go over a couple of the big (and in my opinion, most relevant) ones.</p>
<h3 id="custom-elements" tabindex="-1">Custom Elements <a href="https://www.zachleat.com/web/good-bad-web-components/#custom-elements" aria-hidden="true" class="direct-link">#</a></h3>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-DyOT" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/xeF6BJgIqI-600.avif 600w, https://www.zachleat.com/img/built/xeF6BJgIqI-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/xeF6BJgIqI-600.jpeg 600w, https://www.zachleat.com/img/built/xeF6BJgIqI-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/xeF6BJgIqI-600.jpeg" alt="Slide 35" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/OViECyPqdn-600.avif 600w, https://www.zachleat.com/img/built/OViECyPqdn-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/OViECyPqdn-600.jpeg 600w, https://www.zachleat.com/img/built/OViECyPqdn-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/OViECyPqdn-600.jpeg" alt="Slide 36" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/wNrjAwZIsZ-600.avif 600w, https://www.zachleat.com/img/built/wNrjAwZIsZ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/wNrjAwZIsZ-600.jpeg 600w, https://www.zachleat.com/img/built/wNrjAwZIsZ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/wNrjAwZIsZ-600.jpeg" alt="Slide 37" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/1ozwBECYxt-600.avif 600w, https://www.zachleat.com/img/built/1ozwBECYxt-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/1ozwBECYxt-600.jpeg 600w, https://www.zachleat.com/img/built/1ozwBECYxt-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/1ozwBECYxt-600.jpeg" alt="Slide 38" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/QM6jfP1TqA-600.avif 600w, https://www.zachleat.com/img/built/QM6jfP1TqA-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/QM6jfP1TqA-600.jpeg 600w, https://www.zachleat.com/img/built/QM6jfP1TqA-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/QM6jfP1TqA-600.jpeg" alt="Slide 39" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/vtgCYbZxbH-600.avif 600w, https://www.zachleat.com/img/built/vtgCYbZxbH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/vtgCYbZxbH-600.jpeg 600w, https://www.zachleat.com/img/built/vtgCYbZxbH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/vtgCYbZxbH-600.jpeg" alt="Slide 40" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/mYJg4fVJon-600.avif 600w, https://www.zachleat.com/img/built/mYJg4fVJon-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/mYJg4fVJon-600.jpeg 600w, https://www.zachleat.com/img/built/mYJg4fVJon-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/mYJg4fVJon-600.jpeg" alt="Slide 41" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/RuM01_HJKB-600.avif 600w, https://www.zachleat.com/img/built/RuM01_HJKB-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/RuM01_HJKB-600.jpeg 600w, https://www.zachleat.com/img/built/RuM01_HJKB-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/RuM01_HJKB-600.jpeg" alt="Slide 42" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/UD-Pk7-d8J-600.avif 600w, https://www.zachleat.com/img/built/UD-Pk7-d8J-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/UD-Pk7-d8J-600.jpeg 600w, https://www.zachleat.com/img/built/UD-Pk7-d8J-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/UD-Pk7-d8J-600.jpeg" alt="Slide 43" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Rqc47Zw_el-600.avif 600w, https://www.zachleat.com/img/built/Rqc47Zw_el-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Rqc47Zw_el-600.jpeg 600w, https://www.zachleat.com/img/built/Rqc47Zw_el-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Rqc47Zw_el-600.jpeg" alt="Slide 44" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/03J4f_6j1Q-600.avif 600w, https://www.zachleat.com/img/built/03J4f_6j1Q-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/03J4f_6j1Q-600.jpeg 600w, https://www.zachleat.com/img/built/03J4f_6j1Q-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/03J4f_6j1Q-600.jpeg" alt="Slide 45" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/PRLb8-wkPt-600.avif 600w, https://www.zachleat.com/img/built/PRLb8-wkPt-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/PRLb8-wkPt-600.jpeg 600w, https://www.zachleat.com/img/built/PRLb8-wkPt-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/PRLb8-wkPt-600.jpeg" alt="Slide 46" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/oLlnpetl_5-600.avif 600w, https://www.zachleat.com/img/built/oLlnpetl_5-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/oLlnpetl_5-600.jpeg 600w, https://www.zachleat.com/img/built/oLlnpetl_5-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/oLlnpetl_5-600.jpeg" alt="Slide 47" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/W4u9kpmrAC-600.avif 600w, https://www.zachleat.com/img/built/W4u9kpmrAC-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/W4u9kpmrAC-600.jpeg 600w, https://www.zachleat.com/img/built/W4u9kpmrAC-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/W4u9kpmrAC-600.jpeg" alt="Slide 48" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/r6PfLTyuYx-600.avif 600w, https://www.zachleat.com/img/built/r6PfLTyuYx-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/r6PfLTyuYx-600.jpeg 600w, https://www.zachleat.com/img/built/r6PfLTyuYx-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/r6PfLTyuYx-600.jpeg" alt="Slide 49" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-DyOT">< Previous</button><output data-carousel-output="carouscroll-id-DyOT"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-DyOT">Next ></button></div></is-land></browser-window></div>
<p>Custom Elements allow you to attach a JavaScript <code>class</code> to any custom element you define on your page, to add behaviors and JavaScript-generated content to any element instances on the page. These are automatically initialized for you for the full lifecycle of the page (for server-rendered, JavaScript-generated, or content injected via <code>fetch()</code>).</p>
<ol>
<li>Custom elements cannot be void elements (like <code><img></code> or <code><meta></code>): they must have a start and end tag.</li>
<li>Custom elements must have a dash in the tag name, so as to not conflict with future additions to the web platform.</li>
</ol>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-06RK" disabled="" class="carouscroll carouscroll-single"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/DJpQRM_jD8-600.avif 600w, https://www.zachleat.com/img/built/DJpQRM_jD8-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/DJpQRM_jD8-600.jpeg 600w, https://www.zachleat.com/img/built/DJpQRM_jD8-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/DJpQRM_jD8-600.jpeg" alt="Slide 61" width="1000" height="562" /></picture></carou-scroll></is-land></browser-window></div>
<p>If you populate these with your own server-rendered nested HTML (also known as the default slot/Light DOM/plain ’ol HTML) and use Custom Elements to for behavior-only additions (and not modify rendered DOM), you can even avoid layout shift!</p>
<p>Some folks have started referring to this approach as creating <a href="https://www.zachleat.com/web/a-taxonomy-of-web-component-types/#html-web-components">HTML Web Components</a> (see post for examples).</p>
<p><em>If your expectation is full stack component-driven development</em>, you will quickly encounter a developer experience problem here when authoring components in this way. Multiple instances of the same component need to repeat the same nested content and any changes to nested content need to be applied manually to all instances. You end up writing this:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span>1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span>2<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span>3<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span></code></pre>
<p>When we want to author this (and have it server-render as the above):</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>2<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>3<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span></code></pre>
<h3 id="shadow-dom" tabindex="-1">Shadow DOM <a href="https://www.zachleat.com/web/good-bad-web-components/#shadow-dom" aria-hidden="true" class="direct-link">#</a></h3>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-fdIT" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/0713KAe4T_-600.avif 600w, https://www.zachleat.com/img/built/0713KAe4T_-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/0713KAe4T_-600.jpeg 600w, https://www.zachleat.com/img/built/0713KAe4T_-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/0713KAe4T_-600.jpeg" alt="Slide 68" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/UpUcX_1AH2-600.avif 600w, https://www.zachleat.com/img/built/UpUcX_1AH2-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/UpUcX_1AH2-600.jpeg 600w, https://www.zachleat.com/img/built/UpUcX_1AH2-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/UpUcX_1AH2-600.jpeg" alt="Slide 69" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/cjin7G9d9C-600.avif 600w, https://www.zachleat.com/img/built/cjin7G9d9C-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/cjin7G9d9C-600.jpeg 600w, https://www.zachleat.com/img/built/cjin7G9d9C-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/cjin7G9d9C-600.jpeg" alt="Slide 70" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/PaFU_RTmgb-600.avif 600w, https://www.zachleat.com/img/built/PaFU_RTmgb-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/PaFU_RTmgb-600.jpeg 600w, https://www.zachleat.com/img/built/PaFU_RTmgb-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/PaFU_RTmgb-600.jpeg" alt="Slide 71" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/7vwPpL185L-600.avif 600w, https://www.zachleat.com/img/built/7vwPpL185L-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/7vwPpL185L-600.jpeg 600w, https://www.zachleat.com/img/built/7vwPpL185L-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/7vwPpL185L-600.jpeg" alt="Slide 72" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/YiT8g_L4nw-600.avif 600w, https://www.zachleat.com/img/built/YiT8g_L4nw-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/YiT8g_L4nw-600.jpeg 600w, https://www.zachleat.com/img/built/YiT8g_L4nw-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/YiT8g_L4nw-600.jpeg" alt="Slide 73" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/iZrWR2Cvm--600.avif 600w, https://www.zachleat.com/img/built/iZrWR2Cvm--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/iZrWR2Cvm--600.jpeg 600w, https://www.zachleat.com/img/built/iZrWR2Cvm--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/iZrWR2Cvm--600.jpeg" alt="Slide 74" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/5j6iLPnTzk-600.avif 600w, https://www.zachleat.com/img/built/5j6iLPnTzk-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/5j6iLPnTzk-600.jpeg 600w, https://www.zachleat.com/img/built/5j6iLPnTzk-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/5j6iLPnTzk-600.jpeg" alt="Slide 75" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/97uMIzR8xY-600.avif 600w, https://www.zachleat.com/img/built/97uMIzR8xY-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/97uMIzR8xY-600.jpeg 600w, https://www.zachleat.com/img/built/97uMIzR8xY-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/97uMIzR8xY-600.jpeg" alt="Slide 76" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/f0GD15TUVk-600.avif 600w, https://www.zachleat.com/img/built/f0GD15TUVk-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/f0GD15TUVk-600.jpeg 600w, https://www.zachleat.com/img/built/f0GD15TUVk-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/f0GD15TUVk-600.jpeg" alt="Slide 77" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/z-yoZGE_6u-600.avif 600w, https://www.zachleat.com/img/built/z-yoZGE_6u-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/z-yoZGE_6u-600.jpeg 600w, https://www.zachleat.com/img/built/z-yoZGE_6u-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/z-yoZGE_6u-600.jpeg" alt="Slide 78" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Ibt7KWcIsQ-600.avif 600w, https://www.zachleat.com/img/built/Ibt7KWcIsQ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Ibt7KWcIsQ-600.jpeg 600w, https://www.zachleat.com/img/built/Ibt7KWcIsQ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Ibt7KWcIsQ-600.jpeg" alt="Slide 79" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/1ZiMtaSJK5-600.avif 600w, https://www.zachleat.com/img/built/1ZiMtaSJK5-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/1ZiMtaSJK5-600.jpeg 600w, https://www.zachleat.com/img/built/1ZiMtaSJK5-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/1ZiMtaSJK5-600.jpeg" alt="Slide 80" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/A8UkE0vqdW-600.avif 600w, https://www.zachleat.com/img/built/A8UkE0vqdW-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/A8UkE0vqdW-600.jpeg 600w, https://www.zachleat.com/img/built/A8UkE0vqdW-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/A8UkE0vqdW-600.jpeg" alt="Slide 81" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/t_5kc0K8Vr-600.avif 600w, https://www.zachleat.com/img/built/t_5kc0K8Vr-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/t_5kc0K8Vr-600.jpeg 600w, https://www.zachleat.com/img/built/t_5kc0K8Vr-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/t_5kc0K8Vr-600.jpeg" alt="Slide 82" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/CnGjT7WjEV-600.avif 600w, https://www.zachleat.com/img/built/CnGjT7WjEV-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/CnGjT7WjEV-600.jpeg 600w, https://www.zachleat.com/img/built/CnGjT7WjEV-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/CnGjT7WjEV-600.jpeg" alt="Slide 83" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Oe3lqSWu3o-600.avif 600w, https://www.zachleat.com/img/built/Oe3lqSWu3o-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Oe3lqSWu3o-600.jpeg 600w, https://www.zachleat.com/img/built/Oe3lqSWu3o-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Oe3lqSWu3o-600.jpeg" alt="Slide 85" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-fdIT">< Previous</button><output data-carousel-output="carouscroll-id-fdIT"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-fdIT">Next ></button></div></is-land></browser-window></div>
<p>Shadow DOM is the next level of our web components evolution. It solves the developer experience problem with repetition in authoring markup at the expense of clientside rendering 😭.</p>
<p>This trade-off introduces additional complexities around managing layout shift and the flash of unstyled content (FOUC). Typical approaches to solve these problems either put all of the JavaScript into the critical rendering path (in the <code><head></code>, which I try to avoid for performance reasons) and/or hide the components until they’re defined via JavaScript (which is by definition a performance problem for critical content).</p>
<p>One such approach to assert control over pre-definition and post-definition styling is the CSS pseudo-class <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:defined"><code>:defined</code></a>. It’s a <em>very useful</em> tool for styling and progressive enhancement (occupying some of the same architectural vibes as the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/scripting"><code>scripting</code> media query</a>), but in Shadow DOM heavy component libraries it is often applied to hide components while loading (👎🏻 no thank you).</p>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-fqwU" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/_9TXsxpwOr-600.avif 600w, https://www.zachleat.com/img/built/_9TXsxpwOr-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/_9TXsxpwOr-600.jpeg 600w, https://www.zachleat.com/img/built/_9TXsxpwOr-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/_9TXsxpwOr-600.jpeg" alt="Slide 86" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/a9XojxuGcL-600.avif 600w, https://www.zachleat.com/img/built/a9XojxuGcL-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/a9XojxuGcL-600.jpeg 600w, https://www.zachleat.com/img/built/a9XojxuGcL-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/a9XojxuGcL-600.jpeg" alt="Slide 87" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/j2Hf8oRwoQ-600.avif 600w, https://www.zachleat.com/img/built/j2Hf8oRwoQ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/j2Hf8oRwoQ-600.jpeg 600w, https://www.zachleat.com/img/built/j2Hf8oRwoQ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/j2Hf8oRwoQ-600.jpeg" alt="Slide 88" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/GXZhA4Eh-T-600.avif 600w, https://www.zachleat.com/img/built/GXZhA4Eh-T-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/GXZhA4Eh-T-600.jpeg 600w, https://www.zachleat.com/img/built/GXZhA4Eh-T-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/GXZhA4Eh-T-600.jpeg" alt="Slide 89" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/zsGm9Y-NVZ-600.avif 600w, https://www.zachleat.com/img/built/zsGm9Y-NVZ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/zsGm9Y-NVZ-600.jpeg 600w, https://www.zachleat.com/img/built/zsGm9Y-NVZ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/zsGm9Y-NVZ-600.jpeg" alt="Slide 90" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/BTU_1FbX88-600.avif 600w, https://www.zachleat.com/img/built/BTU_1FbX88-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/BTU_1FbX88-600.jpeg 600w, https://www.zachleat.com/img/built/BTU_1FbX88-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/BTU_1FbX88-600.jpeg" alt="Slide 91" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/qGyeAo4QFT-600.avif 600w, https://www.zachleat.com/img/built/qGyeAo4QFT-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/qGyeAo4QFT-600.jpeg 600w, https://www.zachleat.com/img/built/qGyeAo4QFT-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/qGyeAo4QFT-600.jpeg" alt="Slide 92" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-fqwU">< Previous</button><output data-carousel-output="carouscroll-id-fqwU"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-fqwU">Next ></button></div></is-land></browser-window></div>
<h3 id="declarative-shadow-dom" tabindex="-1">Declarative Shadow DOM <a href="https://www.zachleat.com/web/good-bad-web-components/#declarative-shadow-dom" aria-hidden="true" class="direct-link">#</a></h3>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-ctNP" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/OfUv5qhjd--600.avif 600w, https://www.zachleat.com/img/built/OfUv5qhjd--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/OfUv5qhjd--600.jpeg 600w, https://www.zachleat.com/img/built/OfUv5qhjd--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/OfUv5qhjd--600.jpeg" alt="Slide 94" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/t8LQjWworE-600.avif 600w, https://www.zachleat.com/img/built/t8LQjWworE-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/t8LQjWworE-600.jpeg 600w, https://www.zachleat.com/img/built/t8LQjWworE-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/t8LQjWworE-600.jpeg" alt="Slide 95" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Rqc47Zw_el-600.avif 600w, https://www.zachleat.com/img/built/Rqc47Zw_el-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Rqc47Zw_el-600.jpeg 600w, https://www.zachleat.com/img/built/Rqc47Zw_el-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Rqc47Zw_el-600.jpeg" alt="Slide 96" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/03J4f_6j1Q-600.avif 600w, https://www.zachleat.com/img/built/03J4f_6j1Q-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/03J4f_6j1Q-600.jpeg 600w, https://www.zachleat.com/img/built/03J4f_6j1Q-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/03J4f_6j1Q-600.jpeg" alt="Slide 97" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/IylpZeHlQV-600.avif 600w, https://www.zachleat.com/img/built/IylpZeHlQV-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/IylpZeHlQV-600.jpeg 600w, https://www.zachleat.com/img/built/IylpZeHlQV-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/IylpZeHlQV-600.jpeg" alt="Slide 99" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/2EgiWxv_Xm-600.avif 600w, https://www.zachleat.com/img/built/2EgiWxv_Xm-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/2EgiWxv_Xm-600.jpeg 600w, https://www.zachleat.com/img/built/2EgiWxv_Xm-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/2EgiWxv_Xm-600.jpeg" alt="Slide 102" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/h1QXNbTe3L-600.avif 600w, https://www.zachleat.com/img/built/h1QXNbTe3L-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/h1QXNbTe3L-600.jpeg 600w, https://www.zachleat.com/img/built/h1QXNbTe3L-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/h1QXNbTe3L-600.jpeg" alt="Slide 103" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Ttz7Pa8QOq-600.avif 600w, https://www.zachleat.com/img/built/Ttz7Pa8QOq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Ttz7Pa8QOq-600.jpeg 600w, https://www.zachleat.com/img/built/Ttz7Pa8QOq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Ttz7Pa8QOq-600.jpeg" alt="Slide 104" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/I_YTIhetfD-600.avif 600w, https://www.zachleat.com/img/built/I_YTIhetfD-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/I_YTIhetfD-600.jpeg 600w, https://www.zachleat.com/img/built/I_YTIhetfD-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/I_YTIhetfD-600.jpeg" alt="Slide 105" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/HlReXLOlvu-600.avif 600w, https://www.zachleat.com/img/built/HlReXLOlvu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/HlReXLOlvu-600.jpeg 600w, https://www.zachleat.com/img/built/HlReXLOlvu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/HlReXLOlvu-600.jpeg" alt="Slide 106" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/NS_rfPaaWs-600.avif 600w, https://www.zachleat.com/img/built/NS_rfPaaWs-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/NS_rfPaaWs-600.jpeg 600w, https://www.zachleat.com/img/built/NS_rfPaaWs-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/NS_rfPaaWs-600.jpeg" alt="Slide 107" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/I8qUZTNhwO-600.avif 600w, https://www.zachleat.com/img/built/I8qUZTNhwO-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/I8qUZTNhwO-600.jpeg 600w, https://www.zachleat.com/img/built/I8qUZTNhwO-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/I8qUZTNhwO-600.jpeg" alt="Slide 108" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-ctNP">< Previous</button><output data-carousel-output="carouscroll-id-ctNP"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-ctNP">Next ></button></div></is-land></browser-window></div>
<p>As we evolve to our next level of web components, we move up to Declarative Shadow DOM (sometimes known as DSD). This allows you to put your Shadow DOM template in nested markup inside of each element instance and the browser will create a <code>shadowRoot</code> for you with the template contents (no JavaScript required).</p>
<p>This solves the clientside rendering dependency for Shadow DOM but at the expense of repetition in authoring markup! The ol’ switcheroo (in some ways) feels like a de-evolution back to the approach we discussed in Custom Elements!</p>
<p>Uniquely, this approach does allow you to use <a href="https://www.zachleat.com/web/styling-web-components/">scoped CSS</a> (and <code><slot></code>) without a JavaScript dependency. Non-declarative (imperative/JavaScript) Shadow DOM offers scoped CSS too but repeats some of the mistakes made by CSS-in-JS approaches and introduces a runtime JavaScript dependency on styling.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span> <span class="token attr-name">shadowrootmode</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>open<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">*</span> <span class="token punctuation">{</span> <span class="token property">color</span><span class="token punctuation">:</span> blue<span class="token punctuation">;</span> <span class="token punctuation">}</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span></code></pre>
<p>Again, the repetition of these templates in every component instance is tedious and brittle.</p>
<p>We can see the tension between authoring repetition and server-side rendering, right? (Non-declarative) Shadow DOM was the only approach so far that offered a good authoring experience without repetition, but required clientside rendering.</p>
<h2 id="server-side-rendering" tabindex="-1">Server-side Rendering <a href="https://www.zachleat.com/web/good-bad-web-components/#server-side-rendering" aria-hidden="true" class="direct-link">#</a></h2>
<p>If we could have a reusable Declarative Shadow DOM template that did not require nesting, that would be ideal, right? <em>(Discussion on this topic is happening in <a href="https://github.com/WICG/webcomponents/issues/1009">this WICG issue</a>)</em></p>
<p>I would love something like this (though I acknowledge the likely issues with streaming here):</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- ⚠️ THIS CODE IS ASPIRATIONAL --></span>
<span class="token comment"><!-- ⚠️ IT DOESN’T WORK ANYWHERE --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span> <span class="token attr-name">definition</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>my-counter<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">button</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"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>2<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>3<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span></code></pre>
<p>This is why folks will claim that server-side rendering is yet to be a solved problem—it has not yet been solved at the web platform level. I’d also wager that this is the biggest remaining complaint about web components.</p>
<p>Folks expect this to be solved because this is a problem that many component libraries have solved. However, I would warn that we’re holding these two disparate things to different expectations.</p>
<p>Clientside component frameworks <em>can</em> and should be compared to web component specifications and tools. But if your component framework introduces an additional server rendering step or abstraction, it seems unfair to compare that to clientside-only web components.</p>
<p>Full stack server-rendered Svelte or React cannot and should not be compared to clientside web component specifications like Custom Elements or Shadow DOM—these are apples and oranges. The biggest thing I hear from these criticisms is that we need an additional server-rendered abstraction for web components, too.</p>
<h2 id="framework-tension" tabindex="-1">Framework Tension <a href="https://www.zachleat.com/web/good-bad-web-components/#framework-tension" aria-hidden="true" class="direct-link">#</a></h2>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-WlXD" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/mlzSEboCIK-600.avif 600w, https://www.zachleat.com/img/built/mlzSEboCIK-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/mlzSEboCIK-600.jpeg 600w, https://www.zachleat.com/img/built/mlzSEboCIK-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/mlzSEboCIK-600.jpeg" alt="Slide 128" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/F9BCbsN-et-600.avif 600w, https://www.zachleat.com/img/built/F9BCbsN-et-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/F9BCbsN-et-600.jpeg 600w, https://www.zachleat.com/img/built/F9BCbsN-et-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/F9BCbsN-et-600.jpeg" alt="Slide 129" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/pNjXtuoZG5-600.avif 600w, https://www.zachleat.com/img/built/pNjXtuoZG5-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/pNjXtuoZG5-600.jpeg 600w, https://www.zachleat.com/img/built/pNjXtuoZG5-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/pNjXtuoZG5-600.jpeg" alt="Slide 130" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/7UTkFfYCSG-600.avif 600w, https://www.zachleat.com/img/built/7UTkFfYCSG-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/7UTkFfYCSG-600.jpeg 600w, https://www.zachleat.com/img/built/7UTkFfYCSG-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/7UTkFfYCSG-600.jpeg" alt="Slide 132" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/BiJaXQsqOH-600.avif 600w, https://www.zachleat.com/img/built/BiJaXQsqOH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/BiJaXQsqOH-600.jpeg 600w, https://www.zachleat.com/img/built/BiJaXQsqOH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/BiJaXQsqOH-600.jpeg" alt="Slide 133" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-WlXD">< Previous</button><output data-carousel-output="carouscroll-id-WlXD"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-WlXD">Next ></button></div></is-land></browser-window></div>
<p>JavaScript Frameworks and Web Components can work together—though some are more web component friendly than others.</p>
<p>In some respects it feels like the web platform chased the early clientside rendered, single page application (SPA) architectural vibes of React, Ember, Angular, and others. The more nimble frameworks have pivoted away from those mistakes. The platform will always be a bit behind those that live further down the stack (and importantly, informed by their work) but with the right amount of patience can offer improved performance and long-term maintenance potential.</p>
<ul>
<li>If you want to go all-in on Web Components, have a look at these answers to the server-rendered web component question:
<ul>
<li><a href="https://lit.dev/"><img alt="IndieWeb Avatar for https://lit.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Flit.dev%2F/" width="60" height="60" class="z-avatar" />Lit</a></li>
<li><a href="https://enhance.dev/"><img alt="IndieWeb Avatar for https://enhance.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fenhance.dev%2F/" width="60" height="60" class="z-avatar" />Enhance</a></li>
<li><a href="https://www.11ty.dev/docs/languages/webc/"><img alt="IndieWeb Avatar for https://www.11ty.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fwww.11ty.dev%2F/" width="60" height="60" class="z-avatar" />WebC</a></li>
</ul>
</li>
<li>React has the worst <a href="https://custom-elements-everywhere.com/"><code>custom-elements-everywhere.com</code></a> compatibility test score. If you’re deep in React-world, look at using <a href="https://preactjs.com/"><img alt="IndieWeb Avatar for https://preactjs.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fpreactjs.com%2F/" width="60" height="60" class="z-avatar" />Preact</a> to provide a more future-friendly and compatible experience.</li>
<li>Solid, Angular, Svelte, and Vue support web components as a compilation target too.</li>
</ul>
<h2 id="a-webc-example" tabindex="-1">A WebC Example <a href="https://www.zachleat.com/web/good-bad-web-components/#a-webc-example" aria-hidden="true" class="direct-link">#</a></h2>
<p>I built <a href="https://www.11ty.dev/docs/languages/webc/">WebC</a> so I guess I can now pivot this post to show you a small example of the rendered output you get when using it.</p>
<p>Consider a content template file <code>index.webc</code>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>2<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span></code></pre>
<h3 id="classical-html" tabindex="-1">Classical HTML <a href="https://www.zachleat.com/web/good-bad-web-components/#classical-html" aria-hidden="true" class="direct-link">#</a></h3>
<p>Next, we’ll add a component definition for <code>my-counter</code> to <code>_components/my-counter.webc</code>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span> <span class="token attr-name"><span class="token namespace">webc:</span>scoped</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">:host button</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"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span></code></pre>
<p>WebC will render <code>index.webc</code> as:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">.wrfp4zhxg button</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"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</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>wrfp4zhxg<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span>1<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</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>wrfp4zhxg<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span>2<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span></code></pre>
<h3 id="declarative-shadow-dom-1" tabindex="-1">Declarative Shadow DOM <a href="https://www.zachleat.com/web/good-bad-web-components/#declarative-shadow-dom-1" aria-hidden="true" class="direct-link">#</a></h3>
<p>You can also modify your component definition (<code>_components/my-counter.webc</code>) to use Declarative Shadow DOM, avoiding the issue of repeating your Shadow DOM template in your content:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span> <span class="token attr-name">shadowrootmode</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>open<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">button</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"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span> <span class="token attr-name"><span class="token namespace">webc:</span>raw</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span></code></pre>
<p>WebC will now server-render <code>index.webc</code> as:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span> <span class="token attr-name">shadowrootmode</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>open<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">button</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"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span>
1
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-counter</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span> <span class="token attr-name">shadowrootmode</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>open<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
<span class="token selector">button</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"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>slot</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span>
2
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-counter</span><span class="token punctuation">></span></span></code></pre>
<p>This server-applied abstraction offers a variety of benefits over earlier approaches:</p>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script><div><browser-window shadow="" flush=""><is-land on:idle="" on:visible=""><template data-island=""><script type="module" src="https://www.zachleat.com/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-gA4y" class="carouscroll"><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/h1QXNbTe3L-600.avif 600w, https://www.zachleat.com/img/built/h1QXNbTe3L-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/h1QXNbTe3L-600.jpeg 600w, https://www.zachleat.com/img/built/h1QXNbTe3L-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/h1QXNbTe3L-600.jpeg" alt="Slide 138" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/Ttz7Pa8QOq-600.avif 600w, https://www.zachleat.com/img/built/Ttz7Pa8QOq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/Ttz7Pa8QOq-600.jpeg 600w, https://www.zachleat.com/img/built/Ttz7Pa8QOq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/Ttz7Pa8QOq-600.jpeg" alt="Slide 139" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/I_YTIhetfD-600.avif 600w, https://www.zachleat.com/img/built/I_YTIhetfD-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/I_YTIhetfD-600.jpeg 600w, https://www.zachleat.com/img/built/I_YTIhetfD-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/I_YTIhetfD-600.jpeg" alt="Slide 140" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/PIEV_sKsRK-600.avif 600w, https://www.zachleat.com/img/built/PIEV_sKsRK-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/PIEV_sKsRK-600.jpeg 600w, https://www.zachleat.com/img/built/PIEV_sKsRK-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/PIEV_sKsRK-600.jpeg" alt="Slide 141" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/kg453c0O_c-600.avif 600w, https://www.zachleat.com/img/built/kg453c0O_c-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/kg453c0O_c-600.jpeg 600w, https://www.zachleat.com/img/built/kg453c0O_c-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/kg453c0O_c-600.jpeg" alt="Slide 142" width="1000" height="562" /></picture><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/_EkeAJDqpO-600.avif 600w, https://www.zachleat.com/img/built/_EkeAJDqpO-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/_EkeAJDqpO-600.jpeg 600w, https://www.zachleat.com/img/built/_EkeAJDqpO-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw" /><img loading="lazy" decoding="async" src="https://www.zachleat.com/img/built/_EkeAJDqpO-600.jpeg" alt="Slide 143" width="1000" height="562" /></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled="" data-carousel-previous="carouscroll-id-gA4y">< Previous</button><output data-carousel-output="carouscroll-id-gA4y"></output><button type="button" disabled="" data-carousel-next="carouscroll-id-gA4y">Next ></button></div></is-land></browser-window></div>
<p>If you want to learn more, you can try out the <a href="https://github.com/11ty/eleventy-base-webc"><code>eleventy-base-webc</code> starter project</a> or for a deeper dive into progressive enhancement strategies have a look at <a href="https://demo-webc-image-compare.netlify.app/">Seven Progressive Enhancement Recipes using Eleventy WebC Image Comparison Components</a>.</p>
<h2 id="appendices" tabindex="-1">Appendices <a href="https://www.zachleat.com/web/good-bad-web-components/#appendices" aria-hidden="true" class="direct-link">#</a></h2>
<!-- ### Full Slide Deck
<details>
<summary>Expand to view the entire slide deck (152 slides)</summary>
<script type="module" src="/static/browser-window.js"></script><div><browser-window shadow flush><is-land on:idle on:visible><template data-island><script type="module" src="/static/carouscroll.js"></script></template><carou-scroll tabindex="0" id="carouscroll-id-mjXW" class="carouscroll"><picture><source type="image/avif" srcset="/img/built/cCq1crdetf-600.avif 600w, /img/built/cCq1crdetf-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/cCq1crdetf-600.jpeg 600w, /img/built/cCq1crdetf-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/cCq1crdetf-600.jpeg" alt="Slide 2" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/iw2E4B3JRF-600.avif 600w, /img/built/iw2E4B3JRF-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/iw2E4B3JRF-600.jpeg 600w, /img/built/iw2E4B3JRF-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/iw2E4B3JRF-600.jpeg" alt="Slide 6" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/-HP3nVOPmP-600.avif 600w, /img/built/-HP3nVOPmP-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/-HP3nVOPmP-600.jpeg 600w, /img/built/-HP3nVOPmP-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/-HP3nVOPmP-600.jpeg" alt="Slide 7" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/lxmJvyKONW-600.avif 600w, /img/built/lxmJvyKONW-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/lxmJvyKONW-600.jpeg 600w, /img/built/lxmJvyKONW-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/lxmJvyKONW-600.jpeg" alt="Slide 8" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/X7wCO_4niH-600.avif 600w, /img/built/X7wCO_4niH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/X7wCO_4niH-600.jpeg 600w, /img/built/X7wCO_4niH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/X7wCO_4niH-600.jpeg" alt="Slide 10" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/xiYNFxVCiu-600.avif 600w, /img/built/xiYNFxVCiu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/xiYNFxVCiu-600.jpeg 600w, /img/built/xiYNFxVCiu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/xiYNFxVCiu-600.jpeg" alt="Slide 17" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/D2zwMnH40m-600.avif 600w, /img/built/D2zwMnH40m-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/D2zwMnH40m-600.jpeg 600w, /img/built/D2zwMnH40m-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/D2zwMnH40m-600.jpeg" alt="Slide 18" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/bN13qqkj7a-600.avif 600w, /img/built/bN13qqkj7a-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/bN13qqkj7a-600.jpeg 600w, /img/built/bN13qqkj7a-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/bN13qqkj7a-600.jpeg" alt="Slide 19" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/ve5hpYQ_3A-600.avif 600w, /img/built/ve5hpYQ_3A-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/ve5hpYQ_3A-600.jpeg 600w, /img/built/ve5hpYQ_3A-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/ve5hpYQ_3A-600.jpeg" alt="Slide 20" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/iFpddCG3cU-600.avif 600w, /img/built/iFpddCG3cU-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/iFpddCG3cU-600.jpeg 600w, /img/built/iFpddCG3cU-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/iFpddCG3cU-600.jpeg" alt="Slide 21" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/dw3vs-6Niu-600.avif 600w, /img/built/dw3vs-6Niu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/dw3vs-6Niu-600.jpeg 600w, /img/built/dw3vs-6Niu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/dw3vs-6Niu-600.jpeg" alt="Slide 22" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Q3nnjNFc9u-600.avif 600w, /img/built/Q3nnjNFc9u-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Q3nnjNFc9u-600.jpeg 600w, /img/built/Q3nnjNFc9u-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Q3nnjNFc9u-600.jpeg" alt="Slide 23" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/k9m1Msdohq-600.avif 600w, /img/built/k9m1Msdohq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/k9m1Msdohq-600.jpeg 600w, /img/built/k9m1Msdohq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/k9m1Msdohq-600.jpeg" alt="Slide 24" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/IhurFzntot-600.avif 600w, /img/built/IhurFzntot-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/IhurFzntot-600.jpeg 600w, /img/built/IhurFzntot-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/IhurFzntot-600.jpeg" alt="Slide 25" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/LpHlxyNtQi-600.avif 600w, /img/built/LpHlxyNtQi-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/LpHlxyNtQi-600.jpeg 600w, /img/built/LpHlxyNtQi-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/LpHlxyNtQi-600.jpeg" alt="Slide 26" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/8GhqThR1Z0-600.avif 600w, /img/built/8GhqThR1Z0-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/8GhqThR1Z0-600.jpeg 600w, /img/built/8GhqThR1Z0-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/8GhqThR1Z0-600.jpeg" alt="Slide 27" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/7y-BhuCZxH-600.avif 600w, /img/built/7y-BhuCZxH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/7y-BhuCZxH-600.jpeg 600w, /img/built/7y-BhuCZxH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/7y-BhuCZxH-600.jpeg" alt="Slide 29" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/vs3FO0SrQF-600.avif 600w, /img/built/vs3FO0SrQF-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/vs3FO0SrQF-600.jpeg 600w, /img/built/vs3FO0SrQF-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/vs3FO0SrQF-600.jpeg" alt="Slide 31" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/SZaFAM-Qy--600.avif 600w, /img/built/SZaFAM-Qy--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/SZaFAM-Qy--600.jpeg 600w, /img/built/SZaFAM-Qy--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/SZaFAM-Qy--600.jpeg" alt="Slide 33" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/YrtBUBBney-600.avif 600w, /img/built/YrtBUBBney-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/YrtBUBBney-600.jpeg 600w, /img/built/YrtBUBBney-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/YrtBUBBney-600.jpeg" alt="Slide 34" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/xeF6BJgIqI-600.avif 600w, /img/built/xeF6BJgIqI-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/xeF6BJgIqI-600.jpeg 600w, /img/built/xeF6BJgIqI-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/xeF6BJgIqI-600.jpeg" alt="Slide 35" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/OViECyPqdn-600.avif 600w, /img/built/OViECyPqdn-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/OViECyPqdn-600.jpeg 600w, /img/built/OViECyPqdn-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/OViECyPqdn-600.jpeg" alt="Slide 36" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/wNrjAwZIsZ-600.avif 600w, /img/built/wNrjAwZIsZ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/wNrjAwZIsZ-600.jpeg 600w, /img/built/wNrjAwZIsZ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/wNrjAwZIsZ-600.jpeg" alt="Slide 37" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/1ozwBECYxt-600.avif 600w, /img/built/1ozwBECYxt-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/1ozwBECYxt-600.jpeg 600w, /img/built/1ozwBECYxt-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/1ozwBECYxt-600.jpeg" alt="Slide 38" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/QM6jfP1TqA-600.avif 600w, /img/built/QM6jfP1TqA-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/QM6jfP1TqA-600.jpeg 600w, /img/built/QM6jfP1TqA-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/QM6jfP1TqA-600.jpeg" alt="Slide 39" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/vtgCYbZxbH-600.avif 600w, /img/built/vtgCYbZxbH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/vtgCYbZxbH-600.jpeg 600w, /img/built/vtgCYbZxbH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/vtgCYbZxbH-600.jpeg" alt="Slide 40" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/mYJg4fVJon-600.avif 600w, /img/built/mYJg4fVJon-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/mYJg4fVJon-600.jpeg 600w, /img/built/mYJg4fVJon-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/mYJg4fVJon-600.jpeg" alt="Slide 41" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/RuM01_HJKB-600.avif 600w, /img/built/RuM01_HJKB-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/RuM01_HJKB-600.jpeg 600w, /img/built/RuM01_HJKB-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/RuM01_HJKB-600.jpeg" alt="Slide 42" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/UD-Pk7-d8J-600.avif 600w, /img/built/UD-Pk7-d8J-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/UD-Pk7-d8J-600.jpeg 600w, /img/built/UD-Pk7-d8J-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/UD-Pk7-d8J-600.jpeg" alt="Slide 43" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Rqc47Zw_el-600.avif 600w, /img/built/Rqc47Zw_el-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Rqc47Zw_el-600.jpeg 600w, /img/built/Rqc47Zw_el-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Rqc47Zw_el-600.jpeg" alt="Slide 44" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/03J4f_6j1Q-600.avif 600w, /img/built/03J4f_6j1Q-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/03J4f_6j1Q-600.jpeg 600w, /img/built/03J4f_6j1Q-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/03J4f_6j1Q-600.jpeg" alt="Slide 45" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/PRLb8-wkPt-600.avif 600w, /img/built/PRLb8-wkPt-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/PRLb8-wkPt-600.jpeg 600w, /img/built/PRLb8-wkPt-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/PRLb8-wkPt-600.jpeg" alt="Slide 46" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/oLlnpetl_5-600.avif 600w, /img/built/oLlnpetl_5-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/oLlnpetl_5-600.jpeg 600w, /img/built/oLlnpetl_5-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/oLlnpetl_5-600.jpeg" alt="Slide 47" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/W4u9kpmrAC-600.avif 600w, /img/built/W4u9kpmrAC-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/W4u9kpmrAC-600.jpeg 600w, /img/built/W4u9kpmrAC-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/W4u9kpmrAC-600.jpeg" alt="Slide 48" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/r6PfLTyuYx-600.avif 600w, /img/built/r6PfLTyuYx-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/r6PfLTyuYx-600.jpeg 600w, /img/built/r6PfLTyuYx-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/r6PfLTyuYx-600.jpeg" alt="Slide 49" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/dBUXXSGKiX-600.avif 600w, /img/built/dBUXXSGKiX-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/dBUXXSGKiX-600.jpeg 600w, /img/built/dBUXXSGKiX-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/dBUXXSGKiX-600.jpeg" alt="Slide 50" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/1G3NESTZu3-600.avif 600w, /img/built/1G3NESTZu3-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/1G3NESTZu3-600.jpeg 600w, /img/built/1G3NESTZu3-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/1G3NESTZu3-600.jpeg" alt="Slide 51" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Jpy7SZFHzZ-600.avif 600w, /img/built/Jpy7SZFHzZ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Jpy7SZFHzZ-600.jpeg 600w, /img/built/Jpy7SZFHzZ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Jpy7SZFHzZ-600.jpeg" alt="Slide 52" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/x4ZayRJNDy-600.avif 600w, /img/built/x4ZayRJNDy-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/x4ZayRJNDy-600.jpeg 600w, /img/built/x4ZayRJNDy-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/x4ZayRJNDy-600.jpeg" alt="Slide 53" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/WUkkFlYOZT-600.avif 600w, /img/built/WUkkFlYOZT-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/WUkkFlYOZT-600.jpeg 600w, /img/built/WUkkFlYOZT-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/WUkkFlYOZT-600.jpeg" alt="Slide 55" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Il_KV1rxgE-600.avif 600w, /img/built/Il_KV1rxgE-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Il_KV1rxgE-600.jpeg 600w, /img/built/Il_KV1rxgE-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Il_KV1rxgE-600.jpeg" alt="Slide 57" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/arLon9SPW8-600.avif 600w, /img/built/arLon9SPW8-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/arLon9SPW8-600.jpeg 600w, /img/built/arLon9SPW8-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/arLon9SPW8-600.jpeg" alt="Slide 59" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/_0ixHZBRpi-600.avif 600w, /img/built/_0ixHZBRpi-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/_0ixHZBRpi-600.jpeg 600w, /img/built/_0ixHZBRpi-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/_0ixHZBRpi-600.jpeg" alt="Slide 60" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/DJpQRM_jD8-600.avif 600w, /img/built/DJpQRM_jD8-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/DJpQRM_jD8-600.jpeg 600w, /img/built/DJpQRM_jD8-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/DJpQRM_jD8-600.jpeg" alt="Slide 61" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/3uDUEPSxSb-600.avif 600w, /img/built/3uDUEPSxSb-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/3uDUEPSxSb-600.jpeg 600w, /img/built/3uDUEPSxSb-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/3uDUEPSxSb-600.jpeg" alt="Slide 62" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/KKRnxgv5h1-600.avif 600w, /img/built/KKRnxgv5h1-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/KKRnxgv5h1-600.jpeg 600w, /img/built/KKRnxgv5h1-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/KKRnxgv5h1-600.jpeg" alt="Slide 65" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/BK_Yeh4-DB-600.avif 600w, /img/built/BK_Yeh4-DB-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/BK_Yeh4-DB-600.jpeg 600w, /img/built/BK_Yeh4-DB-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/BK_Yeh4-DB-600.jpeg" alt="Slide 66" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/GSKVbjP47b-600.avif 600w, /img/built/GSKVbjP47b-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/GSKVbjP47b-600.jpeg 600w, /img/built/GSKVbjP47b-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/GSKVbjP47b-600.jpeg" alt="Slide 67" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/0713KAe4T_-600.avif 600w, /img/built/0713KAe4T_-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/0713KAe4T_-600.jpeg 600w, /img/built/0713KAe4T_-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/0713KAe4T_-600.jpeg" alt="Slide 68" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/UpUcX_1AH2-600.avif 600w, /img/built/UpUcX_1AH2-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/UpUcX_1AH2-600.jpeg 600w, /img/built/UpUcX_1AH2-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/UpUcX_1AH2-600.jpeg" alt="Slide 69" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/cjin7G9d9C-600.avif 600w, /img/built/cjin7G9d9C-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/cjin7G9d9C-600.jpeg 600w, /img/built/cjin7G9d9C-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/cjin7G9d9C-600.jpeg" alt="Slide 70" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/PaFU_RTmgb-600.avif 600w, /img/built/PaFU_RTmgb-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/PaFU_RTmgb-600.jpeg 600w, /img/built/PaFU_RTmgb-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/PaFU_RTmgb-600.jpeg" alt="Slide 71" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/7vwPpL185L-600.avif 600w, /img/built/7vwPpL185L-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/7vwPpL185L-600.jpeg 600w, /img/built/7vwPpL185L-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/7vwPpL185L-600.jpeg" alt="Slide 72" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/YiT8g_L4nw-600.avif 600w, /img/built/YiT8g_L4nw-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/YiT8g_L4nw-600.jpeg 600w, /img/built/YiT8g_L4nw-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/YiT8g_L4nw-600.jpeg" alt="Slide 73" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/iZrWR2Cvm--600.avif 600w, /img/built/iZrWR2Cvm--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/iZrWR2Cvm--600.jpeg 600w, /img/built/iZrWR2Cvm--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/iZrWR2Cvm--600.jpeg" alt="Slide 74" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/5j6iLPnTzk-600.avif 600w, /img/built/5j6iLPnTzk-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/5j6iLPnTzk-600.jpeg 600w, /img/built/5j6iLPnTzk-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/5j6iLPnTzk-600.jpeg" alt="Slide 75" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/97uMIzR8xY-600.avif 600w, /img/built/97uMIzR8xY-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/97uMIzR8xY-600.jpeg 600w, /img/built/97uMIzR8xY-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/97uMIzR8xY-600.jpeg" alt="Slide 76" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/f0GD15TUVk-600.avif 600w, /img/built/f0GD15TUVk-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/f0GD15TUVk-600.jpeg 600w, /img/built/f0GD15TUVk-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/f0GD15TUVk-600.jpeg" alt="Slide 77" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/z-yoZGE_6u-600.avif 600w, /img/built/z-yoZGE_6u-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/z-yoZGE_6u-600.jpeg 600w, /img/built/z-yoZGE_6u-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/z-yoZGE_6u-600.jpeg" alt="Slide 78" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Ibt7KWcIsQ-600.avif 600w, /img/built/Ibt7KWcIsQ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Ibt7KWcIsQ-600.jpeg 600w, /img/built/Ibt7KWcIsQ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Ibt7KWcIsQ-600.jpeg" alt="Slide 79" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/1ZiMtaSJK5-600.avif 600w, /img/built/1ZiMtaSJK5-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/1ZiMtaSJK5-600.jpeg 600w, /img/built/1ZiMtaSJK5-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/1ZiMtaSJK5-600.jpeg" alt="Slide 80" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/A8UkE0vqdW-600.avif 600w, /img/built/A8UkE0vqdW-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/A8UkE0vqdW-600.jpeg 600w, /img/built/A8UkE0vqdW-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/A8UkE0vqdW-600.jpeg" alt="Slide 81" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/t_5kc0K8Vr-600.avif 600w, /img/built/t_5kc0K8Vr-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/t_5kc0K8Vr-600.jpeg 600w, /img/built/t_5kc0K8Vr-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/t_5kc0K8Vr-600.jpeg" alt="Slide 82" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/CnGjT7WjEV-600.avif 600w, /img/built/CnGjT7WjEV-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/CnGjT7WjEV-600.jpeg 600w, /img/built/CnGjT7WjEV-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/CnGjT7WjEV-600.jpeg" alt="Slide 83" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Oe3lqSWu3o-600.avif 600w, /img/built/Oe3lqSWu3o-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Oe3lqSWu3o-600.jpeg 600w, /img/built/Oe3lqSWu3o-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Oe3lqSWu3o-600.jpeg" alt="Slide 85" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/_9TXsxpwOr-600.avif 600w, /img/built/_9TXsxpwOr-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/_9TXsxpwOr-600.jpeg 600w, /img/built/_9TXsxpwOr-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/_9TXsxpwOr-600.jpeg" alt="Slide 86" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/a9XojxuGcL-600.avif 600w, /img/built/a9XojxuGcL-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/a9XojxuGcL-600.jpeg 600w, /img/built/a9XojxuGcL-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/a9XojxuGcL-600.jpeg" alt="Slide 87" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/j2Hf8oRwoQ-600.avif 600w, /img/built/j2Hf8oRwoQ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/j2Hf8oRwoQ-600.jpeg 600w, /img/built/j2Hf8oRwoQ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/j2Hf8oRwoQ-600.jpeg" alt="Slide 88" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/GXZhA4Eh-T-600.avif 600w, /img/built/GXZhA4Eh-T-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/GXZhA4Eh-T-600.jpeg 600w, /img/built/GXZhA4Eh-T-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/GXZhA4Eh-T-600.jpeg" alt="Slide 89" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/zsGm9Y-NVZ-600.avif 600w, /img/built/zsGm9Y-NVZ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/zsGm9Y-NVZ-600.jpeg 600w, /img/built/zsGm9Y-NVZ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/zsGm9Y-NVZ-600.jpeg" alt="Slide 90" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/BTU_1FbX88-600.avif 600w, /img/built/BTU_1FbX88-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/BTU_1FbX88-600.jpeg 600w, /img/built/BTU_1FbX88-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/BTU_1FbX88-600.jpeg" alt="Slide 91" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/qGyeAo4QFT-600.avif 600w, /img/built/qGyeAo4QFT-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/qGyeAo4QFT-600.jpeg 600w, /img/built/qGyeAo4QFT-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/qGyeAo4QFT-600.jpeg" alt="Slide 92" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/a3Abx7Tuh7-600.avif 600w, /img/built/a3Abx7Tuh7-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/a3Abx7Tuh7-600.jpeg 600w, /img/built/a3Abx7Tuh7-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/a3Abx7Tuh7-600.jpeg" alt="Slide 93" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/OfUv5qhjd--600.avif 600w, /img/built/OfUv5qhjd--1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/OfUv5qhjd--600.jpeg 600w, /img/built/OfUv5qhjd--1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/OfUv5qhjd--600.jpeg" alt="Slide 94" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/t8LQjWworE-600.avif 600w, /img/built/t8LQjWworE-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/t8LQjWworE-600.jpeg 600w, /img/built/t8LQjWworE-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/t8LQjWworE-600.jpeg" alt="Slide 95" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Rqc47Zw_el-600.avif 600w, /img/built/Rqc47Zw_el-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Rqc47Zw_el-600.jpeg 600w, /img/built/Rqc47Zw_el-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Rqc47Zw_el-600.jpeg" alt="Slide 96" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/03J4f_6j1Q-600.avif 600w, /img/built/03J4f_6j1Q-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/03J4f_6j1Q-600.jpeg 600w, /img/built/03J4f_6j1Q-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/03J4f_6j1Q-600.jpeg" alt="Slide 97" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/IylpZeHlQV-600.avif 600w, /img/built/IylpZeHlQV-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/IylpZeHlQV-600.jpeg 600w, /img/built/IylpZeHlQV-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/IylpZeHlQV-600.jpeg" alt="Slide 99" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/2EgiWxv_Xm-600.avif 600w, /img/built/2EgiWxv_Xm-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/2EgiWxv_Xm-600.jpeg 600w, /img/built/2EgiWxv_Xm-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/2EgiWxv_Xm-600.jpeg" alt="Slide 102" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/h1QXNbTe3L-600.avif 600w, /img/built/h1QXNbTe3L-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/h1QXNbTe3L-600.jpeg 600w, /img/built/h1QXNbTe3L-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/h1QXNbTe3L-600.jpeg" alt="Slide 103" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Ttz7Pa8QOq-600.avif 600w, /img/built/Ttz7Pa8QOq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Ttz7Pa8QOq-600.jpeg 600w, /img/built/Ttz7Pa8QOq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Ttz7Pa8QOq-600.jpeg" alt="Slide 104" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/I_YTIhetfD-600.avif 600w, /img/built/I_YTIhetfD-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/I_YTIhetfD-600.jpeg 600w, /img/built/I_YTIhetfD-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/I_YTIhetfD-600.jpeg" alt="Slide 105" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/HlReXLOlvu-600.avif 600w, /img/built/HlReXLOlvu-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/HlReXLOlvu-600.jpeg 600w, /img/built/HlReXLOlvu-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/HlReXLOlvu-600.jpeg" alt="Slide 106" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/NS_rfPaaWs-600.avif 600w, /img/built/NS_rfPaaWs-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/NS_rfPaaWs-600.jpeg 600w, /img/built/NS_rfPaaWs-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/NS_rfPaaWs-600.jpeg" alt="Slide 107" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/I8qUZTNhwO-600.avif 600w, /img/built/I8qUZTNhwO-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/I8qUZTNhwO-600.jpeg 600w, /img/built/I8qUZTNhwO-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/I8qUZTNhwO-600.jpeg" alt="Slide 108" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/nKWR4SaqK8-600.avif 600w, /img/built/nKWR4SaqK8-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/nKWR4SaqK8-600.jpeg 600w, /img/built/nKWR4SaqK8-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/nKWR4SaqK8-600.jpeg" alt="Slide 109" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/gDH3sy9Mi4-600.avif 600w, /img/built/gDH3sy9Mi4-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/gDH3sy9Mi4-600.jpeg 600w, /img/built/gDH3sy9Mi4-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/gDH3sy9Mi4-600.jpeg" alt="Slide 110" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/EoZQ-snPXP-600.avif 600w, /img/built/EoZQ-snPXP-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/EoZQ-snPXP-600.jpeg 600w, /img/built/EoZQ-snPXP-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/EoZQ-snPXP-600.jpeg" alt="Slide 112" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/BqTB2nSo6M-600.avif 600w, /img/built/BqTB2nSo6M-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/BqTB2nSo6M-600.jpeg 600w, /img/built/BqTB2nSo6M-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/BqTB2nSo6M-600.jpeg" alt="Slide 114" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/sWWUIacQo8-600.avif 600w, /img/built/sWWUIacQo8-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/sWWUIacQo8-600.jpeg 600w, /img/built/sWWUIacQo8-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/sWWUIacQo8-600.jpeg" alt="Slide 115" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/RoHH_hr66c-600.avif 600w, /img/built/RoHH_hr66c-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/RoHH_hr66c-600.jpeg 600w, /img/built/RoHH_hr66c-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/RoHH_hr66c-600.jpeg" alt="Slide 118" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Oh2OT5g4iS-600.avif 600w, /img/built/Oh2OT5g4iS-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Oh2OT5g4iS-600.jpeg 600w, /img/built/Oh2OT5g4iS-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Oh2OT5g4iS-600.jpeg" alt="Slide 119" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/pvOPg6HUba-600.avif 600w, /img/built/pvOPg6HUba-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/pvOPg6HUba-600.jpeg 600w, /img/built/pvOPg6HUba-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/pvOPg6HUba-600.jpeg" alt="Slide 120" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/OWZR9ph_Gq-600.avif 600w, /img/built/OWZR9ph_Gq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/OWZR9ph_Gq-600.jpeg 600w, /img/built/OWZR9ph_Gq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/OWZR9ph_Gq-600.jpeg" alt="Slide 121" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/pmGrYj_hRB-600.avif 600w, /img/built/pmGrYj_hRB-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/pmGrYj_hRB-600.jpeg 600w, /img/built/pmGrYj_hRB-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/pmGrYj_hRB-600.jpeg" alt="Slide 123" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/USSZEWy7nJ-600.avif 600w, /img/built/USSZEWy7nJ-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/USSZEWy7nJ-600.jpeg 600w, /img/built/USSZEWy7nJ-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/USSZEWy7nJ-600.jpeg" alt="Slide 124" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/3Ryiemf9Om-600.avif 600w, /img/built/3Ryiemf9Om-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/3Ryiemf9Om-600.jpeg 600w, /img/built/3Ryiemf9Om-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/3Ryiemf9Om-600.jpeg" alt="Slide 125" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/bch16kYPbE-600.avif 600w, /img/built/bch16kYPbE-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/bch16kYPbE-600.jpeg 600w, /img/built/bch16kYPbE-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/bch16kYPbE-600.jpeg" alt="Slide 126" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/mlzSEboCIK-600.avif 600w, /img/built/mlzSEboCIK-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/mlzSEboCIK-600.jpeg 600w, /img/built/mlzSEboCIK-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/mlzSEboCIK-600.jpeg" alt="Slide 128" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/F9BCbsN-et-600.avif 600w, /img/built/F9BCbsN-et-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/F9BCbsN-et-600.jpeg 600w, /img/built/F9BCbsN-et-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/F9BCbsN-et-600.jpeg" alt="Slide 129" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/pNjXtuoZG5-600.avif 600w, /img/built/pNjXtuoZG5-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/pNjXtuoZG5-600.jpeg 600w, /img/built/pNjXtuoZG5-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/pNjXtuoZG5-600.jpeg" alt="Slide 130" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/7UTkFfYCSG-600.avif 600w, /img/built/7UTkFfYCSG-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/7UTkFfYCSG-600.jpeg 600w, /img/built/7UTkFfYCSG-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/7UTkFfYCSG-600.jpeg" alt="Slide 132" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/BiJaXQsqOH-600.avif 600w, /img/built/BiJaXQsqOH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/BiJaXQsqOH-600.jpeg 600w, /img/built/BiJaXQsqOH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/BiJaXQsqOH-600.jpeg" alt="Slide 133" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/QeFHTRri7s-600.avif 600w, /img/built/QeFHTRri7s-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/QeFHTRri7s-600.jpeg 600w, /img/built/QeFHTRri7s-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/QeFHTRri7s-600.jpeg" alt="Slide 134" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/9fO7NU-AQ_-600.avif 600w, /img/built/9fO7NU-AQ_-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/9fO7NU-AQ_-600.jpeg 600w, /img/built/9fO7NU-AQ_-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/9fO7NU-AQ_-600.jpeg" alt="Slide 135" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/ZT_S_9cgLz-600.avif 600w, /img/built/ZT_S_9cgLz-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/ZT_S_9cgLz-600.jpeg 600w, /img/built/ZT_S_9cgLz-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/ZT_S_9cgLz-600.jpeg" alt="Slide 136" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/SGkoqLwVdf-600.avif 600w, /img/built/SGkoqLwVdf-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/SGkoqLwVdf-600.jpeg 600w, /img/built/SGkoqLwVdf-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/SGkoqLwVdf-600.jpeg" alt="Slide 137" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/h1QXNbTe3L-600.avif 600w, /img/built/h1QXNbTe3L-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/h1QXNbTe3L-600.jpeg 600w, /img/built/h1QXNbTe3L-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/h1QXNbTe3L-600.jpeg" alt="Slide 138" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Ttz7Pa8QOq-600.avif 600w, /img/built/Ttz7Pa8QOq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Ttz7Pa8QOq-600.jpeg 600w, /img/built/Ttz7Pa8QOq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Ttz7Pa8QOq-600.jpeg" alt="Slide 139" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/I_YTIhetfD-600.avif 600w, /img/built/I_YTIhetfD-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/I_YTIhetfD-600.jpeg 600w, /img/built/I_YTIhetfD-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/I_YTIhetfD-600.jpeg" alt="Slide 140" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/PIEV_sKsRK-600.avif 600w, /img/built/PIEV_sKsRK-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/PIEV_sKsRK-600.jpeg 600w, /img/built/PIEV_sKsRK-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/PIEV_sKsRK-600.jpeg" alt="Slide 141" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/kg453c0O_c-600.avif 600w, /img/built/kg453c0O_c-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/kg453c0O_c-600.jpeg 600w, /img/built/kg453c0O_c-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/kg453c0O_c-600.jpeg" alt="Slide 142" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/_EkeAJDqpO-600.avif 600w, /img/built/_EkeAJDqpO-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/_EkeAJDqpO-600.jpeg 600w, /img/built/_EkeAJDqpO-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/_EkeAJDqpO-600.jpeg" alt="Slide 143" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/Oo7LTMQbrp-600.avif 600w, /img/built/Oo7LTMQbrp-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/Oo7LTMQbrp-600.jpeg 600w, /img/built/Oo7LTMQbrp-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/Oo7LTMQbrp-600.jpeg" alt="Slide 144" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/I1syFpCrjA-600.avif 600w, /img/built/I1syFpCrjA-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/I1syFpCrjA-600.jpeg 600w, /img/built/I1syFpCrjA-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/I1syFpCrjA-600.jpeg" alt="Slide 148" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/hpqdGE_oMq-600.avif 600w, /img/built/hpqdGE_oMq-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/hpqdGE_oMq-600.jpeg 600w, /img/built/hpqdGE_oMq-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/hpqdGE_oMq-600.jpeg" alt="Slide 149" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/NjZn7g21ei-600.avif 600w, /img/built/NjZn7g21ei-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/NjZn7g21ei-600.jpeg 600w, /img/built/NjZn7g21ei-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/NjZn7g21ei-600.jpeg" alt="Slide 150" width="1000" height="562"></picture><picture><source type="image/avif" srcset="/img/built/N0JWRjIrIH-600.avif 600w, /img/built/N0JWRjIrIH-1000.avif 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><source type="image/jpeg" srcset="/img/built/N0JWRjIrIH-600.jpeg 600w, /img/built/N0JWRjIrIH-1000.jpeg 1000w" sizes="(min-width: 106.25em) 82.75em, (min-width: 61.25em) calc(91.43vw - 13.25em), 100vw"><img loading="lazy" decoding="async" src="/img/built/N0JWRjIrIH-600.jpeg" alt="Slide 152" width="1000" height="562"></picture></carou-scroll><div class="carouscroll-meta"><button type="button" disabled data-carousel-previous="carouscroll-id-mjXW">< Previous</button><output data-carousel-output="carouscroll-id-mjXW"></output><button type="button" disabled data-carousel-next="carouscroll-id-mjXW">Next ></button></browser-window></div></is-land></div>
</details> -->
<p>This talk was given at four different events in 2023 (some of which videos are available):</p>
<ul>
<li><a href="https://www.zachleat.com/web/jsheroes/">JS Heroes</a> (May 2023)</li>
<li><a href="https://www.zachleat.com/web/smashingconf/2023/video/">Smashing Conference Freiburg</a> (September 2023)</li>
<li><a href="https://www.zachleat.com/web/jsnation/">JS Nation</a> (May 2023)</li>
<li><a href="https://www.zachleat.com/web/stanford-webcamp/">Stanford Web Camp</a> (May 2023)</li>
</ul>
Join the 11ty International Symposium on Making Web Sites Real Good (it’s an 11ty Conference)2024-01-30T00:00:00Zhttp://www.zachleat.com/web/11tyconf/
<p><strong>Update: <a href="https://conf.11ty.dev/#register">Registration is now open! Join us!</a></strong></p>
<p>On May 9, 2024 (this year!) we’ll gather together our extremely-online community of webcraftspeople to share what we know! Join us at the <a href="https://conf.11ty.dev/"><strong>11ty International Symposium on Making Web Sites Real Good</strong></a>.</p>
<p>More on the 11ty Blog:</p>
<ul>
<li><em><a href="https://www.11ty.dev/blog/conference/">We’re running an 11ty Conference!</a> (2024 Jan 30)</em></li>
<li><em><a href="https://www.11ty.dev/blog/register-for-11ty-conf/">Register now for the 11ty Conference!</a> (2024 Feb 16)</em></li>
</ul>
TheJam.dev 20242024-01-24T00:00:00Zhttp://www.zachleat.com/web/thejam/
<p>This is an event post. If you’d like to watch the talk, you can do so at: <a href="https://www.zachleat.com/web/eleventy-v3-esm/">Lessons learned moving Eleventy from CommonJS to ESM</a>.</p>
Exploring the Bounds of Jamstack on What the Jam2024-01-23T00:00:00Zhttp://www.zachleat.com/web/what-the-jam/
<blockquote>
<p>Welcome to a new episode of "What the Jam," the show that dives deep into the fascinating realm of Jamstack, bringing you face-to-face with its most influential figures. In this episode, we're excited to feature Zach Leatherman, the creative mind behind Eleventy and a key figure in the Jamstack community. Zach takes us through his journey with Eleventy, discussing the inspirations and challenges behind creating one of the most popular static site generators. He shares his insights on the evolution of Jamstack, its role in reshaping the landscape of web development, and a path forward. Have your say at <a href="https://thefutureofjamstack.org/">https://thefutureofjamstack.org</a></p>
</blockquote>
<script type="module" src="https://www.zachleat.com/static/js/offviewport.js"></script>
<div>
<off-viewport>
<youtube-lite-player>
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=BPKIU9Ow_ZU" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="BPKIU9Ow_ZU" js-api="" playlabel="Play: Exploring the Bounds of Jamstack on What the Jam" 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%3DBPKIU9Ow_ZU/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-lite-player>
</off-viewport>
<youtube-link href="https://youtube.com/watch?v=BPKIU9Ow_ZU"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=BPKIU9Ow_ZU" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Exploring the Bounds of Jamstack on What the Jam</em></a></youtube-link>
</div>
<h2 id="searchable-transcript" tabindex="-1">Searchable Transcript <a href="https://www.zachleat.com/web/what-the-jam/#searchable-transcript" aria-hidden="true" class="direct-link">#</a></h2>
<div><youtube-deep-link videoid="BPKIU9Ow_ZU"><style>
youtube-deep-link [data-offset] {
display: inline;
text-align: left;
margin-inline-end: .25em;
}
youtube-deep-link [data-offset] button {
display: inline-block;
font: inherit;
font-size: 80%;
border: none;
border-radius: 0.3em;
background: inherit;
font-family: Consolas, Monaco, monospace;
margin-inline-end: .25em;
line-height: 1.2;
color: #444;
background: #eee;
vertical-align: text-top;
padding: 0.15em 0.3em;
}
youtube-deep-link [data-offset].active {
background-color: #ffc;
}
youtube-deep-link:defined [data-offset]:hover,
youtube-deep-link:defined [data-offset]:focus {
cursor: pointer;
text-decoration: underline;
}
</style>
<br /><br /><span data-offset="0"><button type="button">00:00</button>And I think that if Jamstack can succeed,
it needs to have a clear definition of what</span><span data-offset="7"><button type="button">00:07</button>it is and what it isn't. And if
it's just going to be this vague,</span><span data-offset="12"><button type="button">00:12</button>all encompassing umbrella term, then
Jamstack will. Fall and there's no use</span><span data-offset="18"><button type="button">00:18</button>to keeping it around it has no educational
purpose It will have no technical purpose.</span><br /><br /><span data-offset="24"><button type="button">00:24</button>It won't mean anything, so if you want
Jamstack to succeed it needs to have</span><span data-offset="29"><button type="button">00:29</button>a clear definition it needs to take hard
stances on things and draw some hard lines</span><span data-offset="36"><button type="button">00:36</button>because that's The unique benefits of JAMstack
have those hard lines. This is What The JAM,</span><span data-offset="42"><button type="button">00:42</button>an interview series where we're
finding out what's next for JAMstack.</span><br /><br /><span data-offset="46"><button type="button">00:46</button>Find out more at thefutureofJAMstack. org. I'm
really excited to have the creator of Eleventy</span><span data-offset="52"><button type="button">00:52</button>and. Handed many pies and Jamstack, Zach
Leatherman here today. Welcome Zach. Yeah.</span><span data-offset="60"><button type="button">01:00</button>Thanks. Thanks for having me. I'm excited
to be here. Zach, what does Jamstack mean</span><span data-offset="68"><button type="button">01:08</button>to you? Oh my goodness. Yeah. Yeah. There's
been a lot of definitions over the years.</span><br /><br /><span data-offset="72"><button type="button">01:12</button>Um, for me, it's build time rendering. It's been
sort of build time rendering from the beginning.</span><span data-offset="78"><button type="button">01:18</button>You're pretty generating all of your markup
and source files and you have a folder of,</span><span data-offset="85"><button type="button">01:25</button>uh, content. That you can, and assets that you can
upload to your web server. Um, and I think maybe</span><span data-offset="92"><button type="button">01:32</button>over the years, I've also sort of included the,
uh, continuous integration server piece of it.</span><br /><br /><span data-offset="99"><button type="button">01:39</button>So the get workflow where you commit to get,
and it automatically triggers a new bill. Um,</span><span data-offset="106"><button type="button">01:46</button>I think that that can also, that's. Maybe
another piece of it that I really like,</span><span data-offset="112"><button type="button">01:52</button>but maybe that's not baseline Jamstack,
that's an enhancement off, off the, uh,</span><span data-offset="117"><button type="button">01:57</button>the sort of, yeah, the baseline
experience of just having static</span><br /><br /><span data-offset="121"><button type="button">02:01</button>HTML files that you can upload to
a web server and host very easily.</span><span data-offset="125"><button type="button">02:05</button>And it's very portable. Yeah, it's, it's a good
question of there's different lines here of,</span><span data-offset="130"><button type="button">02:10</button>of what is core Jamstack. What's a best practice
and then what are things that work well with a</span><span data-offset="139"><button type="button">02:19</button>Jamstack? But aren't necessarily Jamstack and
I think those three things Often get blended</span><br /><br /><span data-offset="146"><button type="button">02:26</button>into a soup and it's hard to pull them apart
Like there I think yeah, you're saying the the</span><span data-offset="154"><button type="button">02:34</button>CICD get kind of workflows of best practices
What would you put in those other categories?</span><span data-offset="164"><button type="button">02:44</button>Uh, what do you mean by other categories? So
there's like core JAMstack, um, best practices,</span><span data-offset="171"><button type="button">02:51</button>and then not JAMstack, but works well
with JAMstack. Yeah, I think that, uh,</span><br /><br /><span data-offset="177"><button type="button">02:57</button>yeah, what I mentioned is, uh, just core
JAMstack has A folder full of files, um,</span><span data-offset="184"><button type="button">03:04</button>sort of Jamstack enhancements
or Jamstack adjacent things, um,</span><span data-offset="189"><button type="button">03:09</button>is the sort of the workflow process of having
an automatic deploy, which I think is great.</span><span data-offset="195"><button type="button">03:15</button>I love that feature. I think serverless is maybe
something where it gets into. Um, more Jamstack</span><br /><br /><span data-offset="202"><button type="button">03:22</button>distant, but Jamstack, still Jamstack adjacent.
Um, but if a host didn't have a serverless, um,</span><span data-offset="210"><button type="button">03:30</button>uh, feature built in, I wouldn't consider
that not to be a Jamstack host. I mean,</span><span data-offset="215"><button type="button">03:35</button>it really comes down to if you are a
static host, you are a Jamstack host.</span><span data-offset="220"><button type="button">03:40</button>I mean, it's, it's very interesting
because I remember from my personal</span><br /><br /><span data-offset="223"><button type="button">03:43</button>website. Sort of back in the day, as I made
a transition from WordPress to Jekyll many,</span><span data-offset="229"><button type="button">03:49</button>many years ago, um, I was still hosting
static files on a PHP application server</span><span data-offset="237"><button type="button">03:57</button>and I was paying for PHP application server
prices, but just hosting static files.</span><span data-offset="243"><button type="button">04:03</button>And, uh, and I, and I could see my host
start to differentiate between the two.</span><br /><br /><span data-offset="248"><button type="button">04:08</button>I was using nearly free speech at the time
and I still have some things on them, but,</span><span data-offset="254"><button type="button">04:14</button>um, But yeah, they sort of went through
this, this milestone of divorcing the PHP</span><span data-offset="261"><button type="button">04:21</button>aspect from the static hosting asset aspect, and
actually had different pricing models for each.</span><span data-offset="267"><button type="button">04:27</button>And so it became easier and cheaper for both the
host and the person paying the host bills me, um,</span><br /><br /><span data-offset="277"><button type="button">04:37</button>to Use a static site and I had to worry about
so many, so like less architecture pieces. Um,</span><span data-offset="284"><button type="button">04:44</button>WordPress went through a very, very
hard time for a while. Um, where,</span><span data-offset="289"><button type="button">04:49</button>yeah, I mean, WordPress instances were
getting hacked all the time and there</span><span data-offset="293"><button type="button">04:53</button>was a lot of vulnerabilities and you
had to keep your WordPress instance.</span><br /><br /><span data-offset="297"><button type="button">04:57</button>Updated. You had to watch those updates too,
because if you weren't good about updating your</span><span data-offset="302"><button type="button">05:02</button>instance, then your website could get hacked. And
I think at one point my website did get hacked,</span><span data-offset="307"><button type="button">05:07</button>um, when I was on WordPress. Um, I mean, it wasn't
like a big deal because they just injected just</span><span data-offset="314"><button type="button">05:14</button>some. I don't remember what it was, but, uh,
yeah, some extra JavaScript onto my pages.</span><br /><br /><span data-offset="319"><button type="button">05:19</button>And this was probably 15 years ago, but, um, yeah,
I think at that point I was like, I need to get</span><span data-offset="325"><button type="button">05:25</button>off of this and get to something where I don't
need to worry about it at all, and that is. Yeah,</span><span data-offset="331"><button type="button">05:31</button>really what the static, uh, Jamstack experience
gives you. Interesting. Was that your journey</span><span data-offset="336"><button type="button">05:36</button>into static and Jamstack, that experience of
getting hacked and the pricing model changed?</span><br /><br /><span data-offset="342"><button type="button">05:42</button>That, that, that was the push. Yeah. I
mean, it wasn't a huge price difference,</span><span data-offset="348"><button type="button">05:48</button>but it did sort of indicate to me
that from a hosting perspective,</span><span data-offset="354"><button type="button">05:54</button>they were thinking about it much differently.
Yeah. Um, and. It was cheaper, yes, but it</span><span data-offset="360"><button type="button">06:00</button>was still less than 5 a month or something. It
wasn't like a, a huge, huge price difference,</span><br /><br /><span data-offset="365"><button type="button">06:05</button>but it did to me indicate that there is an at
scale difference between the hosting company</span><span data-offset="371"><button type="button">06:11</button>paying for application servers and PHP to run, um,
versus just, uh, having a web server by itself.</span><span data-offset="380"><button type="button">06:20</button>Yeah. Yeah, totally. Like their cost
structure, like I mentioned, they had</span><span data-offset="384"><button type="button">06:24</button>VPSs set up and. They have to have a certain
amount of resource allocated to each PHP site,</span><br /><br /><span data-offset="391"><button type="button">06:31</button>which isn't necessarily true with a static
site. You can do more of a shared pool. Um,</span><span data-offset="398"><button type="button">06:38</button>and that's not necessarily a bad thing either.
Um, in the way that it would be with a PHP site.</span><span data-offset="408"><button type="button">06:48</button>Yeah, absolutely. And, and you can kind of see
that in what GitHub offers on their free tier too,</span><span data-offset="414"><button type="button">06:54</button>which is kind of funny. Um, and I think that's how
a lot of people got into Jekyll is through. Uh,</span><br /><br /><span data-offset="420"><button type="button">07:00</button>GitHub pages and being able to
host a website for free, uh,</span><span data-offset="424"><button type="button">07:04</button>on GitHub pages using Jekyll because
Jekyll is built in and that sort of</span><span data-offset="428"><button type="button">07:08</button>catapulted Jekyll's success in the
static site generator space too.</span><span data-offset="432"><button type="button">07:12</button>I mean, GitHub wouldn't have done GitHub pages if
it were PHP based things. They just could not have</span><br /><br /><span data-offset="438"><button type="button">07:18</button>scaled things at that level, um, or would not
have wanted to with the free tier. Um, and so,</span><span data-offset="446"><button type="button">07:26</button>yeah, I think Jamstack really.
In my mind really provides a,</span><span data-offset="450"><button type="button">07:30</button>uh, an extremely nice on ramp for
people, uh, wanting to build a website.</span><span data-offset="456"><button type="button">07:36</button>It's like, it's great for beginners and experts
because it's just fewer things you need to</span><br /><br /><span data-offset="460"><button type="button">07:40</button>worry about. And I really love when you can find
something that works for all experience levels,</span><span data-offset="467"><button type="button">07:47</button>um, for maybe different reasons, but
yeah, they, everyone likes it for, for,</span><span data-offset="473"><button type="button">07:53</button>for similar reasons as well. It's like
the bell curve with the, the beginner.</span><span data-offset="479"><button type="button">07:59</button>The intermediate and the master and
you become full circle at the end,</span><br /><br /><span data-offset="485"><button type="button">08:05</button>where it's like actually simplicity and
not having to worry about all this stuff</span><span data-offset="490"><button type="button">08:10</button>is pretty good. Then you can just focus on
making the experience really good rather</span><span data-offset="495"><button type="button">08:15</button>than having to get all these tools to work
together. Yeah. Yeah. And I think that, uh,</span><span data-offset="502"><button type="button">08:22</button>certain people really like getting tools to work
together, but I don't necessarily like that.</span><br /><br /><span data-offset="507"><button type="button">08:27</button>I mean, I, I. built a tool because, uh, at
least in part, I didn't want to have to, uh,</span><span data-offset="513"><button type="button">08:33</button>configure someone else's tool chain. And
that may be part of how it started, but,</span><span data-offset="518"><button type="button">08:38</button>um, yeah, I mean, yeah, I think it can work
for everybody. So let's go to the genesis of,</span><span data-offset="525"><button type="button">08:45</button>of Eleventy. What were you seeing at the
time? Um, and in the like Jamstack space,</span><br /><br /><span data-offset="534"><button type="button">08:54</button>um, and why do you feel like
Eleventy was, was necessary?</span><span data-offset="541"><button type="button">09:01</button>Yeah, I mean, it's, it's hard to
know if my perspective now has been</span><span data-offset="547"><button type="button">09:07</button>clouded by all of the things that have
happened since, but, uh, in my mind,</span><span data-offset="551"><button type="button">09:11</button>I really remember Eleventy sort of coming
around the same time as Gatsby did. Uh,</span><br /><br /><span data-offset="556"><button type="button">09:16</button>originally, and I remember sort of a lot of
discussions around single page applications and</span><span data-offset="562"><button type="button">09:22</button>client side rendering and whether or not you need
heavy javascript bundles to build static sites.</span><span data-offset="569"><button type="button">09:29</button>Um, and I really think that Jamstack in a
way was. Parts of Jamstack's success were</span><span data-offset="576"><button type="button">09:36</button>built on single page application models,
which I didn't agree with. I didn't think</span><br /><br /><span data-offset="581"><button type="button">09:41</button>that that was a necessary condition for, for
a Jamstack site to exist because I came from,</span><span data-offset="588"><button type="button">09:48</button>uh, experiences where. It didn't. I mean, that's
how the web has been built for many, many years.</span><span data-offset="595"><button type="button">09:55</button>I think the original sort of bake don't
fry article from Aaron Swartz was like</span><span data-offset="601"><button type="button">10:01</button>2003 or something. It's, uh, so the ideas
behind it have been around for 20 years. Um,</span><br /><br /><span data-offset="610"><button type="button">10:10</button>but I think the branding piece of it was, was a
little different. And it did sort of ride the,</span><span data-offset="616"><button type="button">10:16</button>the same marketing wave of. single page
application frameworks, um, originally.</span><span data-offset="622"><button type="button">10:22</button>And I think that's where it got maybe some, some
good marketing bumps from, um, and I think it's,</span><span data-offset="629"><button type="button">10:29</button>I think it's important to acknowledge that
too. Um, because things are a little bit</span><br /><br /><span data-offset="633"><button type="button">10:33</button>different now. Uh, And yeah, I think that,
uh, that's okay. Jamstack doesn't need to be,</span><span data-offset="642"><button type="button">10:42</button>uh, a silver bullet. And we've
kind of talked about this before.</span><span data-offset="644"><button type="button">10:44</button>Um, it doesn't need to be everything to
everyone. And I think if we try to make</span><span data-offset="649"><button type="button">10:49</button>Jamstack everything for everyone, um, then it
loses meaning. A hundred percent. And yeah,</span><br /><br /><span data-offset="657"><button type="button">10:57</button>I think. It just makes it hard to, to
talk about it as well, if it's so broad,</span><span data-offset="662"><button type="button">11:02</button>because it's like, well, which part
of the JAMstack are you talking about?</span><span data-offset="666"><button type="button">11:06</button>Are you talking about applications or are you
talking about static websites or something in</span><span data-offset="671"><button type="button">11:11</button>between? Um, I think that's, that's been one
issue with expanding the definition and the,</span><br /><br /><span data-offset="677"><button type="button">11:17</button>the kind of trajectory of a lot of JavaScript
frameworks. It's normalized a lot of things that,</span><span data-offset="684"><button type="button">11:24</button>that is really against at least the,
the initial ideas behind the JAMstack.</span><span data-offset="692"><button type="button">11:32</button>Like now we're okay shipping huge JavaScript
files. We're okay doing server side rendering</span><span data-offset="700"><button type="button">11:40</button>on what essentially should be a static
website. Uh, we're okay not using HTML</span><br /><br /><span data-offset="707"><button type="button">11:47</button>and CSS and using JavaScript to do those
things instead. And it just It's sad,</span><span data-offset="715"><button type="button">11:55</button>I think, that that has been normalized so much.
Yeah, and I think, I think it's a, I mean,</span><span data-offset="722"><button type="button">12:02</button>web development and front end web development
specifically tends to act like a pendulum.</span><span data-offset="728"><button type="button">12:08</button>Um, and it swings sort of back and forth
and I've been around, I think I've been a,</span><br /><br /><span data-offset="733"><button type="button">12:13</button>I've been a paid front end web developer for 17
years, 18 years now. And so I've been around long</span><span data-offset="741"><button type="button">12:21</button>enough to sort of see some of those trends, repeat
them, repeat themselves over and over again. Um,</span><span data-offset="748"><button type="button">12:28</button>and I think that I'm hopeful for
the next wave, which seems to be</span><span data-offset="753"><button type="button">12:33</button>the pendulum swinging back towards, um, less
JavaScript and, and better performing websites.</span><br /><br /><span data-offset="761"><button type="button">12:41</button>Um, Yeah, away from sort of the default of
single page applications for everything,</span><span data-offset="767"><button type="button">12:47</button>um, which I don't think is a good default
choice. I'm fine with it as a choice. I</span><span data-offset="772"><button type="button">12:52</button>don't think it's a good default choice. What do
you think the default choice should be? I mean,</span><span data-offset="781"><button type="button">13:01</button>it really depends on what style of application
and what style of website that you're building.</span><br /><br /><span data-offset="788"><button type="button">13:08</button>But it's really hard to get away from the basics
of HTML and CSS as the starting points. Um,</span><span data-offset="797"><button type="button">13:17</button>and whether or not you're even using a
site generator at all, I think, is a,</span><span data-offset="801"><button type="button">13:21</button>is an extra layer on top of that question. You
could just have, uh, an HTML file that exists</span><span data-offset="808"><button type="button">13:28</button>on your web server, and that's a website. Um, I
don't know why I mean, people get so tied up in,</span><br /><br /><span data-offset="815"><button type="button">13:35</button>in building all of these sort of Rube Goldberg
style machines to build websites, but I really</span><span data-offset="823"><button type="button">13:43</button>think that if you look at what you need to share
content on the web, it's just an HTML file.</span><span data-offset="830"><button type="button">13:50</button>You can add some CSS to even make
it look pretty. And at that point,</span><span data-offset="833"><button type="button">13:53</button>the user experience is imperceptibly different.
than if you had introduced a JavaScript first</span><br /><br /><span data-offset="842"><button type="button">14:02</button>tool. So what I mean to say is, you can, with
CSS and HTML, with just those two things,</span><span data-offset="849"><button type="button">14:09</button>you can make a website a very, very super
modern, very great looking website, um,</span><span data-offset="856"><button type="button">14:16</button>that will be so much better than anything
that a JavaScript toolchain will introduce.</span><span data-offset="860"><button type="button">14:20</button>And I say that as someone that has
currently maintaining a tool that is</span><br /><br /><span data-offset="865"><button type="button">14:25</button>a JavaScript toolchain tool. Um Yeah, I think
it's really important for people to understand</span><span data-offset="872"><button type="button">14:32</button>the minimum viable things that you need to build
a website. And really, I think a modern website</span><span data-offset="877"><button type="button">14:37</button>only needs HTML and CSS now. Um, I don't think
you can get away with HTML only at this point.</span><span data-offset="883"><button type="button">14:43</button>You've got to make it look a little
bit better, but, um, CSS will get you</span><br /><br /><span data-offset="888"><button type="button">14:48</button>a hundred percent of the way, um, for a lot
of use cases. Yeah. It's easy to forget that,</span><span data-offset="894"><button type="button">14:54</button>you know, these are the technologies that.
The platform is understanding and it's,</span><span data-offset="900"><button type="button">15:00</button>it's a question of how you get there,
whether you just write the thing that</span><span data-offset="905"><button type="button">15:05</button>the browser is going to interpret, or
whether you have a whole tool chain</span><br /><br /><span data-offset="908"><button type="button">15:08</button>behind generating that and both are useful,
but for, for completely different use cases.</span><span data-offset="915"><button type="button">15:15</button>And the former probably isn't used enough.
It's just a lot of overengineering. Yeah,</span><span data-offset="924"><button type="button">15:24</button>and I mean, I think that's maybe the secret to
Eleventy's longevity has been that we really</span><span data-offset="931"><button type="button">15:31</button>try and stay right above that reusability
layer of you're building something with the</span><br /><br /><span data-offset="938"><button type="button">15:38</button>bare minimum, you suddenly need some reusable
templating, um, and maybe some data injection.</span><span data-offset="945"><button type="button">15:45</button>Um, and. We really don't exist. We try to stay as
close to that minimum as possible. Um, we don't</span><span data-offset="952"><button type="button">15:52</button>have a bundler by default. We do have a bunch of
plugins that you can add on top of Eleventy. Um,</span><span data-offset="958"><button type="button">15:58</button>but really the baseline core experience of
Eleventy tries to stick as close to that</span><br /><br /><span data-offset="963"><button type="button">16:03</button>as possible. Um, that step right after you
sort of get to the reusability question and</span><span data-offset="970"><button type="button">16:10</button>your site needs a little bit more needs to
be a little bit more maintenance friendly.</span><span data-offset="975"><button type="button">16:15</button>Um, and yeah, I think sticking to that
and having sort of a web standards based</span><span data-offset="980"><button type="button">16:20</button>approach can you can have a website
that last 10 years, 15 years, um,</span><br /><br /><span data-offset="986"><button type="button">16:26</button>which is maybe. Not how a lot of, uh, folks
think about having websites, but that's what</span><span data-offset="993"><button type="button">16:33</button>I want for my websites. I don't want to have
to rewrite my tech stack every two years,</span><span data-offset="997"><button type="button">16:37</button>which is, seems like it's a very common
thing in the JavaScript ecosystem world.</span><span data-offset="1003"><button type="button">16:43</button>Um, I want some longevity. Yeah.
Yeah, totally. Um, what, um,</span><br /><br /><span data-offset="1011"><button type="button">16:51</button>I want to throw some names at you for, for what
we could call this community and this approach,</span><span data-offset="1020"><button type="button">17:00</button>um, and get, get your feedback
on it. So one, we have Jamstack,</span><span data-offset="1026"><button type="button">17:06</button>keep it the same. Jamstack is, uh, it's a term
that's used throughout the industry and is.</span><span data-offset="1035"><button type="button">17:15</button>Somewhat understood, but also, there's
a lot of conversations like this one,</span><br /><br /><span data-offset="1039"><button type="button">17:19</button>just trying to understand what it
is. Um, then we have Jamstack Plus,</span><span data-offset="1045"><button type="button">17:25</button>which recognizes that Jamstack kind of has this,
these um, ambiguous edges, and it would be an</span><span data-offset="1056"><button type="button">17:36</button>attempt to tighten that up. Static first, which
is going back to what you were saying earlier.</span><span data-offset="1064"><button type="button">17:44</button>It's, it's really trying to emphasize
that the choice should be static first</span><br /><br /><span data-offset="1069"><button type="button">17:49</button>and then use something else.
If that doesn't fit the case,</span><span data-offset="1074"><button type="button">17:54</button>or, um, the final one is a new term that
is, I don't have a word, but is like an</span><span data-offset="1082"><button type="button">18:02</button>interesting cool word that doesn't have any
meaning about the static, the stack itself,</span><span data-offset="1089"><button type="button">18:09</button>but it would be a community and an umbrella
that this community could, could fall under.</span><br /><br /><span data-offset="1095"><button type="button">18:15</button>What do you think about those ideas and
what are you drawn to there? I mean,</span><span data-offset="1101"><button type="button">18:21</button>I am a fan of JAMstack as a term. I
like the JAMstack term. I like what</span><span data-offset="1108"><button type="button">18:28</button>it did for static site generation. Um,
I feel like it has been transformed in a</span><span data-offset="1117"><button type="button">18:37</button>way that has been damaging to it. Um, But
I do also feel like it still has value.</span><br /><br /><span data-offset="1124"><button type="button">18:44</button>Um, I think that it has an incredible amount
of community knowledge of sort of Jamstack</span><span data-offset="1132"><button type="button">18:52</button>as an architectural model. Um, that I do not
think is, uh, I do not think it's something</span><span data-offset="1139"><button type="button">18:59</button>that should be thrown away. Um, so I, I am a fan
of Jamstack. I. I think that it has had a hard</span><span data-offset="1146"><button type="button">19:06</button>time in the last year or two, um, just with the
introduction or the, maybe the popularization of,</span><br /><br /><span data-offset="1155"><button type="button">19:15</button>uh, uh, non Jamstack tools sort of trying to
fit non Jamstack tools under the umbrella.</span><span data-offset="1165"><button type="button">19:25</button>Um, and yeah, I don't think that that's a
necessary. thing that JAMstack needs to do.</span><span data-offset="1171"><button type="button">19:31</button>It needs to be a laser focused definition
of what it is and what it isn't. Um,</span><span data-offset="1177"><button type="button">19:37</button>so that it can be a valuable discussion
point and a valuable educational thing. Um,</span><br /><br /><span data-offset="1184"><button type="button">19:44</button>so I, I am a fan of JAMstack. I
do like the term JAMstack. Um,</span><span data-offset="1189"><button type="button">19:49</button>I, I don't even necessarily think
that Jamstack plus needs to exist.</span><span data-offset="1196"><button type="button">19:56</button>I think that we can just have technologies that
are sort of Jamstack adjacent. And if we need a,</span><span data-offset="1203"><button type="button">20:03</button>a new term for that, that's, that's fine. Um,
but I don't, I don't know if I'm a fan of,</span><br /><br /><span data-offset="1210"><button type="button">20:10</button>of having a, a second term that acts as
like a, an umbrella for the things that,</span><span data-offset="1216"><button type="button">20:16</button>um, I think cheapened the, the
original definition of Jamstack.</span><span data-offset="1221"><button type="button">20:21</button>Um, Because I think it's better. And I
think people respect the intellectual</span><span data-offset="1226"><button type="button">20:26</button>honesty of being able to say, this is what
it is. This is what is it. This is what it</span><br /><br /><span data-offset="1231"><button type="button">20:31</button>isn't. This is what you use it for. And this
is what you wouldn't use it for. Um, so yeah,</span><span data-offset="1238"><button type="button">20:38</button>that's kind of where I'm at on that.
Yeah. Wait, just diving into that one.</span><span data-offset="1242"><button type="button">20:42</button>Where do you think was too, where do you think
Jamstack went too far? Cause like I've rewatched</span><span data-offset="1249"><button type="button">20:49</button>Matt's announcement of JAMstack at Smashing Conf
2016 recently. And there was a, there was a big</span><br /><br /><span data-offset="1258"><button type="button">20:58</button>emphasis on the reason for needing a new term was
because static is like a loaded term that people</span><span data-offset="1269"><button type="button">21:09</button>think that it means simple and there's no
interaction on the page and it's like lesser and.</span><span data-offset="1279"><button type="button">21:19</button>And if you're trying to sell that to
someone, maybe non technical it's,</span><span data-offset="1284"><button type="button">21:24</button>it's tough to just that alone, even
though there are so many benefits and</span><br /><br /><span data-offset="1291"><button type="button">21:31</button>so many websites would be served better if
they were static. Um, but over the years it,</span><span data-offset="1298"><button type="button">21:38</button>that definition has expanded to the point where
now I believe it includes server side rendering.</span><span data-offset="1304"><button type="button">21:44</button>Um, it's unclear what it doesn't include to me.
Like where, where do you think that was pushed too</span><span data-offset="1311"><button type="button">21:51</button>far? Yeah. I mean, it's interesting to look back
on it because, uh, I think that in many respects,</span><br /><br /><span data-offset="1323"><button type="button">22:03</button>the, the Jamstack 2. 0 pivot was an interesting
milestone. And that was sort of when at the same</span><span data-offset="1330"><button type="button">22:10</button>time it was renamed from J A M stack
and JavaScript, uh, APIs and markup.</span><span data-offset="1338"><button type="button">22:18</button>To, um, sort of the title case version,
uh, just like a standalone word. And I'm,</span><span data-offset="1345"><button type="button">22:25</button>I liked the, the branding change. I was in favor
of that. I didn't think that JavaScript APIs</span><br /><br /><span data-offset="1352"><button type="button">22:32</button>and markup was particularly, a particularly
useful descriptor. Um, but at that same time,</span><span data-offset="1359"><button type="button">22:39</button>you could see the industry, um, also sort
of giving rise to frameworks that were.</span><span data-offset="1365"><button type="button">22:45</button>Not statically generated, so your remix
and, uh, certain aspects of Next. js,</span><span data-offset="1372"><button type="button">22:52</button>I think, really pivoted away from Jamstack
core principles. And at the same time,</span><br /><br /><span data-offset="1379"><button type="button">22:59</button>Jamstack tried to include those, um, in a way
that I just don't think that it makes sense</span><span data-offset="1385"><button type="button">23:05</button>to include those. Um, especially looking back
on it, it doesn't make sense to include those.</span><span data-offset="1390"><button type="button">23:10</button>Um, they're not static frameworks. They're not,
um, JAMstack frameworks. And I don't even think</span><span data-offset="1396"><button type="button">23:16</button>the authors of those frameworks would disagree.
Um, so it, it felt like a, an overreach in many</span><br /><br /><span data-offset="1403"><button type="button">23:23</button>respects to, to try and include those under
the umbrella, um, in a way that it just didn't,</span><span data-offset="1410"><button type="button">23:30</button>it didn't make sense to, uh, to the definition of
JAMstack as most people understood it, I think.</span><span data-offset="1416"><button type="button">23:36</button>Yeah, agreed. Cause like, I
think Jamstack, the movement,</span><span data-offset="1422"><button type="button">23:42</button>everyone has a WordPress story like you did
before of security or performance, ouch,</span><br /><br /><span data-offset="1429"><button type="button">23:49</button>static websites. Nice. And I think Jamstack
was the antithesis of, of WordPress in 2016.</span><span data-offset="1438"><button type="button">23:58</button>And a lot of people came on that journey
from WordPress. Everyone has that story.</span><span data-offset="1445"><button type="button">24:05</button>But now, it's hard, it, like, I find myself
asking, like, is WordPress jam stick? And it,</span><span data-offset="1453"><button type="button">24:13</button>like, if it's not, then why not, it, it
kind of, it meets the definition, uh,</span><br /><br /><span data-offset="1460"><button type="button">24:20</button>you know, if, if server side rendering
can be included in the definition,</span><span data-offset="1464"><button type="button">24:24</button>then why not WordPress? Oh, with the
modern definition. Yeah. I think that</span><span data-offset="1470"><button type="button">24:30</button>that is a very good point that under
the modern definition, it's hard to.</span><span data-offset="1475"><button type="button">24:35</button>Discount, uh, PHP style things as non Jamstack.
But I mean, as we've said in this discussion,</span><br /><br /><span data-offset="1484"><button type="button">24:44</button>I, I don't think that WordPress
is a Jamstack framework. Um,</span><span data-offset="1489"><button type="button">24:49</button>and I don't think anything that sort of does
that runtime processing in PHP is a Jamstack</span><span data-offset="1495"><button type="button">24:55</button>framework. I don't, uh, yeah, I don't
think it makes sense to include that.</span><span data-offset="1501"><button type="button">25:01</button>But it's also just sort of fascinating to,
to look back on the history of that Jamstack</span><br /><br /><span data-offset="1506"><button type="button">25:06</button>pivot as well, because, uh, I used to work
for Netlify. So, uh, I sort of saw some of</span><span data-offset="1514"><button type="button">25:14</button>that decision making behind the scenes. And, um,
I remember sort of the very. WordPress focused,</span><span data-offset="1522"><button type="button">25:22</button>uh, mission of the company. Um, and
I was really on board with that.</span><span data-offset="1527"><button type="button">25:27</button>I thought that, uh, Jamstack was a great
alternative to WordPress and WordPress was</span><br /><br /><span data-offset="1531"><button type="button">25:31</button>this huge comp competitor, um, and they
power a ton of the web. And so I really</span><span data-offset="1538"><button type="button">25:38</button>thought that Jamstack was going to be
a great alternative to WordPress. Um,</span><span data-offset="1544"><button type="button">25:44</button>and it didn't dethrone WordPress
like, like maybe I had hoped, but,</span><span data-offset="1548"><button type="button">25:48</button>um, I think at the same time you saw sort of like
Vercel coming in and competing in the same space.</span><br /><br /><span data-offset="1554"><button type="button">25:54</button>And, um, I really think that that was a huge, huge
influence on the, on the pivot as well. Um, yeah,</span><span data-offset="1562"><button type="button">26:02</button>I think that that's the primary reason that
the pivot happened. Yeah. Yeah. It's like,</span><span data-offset="1567"><button type="button">26:07</button>just, I guess WordPress is saying is legacy weird
and, and. Um, Next. js is like the hot new thing,</span><span data-offset="1577"><button type="button">26:17</button>but it's easy to forget that
I don't have the stat on hand,</span><br /><br /><span data-offset="1581"><button type="button">26:21</button>but it's something like 60 percent
of the web is WordPress, right?</span><span data-offset="1584"><button type="button">26:24</button>That's the elephant in everyone's room. Yeah.
WordPress is dominated. All of the JavaScript</span><span data-offset="1592"><button type="button">26:32</button>frameworks, I mean, it's just, it's making so much
money and it's this big, huge success story. And,</span><span data-offset="1599"><button type="button">26:39</button>but yet it has all of these very obvious flaws.
So, I mean, that's sort of maybe getting into,</span><br /><br /><span data-offset="1605"><button type="button">26:45</button>uh, the Netlify pivot, but, um, yeah, I really
think that WordPress should be a thing that is.</span><span data-offset="1612"><button type="button">26:52</button>Competed with more directly. Yeah, agreed.
So what do you think needs to happen in</span><span data-offset="1620"><button type="button">27:00</button>the Jamstack community for it to remain
relevant, um, in the web ecosystem? Yeah,</span><span data-offset="1630"><button type="button">27:10</button>I mean, that's a, that's a very interesting
question. Will Jamstack be able to stand on</span><br /><br /><span data-offset="1637"><button type="button">27:17</button>its own as a, a well established and
well documented architecture pattern?</span><span data-offset="1643"><button type="button">27:23</button>Or will it be seen as sort of this legacy
thing that people are moving past? And I</span><span data-offset="1650"><button type="button">27:30</button>think that if Jamstack can succeed, uh,
it needs to have a clear definition of</span><span data-offset="1657"><button type="button">27:37</button>what it is and what it isn't. Um, and
if it's just going to be this vague,</span><br /><br /><span data-offset="1663"><button type="button">27:43</button>all encompassing umbrella term, uh, then. Jamstack
will fall and there's no use to keeping it around.</span><span data-offset="1672"><button type="button">27:52</button>Um, it has no educational
purpose. It will have no, uh,</span><span data-offset="1676"><button type="button">27:56</button>technical purpose. It won't mean anything.
So if you want Jamstack to succeed, um,</span><span data-offset="1683"><button type="button">28:03</button>it needs to have a clear definition. It needs
to, um, yeah, take hard stances on things, um,</span><br /><br /><span data-offset="1690"><button type="button">28:10</button>and draw some hard lines because that's, um. The
unique benefits of JAMstack have those hard lines.</span><span data-offset="1699"><button type="button">28:19</button>Um, and that's, that's it. And that's
all I guess. Nice. Those are the great</span><span data-offset="1705"><button type="button">28:25</button>conversations that so much, thank you so
much for joining today. Yeah, I really</span><span data-offset="1710"><button type="button">28:30</button>enjoyed it. That's it for today's interview.
Find out more at the future of JAMstack. org.</span>
<script type="module">
class YouTubeDeepLink extends HTMLElement {
#watching = false;
#video = null;
static tagName = "youtube-deep-link";
static classes = {
active: "active"
};
static register(tagName) {
if(!("customElements" in globalThis)) {
return;
}
customElements.define(tagName || this.tagName, this);
}
get videoId() {
return this.getAttribute("videoid");
}
set video(v) {
this.#video = v;
}
get video() {
if(!this.#video) {
this.#video = document.querySelector(`lite-youtube[videoid="${this.videoId}"]`);
}
return this.#video;
}
connectedCallback() {
let buttons = this.querySelectorAll("[data-offset]");
for(let btn of buttons) {
btn.addEventListener("click", (e) => {
let target = e.target.closest("[data-offset]");
let offset = parseInt(target.getAttribute("data-offset"), 10); // in seconds
this.setActiveLink(offset);
this.seek(offset);
e.preventDefault();
});
}
}
async seek(offset) {
if(!this.video) {
// just scroll to the video, the viewport isn’t big enough to show the video and transcript together
let video = document.querySelector(`[videoid="${this.videoId}"]`);
video?.parentNode?.scrollIntoView(true);
} else {
let player = await this.video.getYTPlayer();
this.watch(); // keep the links active
// https://developers.google.com/youtube/iframe_api_reference#seekTo
player?.seekTo(offset, true);
}
}
setActiveLink(timeInSeconds) {
let activeElement = this.querySelector(`[data-offset="${Math.round(timeInSeconds)}"]`);
if(activeElement) {
// remove class from previous
let previousElement = this.querySelector(`.${YouTubeDeepLink.classes.active}`);
previousElement?.classList.remove(YouTubeDeepLink.classes.active);
// add to new
activeElement.classList.add(YouTubeDeepLink.classes.active);
}
}
async watch() {
if(this.#watching) {
return;
}
let player = await this.video.getYTPlayer();
player.addEventListener("onStateChange", ({data}) => {
if(data === 1) { // playing
this.#watching = setInterval(() => {
this.setActiveLink(player.getCurrentTime())
}, 500);
} else { // paused
clearInterval(this.#watching);
}
})
}
}
YouTubeDeepLink.register();
</script></youtube-deep-link></div>
In Case You Missed It: 20232024-01-02T00:00:00Zhttp://www.zachleat.com/web/icymi/2023/
<p><small><em>Series:
2023,
<a href="https://www.zachleat.com/web/icymi/2022/">2022</a>,
<a href="https://www.zachleat.com/web/icymi/2017/">2017</a>,
<a href="https://www.zachleat.com/web/icymi/2016/">2016</a>,
<a href="https://www.zachleat.com/web/icymi/2015/">2015</a>,
<a href="https://www.zachleat.com/web/icymi/2014/">2014</a>,
<a href="https://www.zachleat.com/web/icymi/2013/">2013</a>
</em></small></p>
<p>2023 has been one hell-for-leather roller coaster of a year.</p>
<h2 id="down-for-the-count" tabindex="-1">Down for the count <a href="https://www.zachleat.com/web/icymi/2023/#down-for-the-count" aria-hidden="true" class="direct-link">#</a></h2>
<p>In late April, Eleventy <a href="https://www.zachleat.com/web/eleventy-side-project/">lost its full time sponsorship</a> in the midst of Netlify’s struggle to survive the gauntlet of late-stage venture capitalism.</p>
<p>I burned out pretty hard during this transition. We could go back and forth about the details of how it was handled but as things move forward it doesn’t seem productive to rehash it <em>(I wrote and deleted this section many times)</em>. The most productive summary I can offer is that the Silicon Valley mentality of hockey-stick hyper-growth (or death) is simultaneously unhealthy and needlessly destructive <em>for all involved parties</em>.</p>
<p>I do feel a bit of shame in admitting to myself that despite all of the precautions I took while administering Eleventy as an independent project, Eleventy almost died this year—and the responsibility for that is mine.</p>
<h2 id="don't-call-it-a-comeback" tabindex="-1">Don’t call it a comeback <a href="https://www.zachleat.com/web/icymi/2023/#don't-call-it-a-comeback" aria-hidden="true" class="direct-link">#</a></h2>
<p>In happier news, in August I <a href="https://www.zachleat.com/web/cloudcannon/">joined CloudCannon as a developer advocate</a> and they are donating <strong>part</strong> of my time to Eleventy’s development. Coupled with their four-day work week 🏆🏆🏆, I’ve been using a majority of Fridays for Eleventy development too (per my discretion).</p>
<p>CloudCannon are incredibly supportive of the project and Eleventy certainly <em>would not</em> have rebounded as quickly as it did without their help.</p>
<p>Importantly, CloudCannon’s healthier work-life balance and subsequent mental shift rebalanced my perspective of Eleventy away from something that <em>needed to compete to survive</em> to a much healthier and enjoyable place.</p>
<h2 id="eleventy-in-2023" tabindex="-1">Eleventy in 2023 <a href="https://www.zachleat.com/web/icymi/2023/#eleventy-in-2023" aria-hidden="true" class="direct-link">#</a></h2>
<p>Eleventy still had a pretty great year despite our setbacks.</p>
<ul>
<li>We had 2.9 million downloads in 2023 <em>(+34.36% year over year)</em>.</li>
<li>We crossed cumulative download milestones for <a href="https://www.11ty.dev/blog/six-million/">6 million</a> and <a href="https://www.11ty.dev/blog/seven-million/">7 million</a> and we’re eyeing 8 million soon!</li>
<li>We shipped <a href="https://www.11ty.dev/blog/eleventy-v2/">stable v2.0</a> 🏆</li>
<li>We looked to refocus the project with <a href="https://github.com/11ty/eleventy/pull/3074">Project Slipstream</a> and published our first <a href="https://www.11ty.dev/blog/community-survey/">Community Survey</a> to get help prioritizing those goals (with <a href="https://www.11ty.dev/blog/community-survey-results/">results</a>)</li>
<li>We have shipped a few <a href="https://www.11ty.dev/blog/canary-eleventy-v3/">v3.0 alpha releases</a> with support for ESM and asynchronous configuration.</li>
</ul>
<p>And that’s not all: we have a few surprises in the hopper planned for 2024 that I’m very excited about.</p>
<h2 id="a-few-good-blog-posts" tabindex="-1">A few good Blog Posts <a href="https://www.zachleat.com/web/icymi/2023/#a-few-good-blog-posts" aria-hidden="true" class="direct-link">#</a></h2>
<p><em>Most popular (by total pageviews, ×2):</em></p>
<ol>
<li><a href="https://www.zachleat.com/web/react-criticism/">A Historical Reference of React Criticism</a></li>
<li><a href="https://www.zachleat.com/web/a-taxonomy-of-web-component-types/">An Attempted Taxonomy of Web Components</a></li>
</ol>
<p><em>Most hopeful:</em> <a href="https://www.zachleat.com/web/jamstack-future/">The Tension And Future Of Jamstack</a></p>
<p><em>My personal favorite:</em> <a href="https://www.zachleat.com/web/esif/">Educational, Sensational, Inspirational, Foundational Web Development Reading List</a></p>
<p><em>Controversial:</em> <a href="https://www.zachleat.com/web/netlify-and-nextjs/">Netlify’s Disingenuous Survey-based Attack On Next.js (And Eleventy, Too)</a></p>
<p><em>Underrated:</em> <a href="https://www.zachleat.com/web/javascript-community/">JavaScript, Community</a></p>
<p><em>The hardest one to write:</em> <a href="https://www.zachleat.com/web/eleventy-side-project/">The Next Phase of Eleventy: Return of the Side Project</a></p>
<h2 id="thank-you-notes" tabindex="-1">Thank You Notes <a href="https://www.zachleat.com/web/icymi/2023/#thank-you-notes" aria-hidden="true" class="direct-link">#</a></h2>
<p>I have a lot to be thankful for in 2023 and my family goes at the top of that list. I had so many long lunchtime talks with my wife as we navigated through this year. She has been incredibly patient as she sits on the receiving end of privileged-dude-with-a-podcast-microphone levels of venting about tech drama.</p>
<p>A big thank you to new <a href="https://cloudcannon.com/">CloudCannon</a> friends <a href="https://www.linkedin.com/in/christopher-wingate/">Christopher Wingate</a>, <a href="https://www.linkedin.com/in/georgepaulphillips/">George Phillips</a>, <a href="https://github.com/bglw/">Liam Bigelow</a>, <a href="https://mikeneumegen.com/">Mike Neumegen</a>, <a href="https://www.linkedin.com/in/olivia-nicholson-42a050127/">Olivia Nicholson</a>, <a href="https://www.linkedin.com/in/david-large-4875b81b2/">David Large</a> <em>(and others)</em>.</p>
<p>To everyone in the Eleventy community, especially: <a href="https://about.me/peterdehaan">Peter deHaan</a>, <a href="https://benmyers.dev/"><img alt="IndieWeb Avatar for https://benmyers.dev" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fbenmyers.dev/" width="60" height="60" class="z-avatar" />Ben Myers</a>, <a href="https://www.cassey.dev/">Cassey Lottman</a>, <a href="https://danleatherman.com/"><img alt="IndieWeb Avatar for https://danleatherman.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fdanleatherman.com%2F/" width="60" height="60" class="z-avatar" />Dan Leatherman</a>, <a href="https://darthmall.net/"><img alt="IndieWeb Avatar for https://darthmall.net/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fdarthmall.net%2F/" width="60" height="60" class="z-avatar" />Evan Sheehan</a>, <a href="https://helloyes.dev/"><img alt="IndieWeb Avatar for https://helloyes.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fhelloyes.dev%2F/" width="60" height="60" class="z-avatar" />Thomas M. Semmler</a>, <a href="https://sia.codes/"><img alt="IndieWeb Avatar for https://sia.codes/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fsia.codes%2F/" width="60" height="60" class="z-avatar" />Sia Karamalegos</a>, <a href="https://thinkdobecreate.com/"><img alt="IndieWeb Avatar for https://thinkdobecreate.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fthinkdobecreate.com%2F/" width="60" height="60" class="z-avatar" />Stephanie Eckles</a>, <a href="https://coryd.dev/"><img alt="IndieWeb Avatar for https://coryd.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fcoryd.dev%2F/" width="60" height="60" class="z-avatar" />Cory Dransfeldt</a>, <a href="https://uncenter.dev/"><img alt="IndieWeb Avatar for https://uncenter.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Funcenter.dev%2F/" width="60" height="60" class="z-avatar" />Uncenter</a>, <a href="https://www.bobmonsour.com/"><img alt="IndieWeb Avatar for https://www.bobmonsour.com/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fwww.bobmonsour.com%2F/" width="60" height="60" class="z-avatar" />Bob Monsour</a>, <a href="https://www.hoeser.dev/"><img alt="IndieWeb Avatar for https://www.hoeser.dev/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fwww.hoeser.dev%2F/" width="60" height="60" class="z-avatar" />Raphael Höser</a>, and <a href="https://shivjm.blog/"><img alt="IndieWeb Avatar for https://shivjm.blog/" loading="lazy" decoding="async" src="https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fshivjm.blog%2F/" width="60" height="60" class="z-avatar" />Shiv J.M.</a> <em>(and so many others)</em>.</p>
<p>…and keep building for the web ❤️</p>
Building a multi-language Taylor Swift fan site (10 Minute Version) (Zach's Version)2023-12-22T00:00:00Zhttp://www.zachleat.com/web/taylor-swift-fansite/
<script type="module" src="https://www.zachleat.com/static/js/offviewport.js"></script>
<div>
<off-viewport>
<youtube-lite-player>
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=hxHXXf7dTvk" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="hxHXXf7dTvk" js-api="" playlabel="Play: Building a multi-language Taylor Swift fan site (10 Minute Version) (Zach's Version)" 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%3DhxHXXf7dTvk/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-lite-player>
</off-viewport>
<youtube-link href="https://youtube.com/watch?v=hxHXXf7dTvk"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=hxHXXf7dTvk" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Building a multi-language Taylor Swift fan site (10 Minute Version) (Zach's Version)</em></a></youtube-link>
</div>
<hr />
<p>In the above video I walk through a <a href="https://fluent-reef.cloudvent.net/">Taylor Swift lyrics fansite</a> I built to demonstrate a few internationalization features with Eleventy and CloudCannon.</p>
<ul>
<li><a href="https://fluent-reef.cloudvent.net/">Demo</a></li>
<li><a href="https://github.com/zachleat-cc/demo-cloudcannon-i18n">Source code</a></li>
</ul>
<h2 id="features" tabindex="-1">Features <a href="https://www.zachleat.com/web/taylor-swift-fansite/#features" aria-hidden="true" class="direct-link">#</a></h2>
<ul>
<li>Supports English and Spanish (any number of languages can be added)
<ul>
<li>Using <a href="https://www.11ty.dev/docs/plugins/i18n/">Eleventy’s i18n plugin</a></li>
<li>Using <a href="https://www.npmjs.com/package/rosetta"><code>rosetta</code> for string transformation</a></li>
</ul>
</li>
<li>Custom redirects are not necessary! The HTML links <em>just work</em>.</li>
<li>For-free inter-language page-aware language chooser <em>(e.g. “Also available in…”)</em></li>
<li>Translated header, footer, and banner (in a <a href="https://github.com/CloudCannon/demo-marketing-components">different repo</a> via Site Mounting)</li>
<li>For-free <a href="https://pagefind.app/docs/multilingual/">locale aware search from Pagefind</a></li>
<li>Album art pulled from Spotify Open Graph images</li>
<li>Deep links to various streaming services</li>
<li>Built with Eleventy v3.0
<ul>
<li>Using <code>node</code> front matter in <code>songs.liquid</code> (to enable the <code>before</code> pagination callback in JavaScript)</li>
<li>Using custom <a href="https://github.com/11ty/eleventy/issues/332">dual pagination</a> with <code>before</code> chunking data between languages and songs.</li>
</ul>
</li>
<li>Using the seasonally <a href="https://www.zachleat.com/web/snow-fall/">appropriate <code><snow-fall></code> web component</a>.</li>
</ul>
<h2 id="searchable-transcript" tabindex="-1">Searchable Transcript <a href="https://www.zachleat.com/web/taylor-swift-fansite/#searchable-transcript" aria-hidden="true" class="direct-link">#</a></h2>
<div><youtube-deep-link videoid="hxHXXf7dTvk"><style>
youtube-deep-link [data-offset] {
display: inline;
text-align: left;
margin-inline-end: .25em;
}
youtube-deep-link [data-offset] button {
display: inline-block;
font: inherit;
font-size: 80%;
border: none;
border-radius: 0.3em;
background: inherit;
font-family: Consolas, Monaco, monospace;
margin-inline-end: .25em;
line-height: 1.2;
color: #444;
background: #eee;
vertical-align: text-top;
padding: 0.15em 0.3em;
}
youtube-deep-link [data-offset].active {
background-color: #ffc;
}
youtube-deep-link:defined [data-offset]:hover,
youtube-deep-link:defined [data-offset]:focus {
cursor: pointer;
text-decoration: underline;
}
</style>
<br /><br /><span data-offset="0"><button type="button">00:00</button>All right, what's up everybody?</span><span data-offset="1"><button type="button">00:01</button>Today, I want to talk to you about</span><span data-offset="2"><button type="button">00:02</button>internationalization or creating a website
that can have multiple languages</span><span data-offset="7"><button type="button">00:07</button>and locales to really show off your
content to a variety of global users.</span><br /><br /><span data-offset="12"><button type="button">00:12</button>And the term internationalization
is often seen as i18n.</span><span data-offset="17"><button type="button">00:17</button>And the secret behind the shortening
of that term is there's 18 letters between</span><span data-offset="20"><button type="button">00:20</button>the I and the N, and they just get
removed and replaced with an 18.</span><span data-offset="25"><button type="button">00:25</button>So when you see someone talking about</span><br /><br /><span data-offset="27"><button type="button">00:27</button>i18n, they're really just talking
about internationalization.</span><span data-offset="30"><button type="button">00:30</button>Now there are a couple of ways
to do this with CloudCannon today.</span><span data-offset="35"><button type="button">00:35</button>You can go into your CloudCannon
application, open up the Site Settings,</span><span data-offset="40"><button type="button">00:40</button>and there's an i18n page in the build
section of your site settings.</span><br /><br /><span data-offset="44"><button type="button">00:44</button>And you can click this little
check box that will opt into the.</span><span data-offset="47"><button type="button">00:47</button>internationalization workflow
that CloudCannon has set up.</span><span data-offset="51"><button type="button">00:51</button>And this is great.
It will take your site.</span><span data-offset="53"><button type="button">00:53</button>Create a couple of different languages</span><br /><br /><span data-offset="55"><button type="button">00:55</button>for it based on some data
files that exist in your</span><span data-offset="58"><button type="button">00:58</button>repository, and
it will automatically redirect</span><span data-offset="60"><button type="button">01:00</button>users to the localized
version of your site,</span><span data-offset="64"><button type="button">01:04</button>depending on what language preference
they have set in their web browser.</span><br /><br /><span data-offset="68"><button type="button">01:08</button>Another great tool in CloudCannon's
arsenal is a tool called Rosey.</span><span data-offset="72"><button type="button">01:12</button>Now, Rosey works very similarly.</span><span data-offset="74"><button type="button">01:14</button>It's a little bit more powerful.</span><span data-offset="75"><button type="button">01:15</button>It will take the output of your statically
generated site, and again,</span><br /><br /><span data-offset="78"><button type="button">01:18</button>it will post process it to create
internationalized versions of the site.</span><span data-offset="83"><button type="button">01:23</button>But today, I'm going to show you a third</span><span data-offset="84"><button type="button">01:24</button>option that allows you to edit
your internationalized strings</span><span data-offset="88"><button type="button">01:28</button>inside of the CloudCannon</span><br /><br /><span data-offset="89"><button type="button">01:29</button>application using Eleventy’s
internationalization plugin.</span><span data-offset="93"><button type="button">01:33</button>And we're going to do that through</span><span data-offset="95"><button type="button">01:35</button>the lens of this Taylor Swift Super Fan
website that I've built.</span><span data-offset="98"><button type="button">01:38</button>This is a site that has two
different languages on it.</span><br /><br /><span data-offset="102"><button type="button">01:42</button>It's got English and Spanish,
and it's a lyrics website.</span><span data-offset="106"><button type="button">01:46</button>So we've got a couple of songs here,</span><span data-offset="108"><button type="button">01:48</button>but the internationalization piece of this
is that we have, each website is generated</span><span data-offset="113"><button type="button">01:53</button>in English and Spanish using
the Eleventy internationalization plugin.</span><br /><br /><span data-offset="117"><button type="button">01:57</button>So the internationalization plugin</span><span data-offset="119"><button type="button">01:59</button>generates links for all of the languages
that you are specifying in your site.</span><span data-offset="123"><button type="button">02:03</button>Every single page on your site will
then have an internationalized link.</span><span data-offset="127"><button type="button">02:07</button>So I can click through to a specific song</span><br /><br /><span data-offset="131"><button type="button">02:11</button>page, and that will also have a customized
link to the Spanish version.</span><span data-offset="136"><button type="button">02:16</button>One of the other things here is I built</span><span data-offset="138"><button type="button">02:18</button>a tiny little web component to highlight
when the user's browser language</span><span data-offset="142"><button type="button">02:22</button>preference doesn't match the language
of the page being shown to the user.</span><br /><br /><span data-offset="146"><button type="button">02:26</button>You can see the URLs are
internationalized as well.</span><span data-offset="149"><button type="button">02:29</button>If you look up in the URL bar,
if we click over to the Spanish version,</span><span data-offset="153"><button type="button">02:33</button>you can see that the URL changes
to include the locale here</span><span data-offset="156"><button type="button">02:36</button>and then also the title of the song has</span><br /><br /><span data-offset="159"><button type="button">02:39</button>been changed to the Spanish
version as well.</span><span data-offset="162"><button type="button">02:42</button>Now, the other neat thing here is that all</span><span data-offset="164"><button type="button">02:44</button>of the content on the page
has been internationalized.</span><span data-offset="167"><button type="button">02:47</button>So this content is actually coming through
using CloudCannon site mounting feature.</span><br /><br /><span data-offset="172"><button type="button">02:52</button>So we have a banner that's set
across all of my different demos.</span><span data-offset="175"><button type="button">02:55</button>We have a drop-down here that shows links
to all of the different demos that I've</span><span data-offset="179"><button type="button">02:59</button>built so far using CloudCannon to show off
the CloudCannon features,</span><span data-offset="182"><button type="button">03:02</button>and all the content inside of this menu
has been internationalized as well.</span><br /><br /><span data-offset="187"><button type="button">03:07</button>But that actually lives
in a separate repository.</span><span data-offset="189"><button type="button">03:09</button>And if you scroll down to the bottom,</span><span data-offset="190"><button type="button">03:10</button>you also see the links and the footer
have been internationalized as well.</span><span data-offset="194"><button type="button">03:14</button>So the other thing you'll notice is
that when you click around on the site,</span><br /><br /><span data-offset="197"><button type="button">03:17</button>if you are in the Spanish version,
you will remain in the Spanish version.</span><span data-offset="201"><button type="button">03:21</button>All the links are relative to the current
language that you're browsing.</span><span data-offset="206"><button type="button">03:26</button>And if you switch back, all the links
will then be to English versions.</span><span data-offset="209"><button type="button">03:29</button>So we're using the Eleventy</span><br /><br /><span data-offset="211"><button type="button">03:31</button>internationalization plugin
to generate the HTML for this site.</span><span data-offset="214"><button type="button">03:34</button>We have the English version of the website</span><span data-offset="216"><button type="button">03:36</button>is available by default
on the root of the URL structure.</span><span data-offset="220"><button type="button">03:40</button>And `es` exists as a Spanish version.</span><br /><br /><span data-offset="223"><button type="button">03:43</button>And if you go into the CloudCannon app,</span><span data-offset="225"><button type="button">03:45</button>the extra benefit that we get now is that
we can edit these songs in CloudCannon.</span><span data-offset="231"><button type="button">03:51</button>So technical and non-technical editors now</span><span data-offset="232"><button type="button">03:52</button>have access to edit the lyrics of
the site in different languages as well.</span><br /><br /><span data-offset="239"><button type="button">03:59</button>So we have our English version here,</span><span data-offset="241"><button type="button">04:01</button>we have the title and the lyrics,
and we also have the Spanish version here,</span><span data-offset="245"><button type="button">04:05</button>which has a separate title
and also separate Spanish lyrics.</span><span data-offset="248"><button type="button">04:08</button>And so if we have a mistake here,
we can come in and easily edit it.</span><br /><br /><span data-offset="252"><button type="button">04:12</button>We also have a URL section to link out</span><span data-offset="254"><button type="button">04:14</button>to streamable versions of the song
if you want to play the song.</span><span data-offset="258"><button type="button">04:18</button>And the nice thing here is that we can</span><span data-offset="260"><button type="button">04:20</button>extend this and add as many languages as
we want, and all that lyric content</span><br /><br /><span data-offset="264"><button type="button">04:24</button>and song title content will
be internationalized as well.</span><span data-offset="268"><button type="button">04:28</button>All right, so let's go out to the source</span><span data-offset="269"><button type="button">04:29</button>code of this site, which links back
to the demo that I've already shown.</span><span data-offset="273"><button type="button">04:33</button>And we're using a package called
Rosetta for string transformation.</span><br /><br /><span data-offset="276"><button type="button">04:36</button>And this is just a tiny general
purpose internationalization library.</span><span data-offset="280"><button type="button">04:40</button>We have a for-free locale aware search,
which I think is just an incredible</span><span data-offset="285"><button type="button">04:45</button>feature that you get
for free with Pagefind.</span><span data-offset="287"><button type="button">04:47</button>So we have Pagefind,
if you noticed in the demonstration.</span><br /><br /><span data-offset="290"><button type="button">04:50</button>At the very bottom,
you can search for a lyric.</span><span data-offset="292"><button type="button">04:52</button>But I didn't have to do anything to make</span><span data-offset="294"><button type="button">04:54</button>sure that the search is
segmented across language.</span><span data-offset="298"><button type="button">04:58</button>So I can search for farm here,
and we'll get the index page,</span><br /><br /><span data-offset="303"><button type="button">05:03</button>which has the title here,
and we'll also get the song page.</span><span data-offset="307"><button type="button">05:07</button>But if I search for a Spanish lyric here,</span><span data-offset="309"><button type="button">05:09</button>and I search for that,
you'll notice that it only shows Spanish</span><span data-offset="314"><button type="button">05:14</button>pages for the Spanish contextual search,
which I think is a great feature.</span><br /><br /><span data-offset="318"><button type="button">05:18</button>I didn't have to do any
extra configuration.</span><span data-offset="321"><button type="button">05:21</button>It worked automatically off</span><span data-offset="322"><button type="button">05:22</button>of the document `lang` attribute that was
set on the element for the page.</span><span data-offset="328"><button type="button">05:28</button>Now, one other thing here is we're
using album art from Spotify.</span><br /><br /><span data-offset="332"><button type="button">05:32</button>We're actually pulling in Open Graph</span><span data-offset="334"><button type="button">05:34</button>images from Spotify's song pages so
that we can show some nice album art here.</span><span data-offset="339"><button type="button">05:39</button>And this is built with Eleventy 3.0,</span><span data-offset="342"><button type="button">05:42</button>Which just released in our
alpha versions this last week.</span><br /><br /><span data-offset="346"><button type="button">05:46</button>You can go to the Eleventy blog
and read more about it.</span><span data-offset="348"><button type="button">05:48</button>I'll link it up in the show notes as well.</span><span data-offset="350"><button type="button">05:50</button>And maybe one more nice thing that you
might have noticed is that on the demo,</span><span data-offset="354"><button type="button">05:54</button>we have a nice little seasonally
appropriate snow web component here.</span><br /><br /><span data-offset="358"><button type="button">05:58</button>This is called .</span><span data-offset="359"><button type="button">05:59</button>And I'll link that up
in the show notes as well.</span><span data-offset="362"><button type="button">06:02</button>You can add that to your site.</span><span data-offset="363"><button type="button">06:03</button>What's the source code look like?</span><br /><br /><span data-offset="365"><button type="button">06:05</button>It's a pretty standard Eleventy site.</span><span data-offset="368"><button type="button">06:08</button>You can see some of the front-end
dependencies are linked up here.</span><span data-offset="372"><button type="button">06:12</button>You can see the back-end dependencies.</span><span data-offset="374"><button type="button">06:14</button>We're using Eleventy 3.0,
the Eleventy Image plugin</span><br /><br /><span data-offset="377"><button type="button">06:17</button>to fetch those Open Graph
images from Spotify.</span><span data-offset="381"><button type="button">06:21</button>We're using Pagefind for search</span><span data-offset="383"><button type="button">06:23</button>and Rosetta, as I mentioned,
to internationalize our strings.</span><span data-offset="387"><button type="button">06:27</button>Then in our configuration file,</span><br /><br /><span data-offset="388"><button type="button">06:28</button>this entire section is
just an Eleventy Image piece.</span><span data-offset="391"><button type="button">06:31</button>Then the magic gets into,
we declare our languages here.</span><span data-offset="395"><button type="button">06:35</button>These are all the languages
that we want to use on our site.</span><span data-offset="398"><button type="button">06:38</button>We set those languages as an array in our</span><br /><br /><span data-offset="401"><button type="button">06:41</button>global data, so we can use those in
various places throughout our site's code.</span><span data-offset="405"><button type="button">06:45</button>Then we also add the Eleventy
internationalization plugin here.</span><span data-offset="409"><button type="button">06:49</button>Now, this Eleventy.</span><span data-offset="411"><button type="button">06:51</button>`beforeConfig` event is a pretty little
known event, and this is to work around</span><br /><br /><span data-offset="417"><button type="button">06:57</button>synchronous versus asynchronous
limitations in your Eleventy config file.</span><span data-offset="422"><button type="button">07:02</button>This is only an Eleventy 3.0 feature,
and this is to add asynchronous</span><span data-offset="427"><button type="button">07:07</button>configuration code inside
of a synchronous configuration callback.</span><span data-offset="431"><button type="button">07:11</button>Now, again, we're setting the default
language to English here in</span><br /><br /><span data-offset="433"><button type="button">07:13</button>Eleventy's Internationalization plugin,
and this filter here is an i18n filter.</span><span data-offset="438"><button type="button">07:18</button>This is a way to translate string content</span><span data-offset="442"><button type="button">07:22</button>into the final output
that goes into the site.</span><span data-offset="445"><button type="button">07:25</button>You can see this is a shortcode that will
fetch the album art for us as well.</span><br /><br /><span data-offset="449"><button type="button">07:29</button>It's using Eleventy's Open Graph API to fetch,
again, the URL from Spotify.</span><span data-offset="454"><button type="button">07:34</button>That's pretty much it
for the configuration file.</span><span data-offset="457"><button type="button">07:37</button>Next, we can look at the index page,
which uses Eleventy's pagination feature</span><span data-offset="462"><button type="button">07:42</button>to iterate over the languages
that we set in our global data.</span><br /><br /><span data-offset="465"><button type="button">07:45</button>That's English and Spanish.</span><span data-offset="467"><button type="button">07:47</button>And so we're generating two different</span><span data-offset="469"><button type="button">07:49</button>versions of our homepage,
one for English and one for Spanish.</span><span data-offset="473"><button type="button">07:53</button>So now let's look at this
internationalization filter.</span><br /><br /><span data-offset="476"><button type="button">07:56</button>So you can see using Rosetta,
you feed it a key that then is a lookup</span><span data-offset="482"><button type="button">08:02</button>for Rosetta's string
library that it knows about.</span><span data-offset="485"><button type="button">08:05</button>And I've wired this up to two
JSON files in global data.</span><span data-offset="490"><button type="button">08:10</button>You can see one is for English,</span><br /><br /><span data-offset="491"><button type="button">08:11</button>one is for Spanish, and we have just
a bunch of internationalized strings here.</span><span data-offset="495"><button type="button">08:15</button>Rosetta will look those up,</span><span data-offset="497"><button type="button">08:17</button>but this is the key that we're
feeding in in the template.</span><span data-offset="500"><button type="button">08:20</button>So when you see songs here referenced</span><br /><br /><span data-offset="501"><button type="button">08:21</button>in our heading, that just calls out
to the key in our data file "songs".</span><span data-offset="506"><button type="button">08:26</button>So pretty straightforward,</span><span data-offset="507"><button type="button">08:27</button>we're iterating here over our
song pages that I'll go over next.</span><span data-offset="511"><button type="button">08:31</button>All right, so the next thing I want to go</span><br /><br /><span data-offset="513"><button type="button">08:33</button>over is the CloudCannon
data files that we're using.</span><span data-offset="516"><button type="button">08:36</button>And those are markdown files
that are in our songs folder.</span><span data-offset="520"><button type="button">08:40</button>So you can see we have
two different songs here and then</span><span data-offset="524"><button type="button">08:44</button>an Eleventy data file that then adds</span><br /><br /><span data-offset="526"><button type="button">08:46</button>a tag to every single
song in this directory.</span><span data-offset="529"><button type="button">08:49</button>So let's look at what one of those
song data files looks like.</span><span data-offset="531"><button type="button">08:51</button>We have our English version, which you
can see the title and the lyrics here.</span><span data-offset="534"><button type="button">08:54</button>If you scroll down, you'll see the Spanish</span><br /><br /><span data-offset="536"><button type="button">08:56</button>version here, title and lyrics, and then
we have our URLs here at the bottom.</span><span data-offset="540"><button type="button">09:00</button>That's the same for the other song as</span><span data-offset="542"><button type="button">09:02</button>well: English, Spanish,
and URLs at the bottom.</span><span data-offset="545"><button type="button">09:05</button>All right, so we have our markdown files.</span><br /><br /><span data-offset="546"><button type="button">09:06</button>Those are populated into a collection
using this `tags` property here.</span><span data-offset="551"><button type="button">09:11</button>We can now iterate over
our songs in our songs.liquid template.</span><span data-offset="557"><button type="button">09:17</button>And you can see that in the
Pagination here as well.</span><span data-offset="559"><button type="button">09:19</button>Importantly, in the before callback here,</span><br /><br /><span data-offset="562"><button type="button">09:22</button>this is a callback that Eleventy uses to allow
folks to modify the Pagination data set</span><span data-offset="567"><button type="button">09:27</button>before it's fed into Eleventy
to generate pages.</span><span data-offset="571"><button type="button">09:31</button>And I won't go over this code too much,</span><span data-offset="572"><button type="button">09:32</button>but we're using it here to iterate over
the songs: for each individual song</span><br /><br /><span data-offset="577"><button type="button">09:37</button>we want to output an English version</span><span data-offset="579"><button type="button">09:39</button>of the page and a Spanish
version of the page.</span><span data-offset="581"><button type="button">09:41</button>We're iterating over the songs,
we're iterating over the languages.</span><span data-offset="584"><button type="button">09:44</button>We're creating one page for each
song and language combination.</span><br /><br /><span data-offset="590"><button type="button">09:50</button>And one last piece I want to go over
here is we have our Eleventy layout file.</span><span data-offset="594"><button type="button">09:54</button>This is an Eleventy internationalization plugin
feature that gives you a list of links</span><span data-offset="600"><button type="button">10:00</button>for the current page in all
of the other languages.</span><span data-offset="604"><button type="button">10:04</button>We're just iterating over
that and showing those.</span><br /><br /><span data-offset="606"><button type="button">10:06</button>Let me show you what
that looks like in the demo.</span><span data-offset="608"><button type="button">10:08</button>That's this piece right here.</span><span data-offset="610"><button type="button">10:10</button>It's also available in English and click
through there, also available in Spanish.</span><span data-offset="615"><button type="button">10:15</button>And if we added more languages to our</span><br /><br /><span data-offset="617"><button type="button">10:17</button>site, then those links would
also be represented here.</span><span data-offset="621"><button type="button">10:21</button>We could add Spanish, German,
whatever you want to add.</span><span data-offset="624"><button type="button">10:24</button>All right, so that's how you set up
an internationalization site using Eleventy</span><span data-offset="630"><button type="button">10:30</button>with CloudCannon while allowing your
editing team to come in and edit</span><br /><br /><span data-offset="634"><button type="button">10:34</button>the internationalized strings as
well right in the CloudCannon app.</span><span data-offset="638"><button type="button">10:38</button>Hope you enjoyed it.
If you have any more questions,</span><span data-offset="640"><button type="button">10:40</button>you can post them in the comments below
or just send us a message on social media.</span><span data-offset="645"><button type="button">10:45</button>Thanks, and keep building for the web.</span>
<script type="module">
class YouTubeDeepLink extends HTMLElement {
#watching = false;
#video = null;
static tagName = "youtube-deep-link";
static classes = {
active: "active"
};
static register(tagName) {
if(!("customElements" in globalThis)) {
return;
}
customElements.define(tagName || this.tagName, this);
}
get videoId() {
return this.getAttribute("videoid");
}
set video(v) {
this.#video = v;
}
get video() {
if(!this.#video) {
this.#video = document.querySelector(`lite-youtube[videoid="${this.videoId}"]`);
}
return this.#video;
}
connectedCallback() {
let buttons = this.querySelectorAll("[data-offset]");
for(let btn of buttons) {
btn.addEventListener("click", (e) => {
let target = e.target.closest("[data-offset]");
let offset = parseInt(target.getAttribute("data-offset"), 10); // in seconds
this.setActiveLink(offset);
this.seek(offset);
e.preventDefault();
});
}
}
async seek(offset) {
if(!this.video) {
// just scroll to the video, the viewport isn’t big enough to show the video and transcript together
let video = document.querySelector(`[videoid="${this.videoId}"]`);
video?.parentNode?.scrollIntoView(true);
} else {
let player = await this.video.getYTPlayer();
this.watch(); // keep the links active
// https://developers.google.com/youtube/iframe_api_reference#seekTo
player?.seekTo(offset, true);
}
}
setActiveLink(timeInSeconds) {
let activeElement = this.querySelector(`[data-offset="${Math.round(timeInSeconds)}"]`);
if(activeElement) {
// remove class from previous
let previousElement = this.querySelector(`.${YouTubeDeepLink.classes.active}`);
previousElement?.classList.remove(YouTubeDeepLink.classes.active);
// add to new
activeElement.classList.add(YouTubeDeepLink.classes.active);
}
}
async watch() {
if(this.#watching) {
return;
}
let player = await this.video.getYTPlayer();
player.addEventListener("onStateChange", ({data}) => {
if(data === 1) { // playing
this.#watching = setInterval(() => {
this.setActiveLink(player.getCurrentTime())
}, 500);
} else { // paused
clearInterval(this.#watching);
}
})
}
}
YouTubeDeepLink.register();
</script></youtube-deep-link></div>
Netlify’s Disingenuous Survey-based Attack on Next.js (and Eleventy, too)2023-12-19T00:00:00Zhttp://www.zachleat.com/web/netlify-and-nextjs/
<p>As <a href="https://www.zachleat.com/web/jamstack-future/">Netlify distances itself from Jamstack</a> in an attempt to survive a VC-funded battle with Vercel, they have also rebranded the <em>Jamstack Community Survey</em> to a more generic <a href="https://www.netlify.com/resources/ebooks/the-state-of-web-development-2023/"><em>The State of Web Development</em> report</a> (behind an email signup form). It <em>should</em> be called the Netlify Community Survey (as the results only represent Netlify customers) but I digress.</p>
<p>A few disclaimers before we get started:</p>
<ol>
<li>I am the creator and maintainer of <a href="https://www.11ty.dev/">Eleventy</a>.</li>
<li>From 2020–2023 I was <a href="https://www.zachleat.com/resume/#professional">employed</a> by Netlify.</li>
<li>In 2021 I worked on the Jamstack Community Survey results website (but was not involved in the survey data collection nor the results content).</li>
</ol>
<h2 id="problems" tabindex="-1">Problems <a href="https://www.zachleat.com/web/netlify-and-nextjs/#problems" aria-hidden="true" class="direct-link">#</a></h2>
<p>This year’s report has a few changes to the presentation of results that I think have traversed the report into territory that I called <a href="https://fediverse.zachleat.com/@zachleat/111598996307378754">“intellectually dishonest”</a> on Mastodon. My default in these situations is to let these things go but enough people have tagged me on this—while also drawing some damaging conclusions from the disingenuity in this report—that I feel the need to weigh in (though frankly I’m a little annoyed that I need to spend time on it at all).</p>
<p>My primary beef with this year’s report is on page 10 (of 13) and features a section on rendering frameworks and site generators, with one and only one chart documenting changes in satisfaction score versus change in usage.</p>
<p><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/6c8TxHHdhO-400.avif 400w, https://www.zachleat.com/img/built/6c8TxHHdhO-980.avif 980w, https://www.zachleat.com/img/built/6c8TxHHdhO-1324.avif 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/6c8TxHHdhO-400.jpeg 400w, https://www.zachleat.com/img/built/6c8TxHHdhO-980.jpeg 980w, https://www.zachleat.com/img/built/6c8TxHHdhO-1324.jpeg 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><img loading="eager" decoding="async" src="https://www.zachleat.com/img/built/6c8TxHHdhO-400.jpeg" alt="This chart shows Astro’s stellar growth this year, the runaway winner for increased satisfaction and usage. Next and 11ty are shown in the bottom left corner, losing usage and satisfaction" width="1324" height="831" /></picture></p>
<p>This chart looks bad for Next. It also looks bad for Eleventy. But the bigger problem here is that this chart shows <em>changes</em> without any context of absolute scores. Next might have gotten some caveats in the text written in the section but 11ty did not.</p>
<p>In previous years, the report <a href="https://jamstack.org/survey/2022/#web-frameworks">discussed absolute scores prior to deltas</a> because <em>this context is important</em>.</p>
<p><em>(I also took special note that the label for Gatsby—a framework purchased by Netlify and under very recent <a href="https://twitter.com/FredKSchott/status/1693007599803752638">criticism</a> for languishing investment—was obscured behind Gridsome)</em></p>
<p>On page 32 (of 39) of the report buried <em>deep</em> into the Appendices section the report finally includes the real scores (conveniently unordered):</p>
<p><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/_AidvZf8j2-400.avif 400w, https://www.zachleat.com/img/built/_AidvZf8j2-980.avif 980w, https://www.zachleat.com/img/built/_AidvZf8j2-1324.avif 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/_AidvZf8j2-400.jpeg 400w, https://www.zachleat.com/img/built/_AidvZf8j2-980.jpeg 980w, https://www.zachleat.com/img/built/_AidvZf8j2-1324.jpeg 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><img loading="eager" decoding="async" src="https://www.zachleat.com/img/built/_AidvZf8j2-400.jpeg" alt="Astro has the best positive sentiment too at 87%, Next is 78% and Eleventy is 74%" width="1324" height="1311" /></picture></p>
<p>Now we learn that Next.js is ranked #3 (tied with SolidStart and Remix) for satisfaction and 11ty is ranked #6 (tied with Nuxt).</p>
<p>Here’s what the chart looks like using absolute values for satisfaction percentages and usage, painting a story that Netlify probably doesn’t want to share:</p>
<p><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/3ko57G-YOe-400.avif 400w, https://www.zachleat.com/img/built/3ko57G-YOe-980.avif 980w, https://www.zachleat.com/img/built/3ko57G-YOe-1324.avif 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/3ko57G-YOe-400.jpeg 400w, https://www.zachleat.com/img/built/3ko57G-YOe-980.jpeg 980w, https://www.zachleat.com/img/built/3ko57G-YOe-1324.jpeg 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><img loading="eager" decoding="async" src="https://www.zachleat.com/img/built/3ko57G-YOe-400.jpeg" alt="Next.js is the clear winner here. A cluster of Nuxt, Astro, 11ty, and Sveltekit all sit together with high scores and similar usage. Gatsby is an outlier with large usage but low satisfaction." width="1324" height="869" /></picture></p>
<h2 id="without-ascribing-malice" tabindex="-1">Without ascribing malice <a href="https://www.zachleat.com/web/netlify-and-nextjs/#without-ascribing-malice" aria-hidden="true" class="direct-link">#</a></h2>
<p>I am aware that Netlify has historically employed a Data Scientist with statistics and data analysis background to conduct this survey and prepare the report. This person departed the company months ago. The Data Scientist in question was very good at documenting and publishing the survey and report’s methodology and you can see <a href="https://jamstack.org/survey/2022/community-survey-2022-methodology.pdf">one such (2022) document</a>.</p>
<p>The 2023 report did not include a methodology document.</p>
<p>The 2022 methodology document reported <em>how</em> the data was collected (sources) and margins of error and included the following notable section at the end:</p>
<blockquote>
<p>Conclusions we cannot draw:</p>
<p>We do not believe these results are indicative of preferences amongst web developers as a whole. We are also not making claims as to:</p>
<ul>
<li>Popularity (or not) of Netlify products and services, given bias in respondents</li>
</ul>
</blockquote>
<p>The 2023 report name change to <em>The State of Web Development</em> seemingly goes against the first point.</p>
<p>The 2023 report also goes against the second point by eschewing any mention of bias. It (surprisingly) includes a section on the popularity of Netlify itself:</p>
<p><picture><source type="image/avif" srcset="https://www.zachleat.com/img/built/1GFda9fcMe-400.avif 400w, https://www.zachleat.com/img/built/1GFda9fcMe-980.avif 980w, https://www.zachleat.com/img/built/1GFda9fcMe-1324.avif 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><source type="image/jpeg" srcset="https://www.zachleat.com/img/built/1GFda9fcMe-400.jpeg 400w, https://www.zachleat.com/img/built/1GFda9fcMe-980.jpeg 980w, https://www.zachleat.com/img/built/1GFda9fcMe-1324.jpeg 1324w" sizes="(min-width: 75em) 44.5625em, (min-width: 61.25em) 40.6875em, (min-width: 41.25em) 36.8125em, 96vw" /><img loading="eager" decoding="async" src="https://www.zachleat.com/img/built/1GFda9fcMe-400.jpeg" alt="88% of Netlify customers want to use Netlify more. 79% of Netlify customers want to use Vercel more." width="1324" height="1457" /></picture></p>
<p>I was taken-aback that Netlify would <em>publish</em> statistics to show what percentage of Netlify customers want to use Vercel more—but apparently it’s 79%.</p>
<h2 id="conclusions" tabindex="-1">Conclusions <a href="https://www.zachleat.com/web/netlify-and-nextjs/#conclusions" aria-hidden="true" class="direct-link">#</a></h2>
<p>Importantly: After Eleventy’s <a href="https://www.zachleat.com/web/eleventy-side-project/">Netlify-induced setbacks</a> this year, we are <a href="https://www.11ty.dev/blog/canary-eleventy-v3/">back on our feet</a> and we will continue to punch far above our <code>node_modules</code> weight-class. Many of the alternative frameworks discussed here are VC-funded with full-time developers and have <em>millions</em> in funding. We don’t.</p>
<p>Eleventy’s project goals are different: stay focused, keep our core feature set small, and ruthlessly minimize our external dependencies for a long-term future.</p>
<p>On Astro (since folks have asked), I will say that Astro and Eleventy share the same zero JavaScript footprint vision for the web and in that regard we are allies in the web development framework melee.</p>
<p>Reading the report, it’s clear that Netlify has a vested interest in elevating Astro because Astro is best poised to dethrone Next.js. If Astro wins, Netlify wins. Perhaps surprisingly I <em>also</em> believe that if Astro wins, Eleventy wins! It is <em>not</em> a zero-sum game and I know that 2022 Netlify <a href="https://www.zachleat.com/web/jamstackconf-oss-panel/">believed this</a> (even if 2023 Netlify does not).</p>
<p>Appreciate y’all—and keep building for the web!</p>
Eleventy v3 with ESM support now on the canary channel2023-12-18T00:00:00Zhttp://www.zachleat.com/web/eleventy-v3-alpha/
<p>The very first Eleventy v3 alpha release is out in the wild and you can try it out on your web site. The two big flagship features (so far) are ESM support (while keeping CommonJS support) and asynchronous configuration callbacks in both ESM and CommonJS.</p>
<p><code>eleventy.config.js</code> (in a CommonJS project) or <code>eleventy.config.cjs</code> (in an ESM project):</p>
<pre class="language-js"><code class="language-js">module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</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>
<span class="token comment">// …</span>
<span class="token punctuation">}</span></code></pre>
<p><code>eleventy.config.js</code> (in an ESM project):</p>
<pre class="language-js"><code class="language-js"><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>
<span class="token comment">// …</span>
<span class="token punctuation">}</span></code></pre>
<p>Try it out!</p>
<p><em>Read more: <a href="https://www.11ty.dev/blog/canary-eleventy-v3/"><code>11ty.dev/blog/canary-eleventy-v3/</code></a></em></p>
<script type="module" src="https://www.zachleat.com/static/browser-window.js"></script>
<div><browser-window mode="dark" icon="" url="https://www.11ty.dev/blog/canary-eleventy-v3/" shadow="" flush=""><a href="https://www.11ty.dev/blog/canary-eleventy-v3/" class="favicon-optout"><img alt="Screenshot image for https://v1.screenshot.11ty.dev/https%3A%2F%2Fwww.11ty.dev%2Fblog%2Fcanary-eleventy-v3%2F/opengraph/_x202403_4/" loading="lazy" decoding="async" src="https://v1.screenshot.11ty.dev/https%3A%2F%2Fwww.11ty.dev%2Fblog%2Fcanary-eleventy-v3%2F/opengraph/_x202403_4/" width="1200" height="630" /></a></browser-window></div>
snow-fall Web Component2023-12-15T00:00:00Zhttp://www.zachleat.com/web/snow-fall/
<p><code><snow-fall></code> is a zero-dependency, JavaScript web component to add snow to your web site (or to an element on your web site). This is a web component implementation of <a href="https://codepen.io/alphardex/pen/dyPorwJ">this Codepen</a> from <a href="https://codepen.io/alphardex"><code>alphardex</code></a>.</p>
<ul>
<li><a href="https://zachleat.github.io/snow-fall/demo.html">Demo</a></li>
<li><a href="https://github.com/zachleat/snow-fall">Source code on GitHub</a></li>
</ul>
<script type="module" src="https://www.zachleat.com/static/snow-fall.js"></script>
<is-land on:media="(prefers-reduced-motion: no-preference)">
<snow-fall></snow-fall>
</is-land>
<h2 id="usage" tabindex="-1">Usage <a href="https://www.zachleat.com/web/snow-fall/#usage" aria-hidden="true" class="direct-link">#</a></h2>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</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>snow-fall.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"></</span>script</span><span class="token punctuation">></span></span>
<span class="token comment"><!-- Add snow to the page --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span>
<span class="token comment"><!-- Add snow to the first child element --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">width</span><span class="token punctuation">:</span> 300px<span class="token punctuation">;</span> <span class="token property">height</span><span class="token punctuation">:</span> 300px</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span></code></pre>
<p>Installable from npm:</p>
<pre><code>npm install @zachleat/snow-fall
</code></pre>
<h3 id="use-with-lessis-landgreater" tabindex="-1">Use with <code><is-land></code> <a href="https://www.zachleat.com/web/snow-fall/#use-with-lessis-landgreater" aria-hidden="true" class="direct-link">#</a></h3>
<p>This is best used with <a href="https://www.zachleat.com/web/is-land/"><code><is-land></code></a> to respect user preferences for reduced motion:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>is-land</span> <span class="token attr-name"><span class="token namespace">on:</span>media</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>(prefers-reduced-motion: no-preference)<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>is-land</span><span class="token punctuation">></span></span></code></pre>
<h3 id="change-the-snow-color" tabindex="-1">Change the snow color <a href="https://www.zachleat.com/web/snow-fall/#change-the-snow-color" aria-hidden="true" class="direct-link">#</a></h3>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- Change snow color --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--snow-fall-color</span><span class="token punctuation">:</span> rebeccapurple</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span></code></pre>
<h3 id="change-the-number-of-snowflakes" tabindex="-1">Change the number of snowflakes <a href="https://www.zachleat.com/web/snow-fall/#change-the-number-of-snowflakes" aria-hidden="true" class="direct-link">#</a></h3>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- Default: 100 --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span> <span class="token attr-name">count</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>200<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span></code></pre>
<h3 id="change-the-size-of-the-snowflakes" tabindex="-1">Change the size of the snowflakes <a href="https://www.zachleat.com/web/snow-fall/#change-the-size-of-the-snowflakes" aria-hidden="true" class="direct-link">#</a></h3>
<p><em>Added in v1.0.2</em></p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- Default: 10px --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span> <span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span><span class="token value css language-css"><span class="token property">--snow-fall-size</span><span class="token punctuation">:</span> 20px</span><span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span></code></pre>
<h3 id="force-the-rendering-mode-(pageelement)" tabindex="-1">Force the rendering mode (page/element) <a href="https://www.zachleat.com/web/snow-fall/#force-the-rendering-mode-(pageelement)" aria-hidden="true" class="direct-link">#</a></h3>
<p><em>You probably don’t need this.</em></p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span> <span class="token attr-name">mode</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>page<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>snow-fall</span> <span class="token attr-name">mode</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>element<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>snow-fall</span><span class="token punctuation">></span></span></code></pre>
One YouTube Embed weighs almost 1.2 MB2023-12-11T00:00:00Zhttp://www.zachleat.com/web/youtube-embeds/
<p>The default YouTube embed is… well… atrocious (sorry). Consider an embed for this <a href="https://www.youtube.com/watch?v=YYJpFdEaAuc">sample video</a>:</p>
<ul>
<li><code>1149 kB</code> total weight, including:</li>
<li><code>971 kB</code> JavaScript</li>
<li><code>61 kB</code> Poster Image (jpeg, 1280×720)</li>
<li><code>48 kB</code> CSS</li>
<li><code>41 kB</code> of iframed HTML</li>
<li><code>28 kB</code> for ×3 Roboto Web Fonts</li>
</ul>
<p><em>(compressed wire weights reported)</em></p>
<p>This, uh, conspicuously—does not include any preloading of video content.</p>
<p>The weight also grows linearly with every embed—resources are <em>not</em> shared: two embeds weigh 2.4 MB; three embeds weigh 3.6 MB (you get the idea).</p>
<h2 id="a-better-solution" tabindex="-1">A better solution <a href="https://www.zachleat.com/web/youtube-embeds/#a-better-solution" aria-hidden="true" class="direct-link">#</a></h2>
<p>Instead of the above, I typically use <a href="https://github.com/paulirish/lite-youtube-embed">Paul Irish’s <code>lite-youtube</code> web component</a> for better YouTube embeds on my site. The results speak for themselves: the <em>entirety of this blog post</em> weighs a grand total of 229 kB (and that includes the demo YouTube embed below)—just 17% of the original YouTube embed.</p>
<p>Even better, <code><lite-youtube></code> is a web component which means it can be managed with <a href="https://www.zachleat.com/web/is-land/"><code><is-land></code></a>.</p>
<p>If you look at just the weight of <code><lite-youtube></code>:</p>
<ul>
<li><code>31 kB</code> total weight, including:</li>
<li><code>6 kB</code> JavaScript</li>
<li><code>3 kB</code> CSS</li>
<li><code>22 kB</code> Poster Image</li>
</ul>
<p><strong>This is a huge improvement: a reduction of 1149 kB to 31 kB.</strong></p>
<h2 id="higher-quality-posters" tabindex="-1">Higher quality posters <a href="https://www.zachleat.com/web/youtube-embeds/#higher-quality-posters" aria-hidden="true" class="direct-link">#</a></h2>
<p><code><lite-youtube></code> does provide an <em>eagerly loaded</em> JavaScript-only 480×360 poster <code>background-image</code> for you (usually ~20–30kB). This behavior is skipped if you’ve added your own <code>background-image</code> in a <code>style</code> attribute on the tag.</p>
<p>This works fine but I wanted my poster images to be a bit higher quality and I used the 11ty <a href="https://www.11ty.dev/docs/services/opengraph/">OpenGraph API</a> to easily accomplish this. I pointed the <code>background-image</code> property to the API and required a JPEG format in the output, which seemed to average about 80–100kB each, instead (and didn’t require JavaScript).</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>lite-youtube</span>
<span class="token attr-name">videoid</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>YYJpFdEaAuc<span class="token punctuation">"</span></span>
<span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>
background-image: url(<span class="token punctuation">'</span>https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DYYJpFdEaAuc/auto/jpeg/<span class="token punctuation">'</span>);
<span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
…
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>lite-youtube</span><span class="token punctuation">></span></span></code></pre>
<h3 id="one-weird-trick-to-lazy-load-the-poster" tabindex="-1">One Weird Trick to Lazy Load the Poster <a href="https://www.zachleat.com/web/youtube-embeds/#one-weird-trick-to-lazy-load-the-poster" aria-hidden="true" class="direct-link">#</a></h3>
<p>These components were being managed by <a href="https://github.com/zachleat/zachleat.com/blob/80c605c0654ff509a996d5dbbef7c142fae01f7b/_components/youtube-lite-player.webc"><code><is-land></code> for lazy initialization</a> and despite the components being lazily initialized the poster images were still being eagerly loaded (<em>before</em> the is-lands were initialized).</p>
<p>Here’s how I fixed it:</p>
<pre class="language-css"><code class="language-css"><span class="token selector">is-land[ready] lite-youtube</span> <span class="token punctuation">{</span>
<span class="token property">--yt-poster-img-url</span><span class="token punctuation">:</span> <span class="token function">var</span><span class="token punctuation">(</span>--yt-poster-img-url-lazy<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>We are using a double custom property method to work with the existing feature of <code>background-image</code> overrides in <code>lite-youtube</code>:</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>lite-youtube</span>
<span class="token attr-name">videoid</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>YYJpFdEaAuc<span class="token punctuation">"</span></span>
<span class="token special-attr"><span class="token attr-name">style</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>
--yt-poster-img-url-lazy: url(<span class="token punctuation">'</span>https://v1.opengraph.11ty.dev/https%3A%2F%2Fyoutube.com%2Fwatch%3Fv%3DYYJpFdEaAuc/auto/jpeg/<span class="token punctuation">'</span>);
background-image: var(--yt-poster-img-url);
<span class="token punctuation">"</span></span></span><span class="token punctuation">></span></span>
…
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>lite-youtube</span><span class="token punctuation">></span></span></code></pre>
<p>As this is using <code><is-land></code> this re-enables the JavaScript dependency for the poster images but I’m okay with that, as I also include a text link to YouTube for each video embed (as part of the <a href="https://github.com/zachleat/zachleat.com/blob/80c605c0654ff509a996d5dbbef7c142fae01f7b/_components/youtube-lite-player.webc">WebC component</a>).</p>
<p>You can see all of this in action below:</p>
<div><youtube-lite-player>
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=YYJpFdEaAuc&t=188s" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="YYJpFdEaAuc" js-api="" params="start=188" playlabel="Play: Partial Hydration and Islands Architecture—Eleventy 🎈 Weekly №12" 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%3DYYJpFdEaAuc/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="Partial Hydration and Islands Architecture—Eleventy 🎈 Weekly №12" href="https://youtube.com/watch?v=YYJpFdEaAuc&t=188s"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=YYJpFdEaAuc&t=188s" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Partial Hydration and Islands Architecture—Eleventy 🎈 Weekly №12</em></a></youtube-link></youtube-lite-player></div>
is-land Web Component2023-12-07T00:00:00Zhttp://www.zachleat.com/web/is-land/
<p><em>This web component originally shipped May 2022 but was conspicuously missing from my web site.</em></p>
<p><code><is-land></code> enables <a href="https://jasonformat.com/islands-architecture/">Islands Architecture</a>* on any web site. It’s an <a href="https://www.zachleat.com/web/a-taxonomy-of-web-component-types/">HTML web component</a>. It has zero dependencies. It doesn’t require any server-side or build integration.</p>
<p><em>*with credit to <a href="https://sylormiller.com/">Katie Sylor-Miller</a>.</em></p>
<p>I use this web component <em>everywhere</em>—it’s a good one. <a href="https://astro.build/">Astro</a>’s has popularized Islands Architecture <em>(great!)</em> and this component serves as a build-independent decoupled alternative.</p>
<p>If you aren’t yet familiar, Islands Architecture (apologies for this oversimplification) is turbo-charged <em>lazy loading</em>. It’s a way to initialize components and resources for sections (islands) of your web site when certain conditions are met: the island becomes visible, the page is idle, the viewport is a certain size, on events (click, mouseover, etc), and respecting user preferences (save data, reduce motion, etc).</p>
<ul>
<li>An overwhelming number of <a href="https://is-land.11ty.dev/">Demos</a></li>
<li><a href="https://github.com/11ty/is-land">Source code</a></li>
<li><a href="https://www.11ty.dev/docs/plugins/partial-hydration/">Documentation on 11ty.dev</a></li>
</ul>
<p>You <em>can</em> use this with JavaScript component frameworks but it’s primarily intended for use with Web Components.</p>
<h2 id="usage" tabindex="-1">Usage <a href="https://www.zachleat.com/web/is-land/#usage" aria-hidden="true" class="direct-link">#</a></h2>
<p>Say you have a <code>my-component.js</code> web component definition file with the following content:</p>
<pre class="language-js"><code class="language-js"><span class="token comment">// my-component.js</span>
customElements<span class="token punctuation">.</span><span class="token function">define</span><span class="token punctuation">(</span><span class="token string">"my-component"</span><span class="token punctuation">,</span> <span class="token keyword">class</span> <span class="token class-name">extends</span> HTMLElement <span class="token punctuation">{</span>
<span class="token comment">// …</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>You can reference it on your web site like this:</p>
<pre class="language-html"><code class="language-html"><span class="token comment"><!-- Make sure is-land.js is first --></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</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>is-land.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"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</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>my-component.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"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>is-land</span> <span class="token attr-name"><span class="token namespace">on:</span>visible</span> <span class="token attr-name"><span class="token namespace">on:</span>idle</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-component</span><span class="token punctuation">></span></span>Fallback content.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-component</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>is-land</span><span class="token punctuation">></span></span></code></pre>
<p>Now <em>any</em> web components inside the <code><is-land></code> (that were not yet <code>:defined</code> when <code>is-land.js</code> loaded) will delay their initialization until the loading conditions are met.</p>
<h3 id="even-lazier-loading" tabindex="-1">Even lazier loading <a href="https://www.zachleat.com/web/is-land/#even-lazier-loading" aria-hidden="true" class="direct-link">#</a></h3>
<p>If you want to lazily load the component definition when the island initializes, you can do that too.</p>
<pre class="language-html"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</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>is-land.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"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>is-land</span> <span class="token attr-name"><span class="token namespace">on:</span>visible</span> <span class="token attr-name"><span class="token namespace">on:</span>idle</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>my-component</span><span class="token punctuation">></span></span>Fallback content.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>my-component</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>template</span> <span class="token attr-name">data-island</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</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>my-component.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"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>template</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>is-land</span><span class="token punctuation">></span></span></code></pre>
<ul>
<li>You could add a <code><link rel="stylesheet" href="my-component.css"></code> in there too, if your component has separate styles too (any valid markup works).</li>
<li>Use <code>data-island="replace"</code> to replace the Island’s content with the new template content.</li>
</ul>
<h3 id="loading-conditions" tabindex="-1">Loading conditions <a href="https://www.zachleat.com/web/is-land/#loading-conditions" aria-hidden="true" class="direct-link">#</a></h3>
<p>When using multiple loading conditions, all of the loading conditions for an island must be true before the island initializes.</p>
<ul>
<li><code>on:visible</code></li>
<li><code>on:idle</code>
<ul>
<li>using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback"><code>requestIdleCallback</code></a></li>
</ul>
</li>
<li><code>on:interaction</code>
<ul>
<li>defaults to <code>touchstart,click</code>, but you can change this to any event with <code>on:interaction="mouseenter,focusin"</code></li>
</ul>
</li>
<li><code>on:media</code>
<ul>
<li><code>on:media="(min-width: 64em)"</code> for Viewport size.</li>
<li><code>on:media="(prefers-reduced-motion: no-preference)"</code> to only initialize when the user has no motion preference.</li>
</ul>
</li>
<li><code>on:save-data="false"</code> to only initialize when the user has <em>not</em> requested to <a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData">Save Data</a>.</li>
</ul>
<h2 id="related" tabindex="-1">Related <a href="https://www.zachleat.com/web/is-land/#related" aria-hidden="true" class="direct-link">#</a></h2>
<p>Read the <a href="https://www.11ty.dev/docs/plugins/partial-hydration/">documentation on 11ty.dev</a> or check out a few introductory videos below:</p>
<div class="fl">
<youtube-lite-player poster-size="medium">
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=YYJpFdEaAuc&t=188s" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="YYJpFdEaAuc" js-api="" params="start=188" playlabel="Play: Partial Hydration and Islands Architecture—Eleventy 🎈 Weekly №12" 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%3DYYJpFdEaAuc/medium/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="Partial Hydration and Islands Architecture—Eleventy 🎈 Weekly №12" href="https://youtube.com/watch?v=YYJpFdEaAuc&t=188s"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=YYJpFdEaAuc&t=188s" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Partial Hydration and Islands Architecture—Eleventy 🎈 Weekly №12</em></a></youtube-link></youtube-lite-player>
<youtube-lite-player poster-size="medium">
<style>
is-land lite-youtube {
background-color: #eee;
border-radius: .5em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
</style>
<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=V9hWgVV_5mg&t=399s" shadow="">
<is-land on:visible="" class="fluid-width-video-wrapper">
<lite-youtube videoid="V9hWgVV_5mg" js-api="" params="start=399" playlabel="Play: Hydrating Components with `is-land` and Framework SSR—Eleventy 🎈 Weekly №13" 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%3DV9hWgVV_5mg/medium/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="Hydrating Components with `is-land` and Framework SSR—Eleventy 🎈 Weekly №13" href="https://youtube.com/watch?v=V9hWgVV_5mg&t=399s"><style>
.lite-youtube-link {
--ellipsis-lines: 2;
margin-top: 0.5em; /* 8px /16 */
background: url(https://v1.indieweb-avatar.11ty.dev/https%3A%2F%2Fyoutube.com/) no-repeat;
background-size: 1em auto;
background-position: 0 .35em;
padding-left: 1.25em;
line-height: 1.4;
}
</style>
<a href="https://youtube.com/watch?v=V9hWgVV_5mg&t=399s" class="lite-youtube-link text-ellipsis-multi">Watch on YouTube: <em>Hydrating Components with `is-land` and Framework SSR—Eleventy 🎈 Weekly №13</em></a></youtube-link></youtube-lite-player>
</div>