<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/assets/xsl/rss-style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
  <title>Darek Kay</title>
  <subtitle>Darek Kay is a professional front-end developer and an accessibility advocate.</subtitle>
  <link href="https://darekkay.com/atom.xml" rel="self"/>
  <link href="https://darekkay.com/"/>
  <updated>2024-02-01T11:39:16Z</updated>
  <id>https://darekkay.com/</id>
  <author>
    <name>Darek Kay</name>
  </author>
    
    <entry>
      <title>Website themes with uBlock Origin</title>
      <summary>Creating custom website skins with the uBlock Origin ad blocker.</summary>
      <link href="https://darekkay.com/blog/ublock-website-themes/"/>
      <published>2024-02-01T11:39:16Z</published>
      <updated>2024-02-01T11:39:16Z</updated>
      <id>https://darekkay.com/blog/ublock-website-themes/</id>
      <media:thumbnail url="https://darekkay.com/blog/ublock-website-themes/cover.png" />
      <content type="html">&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/ublock-website-themes/hero-image.webp&quot; srcset=&quot;https://darekkay.com/blog/ublock-website-themes/hero-image.webp, https://darekkay.com/blog/ublock-website-themes/hero-image-2x.webp 2x&quot; alt=&quot;Hacker news screenshot, half-light and half-dark theme&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;322&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Browser extensions like Stylish, Stylus or Tampermonkey make it possible to create custom website themes/skins. At the same time, I try to lower the number of add-ons that I use, mostly due to security and performance reasons. Interestingly, the &lt;a href=&quot;https://ublockorigin.com/&quot;&gt;uBlock Origin&lt;/a&gt; ad blocker can achieve similar results. We can use the &lt;code&gt;style&lt;/code&gt; &lt;a href=&quot;https://github.com/gorhill/uBlock/wiki/Static-filter-syntax#subjectstylearg&quot;&gt;action operator&lt;/a&gt; to adjust the CSS of any website. Let&#39;s change the header/footer background color on this blog:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;darekkay.com##.&lt;span class=&quot;token property&quot;&gt;inverted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #2e2e2a &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this technique, we can create custom website themes. Here&#39;s my dark mode skin for &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;news.ycombinator.com##&lt;span class=&quot;token property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #CCCCCC &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #1A1A1A &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##&lt;span class=&quot;token property&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #2B2B2B &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##&lt;span class=&quot;token property&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #DFDFDF &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##table&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tr&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; td&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; .pagetop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; .&lt;span class=&quot;token property&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #CCCCCC &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##&lt;span class=&quot;token property&quot;&gt;td&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1px solid #2B2B2B &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #2B2B2B &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##&lt;span class=&quot;token property&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inherit &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; .&lt;span class=&quot;token property&quot;&gt;c00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #eee &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##.c00 &lt;span class=&quot;token property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rgb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;49&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 140&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 212&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##.comhead&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; .&lt;span class=&quot;token property&quot;&gt;subtext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #828282 &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##.comhead &gt; a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; .subtext &gt; &lt;span class=&quot;token property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; orange &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##.comhead &lt;span class=&quot;token property&quot;&gt;font&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #5a5a5a &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##.c5a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; .c88&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; .&lt;span class=&quot;token property&quot;&gt;c9c&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #999 &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##&lt;span class=&quot;token property&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; black &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##&lt;span class=&quot;token property&quot;&gt;textarea&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #E0E0E0 &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;border-left&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 12px solid #CCCCCC &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
news.ycombinator.com##font[color=&lt;span class=&quot;token string&quot;&gt;&quot;#000000&quot;&lt;/span&gt;]&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; #a3b72c &lt;span class=&quot;token important&quot;&gt;!important&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bonus tip: to synchronize your styles across all devices, consider hosting your rules on GitHub. You can click the &amp;quot;Raw&amp;quot; button and provide the URL as a custom filter list to uBlock Origin. Check out &lt;a href=&quot;https://github.com/darekkay/config-files/blob/master/adblocker/styling.txt&quot;&gt;my styling rules&lt;/a&gt; and their &lt;a href=&quot;https://raw.githubusercontent.com/darekkay/config-files/master/adblocker/styling.txt&quot;&gt;raw version&lt;/a&gt;.&lt;/p&gt;
</content>
      <category term="quick-tip" /><category term="theme" /><category term="css" />
    </entry>
    
    <entry>
      <title>Video subtitles, captions, audio descriptions and transcripts</title>
      <summary>The difference between subtitles, closed captions, audio descriptions and transcriptions, including related accessibility guidelines.</summary>
      <link href="https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/"/>
      <published>2023-12-11T14:08:34Z</published>
      <updated>2023-12-11T14:08:34Z</updated>
      <id>https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/</id>
      <media:thumbnail url="https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/cover.png" />
      <content type="html">&lt;p&gt;As I was preparing the requirements for an accessible web video player, there was some confusion around &lt;em&gt;subtitles&lt;/em&gt;, &lt;em&gt;closed captions&lt;/em&gt;, &lt;em&gt;audio descriptions&lt;/em&gt; and &lt;em&gt;transcripts&lt;/em&gt;. In this post, I use interactive examples to show the difference. I also provide related success criteria from the Web Content Accessibility Guidelines (WCAG).&lt;/p&gt;
&lt;h2 id=&quot;subtitles&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/#subtitles&quot;&gt;
      Subtitles&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Subtitles are &lt;strong&gt;text tracks&lt;/strong&gt; matching only &lt;strong&gt;spoken dialogs&lt;/strong&gt; of an audio track. They are meant for people who perceive the audio but need the text for clarification or language translation, e.g. when watching a movie in a foreign language.&lt;/p&gt;
&lt;p&gt;Subtitles are neither required nor sufficient to fulfil WCAG.&lt;/p&gt;
&lt;figure&gt;
  &lt;video class=&quot;block mx-auto&quot; width=&quot;715&quot; height=&quot;406&quot; poster=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-poster.jpg&quot; preload=&quot;none&quot; controls=&quot;&quot;&gt;
    &lt;source src=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-example.mp4&quot; type=&quot;video/mp4&quot;&gt;
    &lt;track src=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-track-subtitles.vtt&quot; kind=&quot;subtitles&quot; srclang=&quot;en&quot; label=&quot;English subtitles&quot; default=&quot;&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;pre class=&quot;language-vtt&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-vtt&quot;&gt;&lt;span class=&quot;token number&quot;&gt;00:00:04.300&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;00:00:07.000&lt;/span&gt;
Hello?&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;closed-captions&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/#closed-captions&quot;&gt;
      Closed captions&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Closed captions (CC) are &lt;strong&gt;text tracks&lt;/strong&gt; transcribing the information within an &lt;strong&gt;audio&lt;/strong&gt; track, including dialogs, music and sound effects. They are necessary for viewers who can&#39;t perceive the audio to understand what is happening, e.g. deaf or hard of hearing people.&lt;/p&gt;
&lt;p&gt;Closed captions are required for videos to fulfil WCAG Level A (&lt;a href=&quot;https://www.w3.org/WAI/WCAG22/Understanding/captions-prerecorded.html&quot;&gt;SC 1.2.2&lt;/a&gt;).&lt;/p&gt;
&lt;figure&gt;
  &lt;video class=&quot;block mx-auto&quot; width=&quot;715&quot; height=&quot;406&quot; poster=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-poster.jpg&quot; preload=&quot;none&quot; controls=&quot;&quot;&gt;
    &lt;source src=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-example.mp4&quot; type=&quot;video/mp4&quot;&gt;
    &lt;track src=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-track-cc.vtt&quot; kind=&quot;captions&quot; srclang=&quot;en&quot; label=&quot;English closed captions&quot; default=&quot;&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;pre class=&quot;language-vtt&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-vtt&quot;&gt;&lt;span class=&quot;token number&quot;&gt;00:00:00.200&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;00:00:04.000&lt;/span&gt;
Suspenseful music playing. Door creaking.

&lt;span class=&quot;token number&quot;&gt;00:00:04.300&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;00:00:07.000&lt;/span&gt;
Female voice: Hello?&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;audio-descriptions&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/#audio-descriptions&quot;&gt;
      Audio descriptions&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Audio descriptions are &lt;strong&gt;audio tracks&lt;/strong&gt; describing visual elements of the &lt;strong&gt;video&lt;/strong&gt; content, including setting, costumes, gestured or facial expressions. They help viewers who can&#39;t perceive the video to understand what is occurring on screen, e.g. blind or low-vision people.&lt;/p&gt;
&lt;p&gt;Audio descriptions are required for videos to fulfil WCAG Level AA (&lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/audio-description-prerecorded.html&quot;&gt;SC 1.2.5&lt;/a&gt;).&lt;/p&gt;
&lt;figure&gt;
  &lt;video class=&quot;block mx-auto&quot; width=&quot;715&quot; height=&quot;406&quot; poster=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-poster.jpg&quot; preload=&quot;none&quot; controls=&quot;&quot;&gt;
    &lt;source src=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/video-audio-description.mp4&quot; type=&quot;video/mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;
&lt;pre class=&quot;language-vtt&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-vtt&quot;&gt;&lt;span class=&quot;token number&quot;&gt;00:00:00.000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;00:00:01.500&lt;/span&gt;
An old door opens.

&lt;span class=&quot;token number&quot;&gt;00:00:02.000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;00:00:04.000&lt;/span&gt;
A decayed room in an abandoned building.

&lt;span class=&quot;token number&quot;&gt;00:00:06.000&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;--&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;00:00:08.000&lt;/span&gt;
Sunlight shines through a window.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;transcripts&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/#transcripts&quot;&gt;
      Transcripts&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Transcripts (transcriptions) are &lt;strong&gt;text&lt;/strong&gt; versions of the &lt;strong&gt;audio&lt;/strong&gt; &lt;em&gt;and&lt;/em&gt; &lt;strong&gt;video&lt;/strong&gt; content. Descriptive transcripts are needed by people who are deaf-blind and others.&lt;/p&gt;
&lt;p&gt;Transcripts are required for videos to fulfil WCAG Level AAA &lt;a href=&quot;https://www.w3.org/WAI/WCAG21/Understanding/media-alternative-prerecorded.html&quot;&gt;SC 1.2.8&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Suspenseful music is playing.
An old door creaks while it opens.
A decayed room in an abandoned building appears.
Sunlight shines through a window.

Female voice: Hello?&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/video-subtitles-closed-captions-audio-descriptions-transcripts/#summary&quot;&gt;
      Summary&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Subtitles&lt;/strong&gt; describe &lt;em&gt;dialogs&lt;/em&gt; via &lt;em&gt;text&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Closed captions&lt;/strong&gt; describe the &lt;em&gt;audio&lt;/em&gt; track via &lt;em&gt;text&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio descriptions&lt;/strong&gt; describe the &lt;em&gt;video&lt;/em&gt; track via &lt;em&gt;audio&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transcripts&lt;/strong&gt; describe the &lt;em&gt;video&lt;/em&gt; and the &lt;em&gt;audio&lt;/em&gt; tracks via &lt;em&gt;text&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For pre-recorded videos containing an audio track, closed captions &lt;em&gt;and&lt;/em&gt; audio descriptions are required to fulfil WCAG Level AA.&lt;/p&gt;
</content>
      <category term="accessibility" />
    </entry>
    
    <entry>
      <title>Building a photography website</title>
      <summary>The setup for sharing my photo gallery.</summary>
      <link href="https://darekkay.com/blog/photography-website/"/>
      <published>2023-10-25T20:49:41Z</published>
      <updated>2024-01-05T17:28:47Z</updated>
      <id>https://darekkay.com/blog/photography-website/</id>
      <media:thumbnail url="https://darekkay.com/blog/photography-website/cover.png" />
      <content type="html">&lt;p&gt;Last year, I started a photography hobby. Soon after, I&#39;ve created a &lt;a href=&quot;https://photos.darekkay.com/&quot;&gt;place where I can share some of my work&lt;/a&gt;, without any attention-driven algorithms dictating the terms. Here&#39;s a technical write-up of my journey.&lt;/p&gt;
&lt;nav class=&quot;article-section&quot;&gt;&lt;h2&gt;Table of contents&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#motivation&quot;&gt;Motivation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#inspiration&quot;&gt;Inspiration&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#design&quot;&gt;Design&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#implementation&quot;&gt;Implementation&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#content-management&quot;&gt;Content management&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#loading-performance&quot;&gt;Loading performance&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#navigation&quot;&gt;Navigation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#rss&quot;&gt;RSS&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#accessibility&quot;&gt;Accessibility&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#pipeline&quot;&gt;Pipeline&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#preparation&quot;&gt;Preparation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#metadata-update&quot;&gt;Metadata update&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#content-update&quot;&gt;Content update&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#further-steps&quot;&gt;Further steps&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/nav&gt;&lt;h2 id=&quot;motivation&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#motivation&quot;&gt;
      Motivation&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Why should one build a photography website in the first place?&lt;/p&gt;
&lt;p&gt;Similar to this blog, I want to reduce my dependency on social media and third-party services. Even if I ignored Instagram&#39;s privacy issues and dark patterns, the fact they can delete my account at a whim is unacceptable. In fact, they shadow-banned my first Instagram account because of &amp;quot;suspicious activity&amp;quot;. &lt;a href=&quot;https://pixelfed.org/&quot;&gt;Pixelfed&lt;/a&gt;, a federated photo sharing platform, solves many issues of Big Tech. However, I can&#39;t guarantee the instance I chose to stand the test of time. &lt;a href=&quot;https://glass.photo/&quot;&gt;Glass&lt;/a&gt; has potential, but there are some issues like no way to provide image alt descriptions.&lt;/p&gt;
&lt;p&gt;Instead of relying on third-party providers alone, I practice &lt;a href=&quot;https://indieweb.org/POSSE&quot;&gt;POSSE&lt;/a&gt;: I publish photos on my own site and syndicate them to my &lt;a href=&quot;https://pixelfed.social/i/web/profile/425185433823763122&quot;&gt;Pixelfed&lt;/a&gt;, &lt;a href=&quot;https://glass.photo/darekkay&quot;&gt;Glass&lt;/a&gt; and &lt;a href=&quot;https://www.instagram.com/darek.kay/&quot;&gt;Instagram&lt;/a&gt; accounts. This approach brings some further advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anyone can &lt;em&gt;access&lt;/em&gt; my photos, without the need for an account.&lt;/li&gt;
&lt;li&gt;People can &lt;em&gt;subscribe&lt;/em&gt; via RSS to view my latest photos, without an algorithm-driven feed.&lt;/li&gt;
&lt;li&gt;I can use a custom, personalized &lt;em&gt;design&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;I can &lt;em&gt;update&lt;/em&gt; my photos, e.g. make changes after getting more experienced with RAW development and photo editing.&lt;/li&gt;
&lt;li&gt;I can use any &lt;em&gt;aspect ratio&lt;/em&gt;. Instagram will often crop the image preview despite choosing &amp;quot;original size&amp;quot; on upload.&lt;/li&gt;
&lt;li&gt;I own my content.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;inspiration&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#inspiration&quot;&gt;
      Inspiration&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Most photography websites are from professional artists who provide a showcase for potential clients. I was looking for a more personal look and feel. Fortunately, some fellow software developers share a photography hobby: &lt;a href=&quot;https://nicolas-hoizey.photo/&quot;&gt;Nicolas Hoizey&lt;/a&gt; (my favorite site!), &lt;a href=&quot;https://moritzpetersen.de/index.html&quot;&gt;Moritz Petersen&lt;/a&gt;, &lt;a href=&quot;https://morning.photos/&quot;&gt;Artem Sapegin&lt;/a&gt;, &lt;a href=&quot;https://florian.photo/&quot;&gt;Florian Ziegler&lt;/a&gt;, &lt;a href=&quot;https://photography.darthmall.net/&quot;&gt;Evan Sheehan&lt;/a&gt;, &lt;a href=&quot;https://gregmorris.co.uk/&quot;&gt;Greg Morris&lt;/a&gt;, &lt;a href=&quot;https://photos.alanwsmith.com/&quot;&gt;Alan W. Smith&lt;/a&gt;, &lt;a href=&quot;https://where.hiddedevries.nl/&quot;&gt;Hidde de Vries&lt;/a&gt; (&lt;a href=&quot;https://hidde.blog/photo-blogging-with-sanity-and-eleventy/&quot;&gt;write-up&lt;/a&gt;), &lt;a href=&quot;https://shom.bandopadhaya.com/photos/&quot;&gt;Shom Bandopadhaya&lt;/a&gt;, &lt;a href=&quot;https://coma.photography/&quot;&gt;Jens Comiotto-Mayer&lt;/a&gt;, &lt;a href=&quot;https://www.matthewhowell.net/photos&quot;&gt;Matthew Howell&lt;/a&gt;, &lt;a href=&quot;https://paulstamatiou.com/photos/&quot;&gt;Paul Stamatiou&lt;/a&gt;, &lt;a href=&quot;https://jesperreiche.com/category/photography/&quot;&gt;Jesper Reiche&lt;/a&gt;, &lt;a href=&quot;https://jamiedumont.com/&quot;&gt;Jamie Dumont&lt;/a&gt;, &lt;a href=&quot;https://www.chuq.me/photography&quot;&gt;Chuq von Rospach&lt;/a&gt; and &lt;a href=&quot;https://matze.rocks/images/&quot;&gt;Matze&lt;/a&gt;. All those websites helped me narrow down the features I want for my own portfolio website.&lt;/p&gt;
&lt;h2 id=&quot;design&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#design&quot;&gt;
      Design&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;As mentioned, I don&#39;t want to convey a business feeling. I want a personal website that &lt;a href=&quot;https://whimsical.club/&quot;&gt;sparks joy&lt;/a&gt;. I&#39;ve decided to go for a pinboard design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The background mimics a corkboard.&lt;/li&gt;
&lt;li&gt;The photos get a big white border, simulating printed Polaroid photos.&lt;/li&gt;
&lt;li&gt;There are three variants of a sticky tape effect.&lt;/li&gt;
&lt;li&gt;The website uses a handwriting font (&amp;quot;Itim&amp;quot;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Previously, I have rotated the photos randomly on every build for some chaos. I loved the effect, but the rotation required the pictures to be interpolated, making them slightly blurry.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://darekkay.com/blog/photography-website/screenshot.avif, https://darekkay.com/blog/photography-website/screenshot-2x.avif 2x&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://darekkay.com/blog/photography-website/screenshot.webp, https://darekkay.com/blog/photography-website/screenshot-2x.webp 2x&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/photography-website/screenshot.jpg&quot; srcset=&quot;https://darekkay.com/blog/photography-website/screenshot.jpg, https://darekkay.com/blog/photography-website/screenshot-2x.jpg 2x&quot; alt=&quot;Website screenshot of the home page gallery&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;455&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m sure this design will change over time — check &lt;a href=&quot;https://photos.darekkay.com/&quot;&gt;photos.darekkay.com&lt;/a&gt; for the current state.&lt;/p&gt;
&lt;h2 id=&quot;implementation&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#implementation&quot;&gt;
      Implementation&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Let&#39;s get a glimpse into the technical side.&lt;/p&gt;
&lt;h3 id=&quot;content-management&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#content-management&quot;&gt;
      Content management&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href=&quot;https://darekkay.com/blog/hexo-to-eleventy/&quot;&gt;Similar to this blog&lt;/a&gt;, I went with the &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; static site generator.&lt;/p&gt;
&lt;p&gt;I&#39;ve decided to index my photos, starting at &lt;code&gt;0001&lt;/code&gt;. For each photograph that I publish, there are five files:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 content
└─ 📁 photo
   └─ 📁 0001
      ├─ 📄 0001.11tydata.json
      ├─ 📄 0001.md
      ├─ 🖼️ 0001-medium.jpg
      └─ 🖼️ 0001-small.jpg
      └─ 🖼️ 0001-small.webp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;11ydata.json&lt;/code&gt; file contains the photo metadata. The markdown file contains the actual content: title, alt description, location, publish date and a short text. The &lt;code&gt;small.webp&lt;/code&gt; and &lt;code&gt;medium.jpg&lt;/code&gt; images are used for the gallery and the preview page respectively. The &lt;code&gt;small.jpg&lt;/code&gt; file is used as social image preview card, as &lt;a href=&quot;https://www.ctrl.blog/entry/webp-ogp.html&quot;&gt;WebP support is still lacking&lt;/a&gt;.&lt;/p&gt;
&lt;dk-alert-box type=&quot;info&quot;&gt;
In the beginning, I was using a single &lt;code&gt;photos.json&lt;/code&gt; to store both the metadata and content. Using &lt;a href=&quot;https://www.11ty.dev/docs/pagination/&quot;&gt;Eleventy pagination&lt;/a&gt;, I didn&#39;t have to create dedicated markdown files. As I have started adding short descriptions to my photos, this workflow was no longer viable. But it&#39;s still a good alternative for basic photo galleries. Make sure to also check &lt;a href=&quot;https://darthmall.net/weblog/2024/11ty-photo-gallery/&quot;&gt;Evan&#39;s approach&lt;/a&gt; to use the image files as Eleventy templates.
&lt;/dk-alert-box&gt;
&lt;p&gt;I store the &lt;code&gt;medium&lt;/code&gt; and &lt;code&gt;small&lt;/code&gt; images as part of the project&#39;s Git directory. Git is not the best choice for storing binary files, but it doesn&#39;t cause any bottleneck, yet. Each photo triplet is on average 350 kB big. I could use Git LFS, but it&#39;s not worth the effort for now.&lt;/p&gt;
&lt;h3 id=&quot;loading-performance&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#loading-performance&quot;&gt;
      Loading performance&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I&#39;ve put much thought into the loading behavior to ensure a good user experience even on slow networks. I also wanted to avoid &lt;em&gt;pagination&lt;/em&gt; and &lt;em&gt;infinite scroll&lt;/em&gt; for the image gallery.&lt;/p&gt;
&lt;p&gt;First, every image provides its width and height to &lt;strong&gt;prevent layout shifts&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Second, I use &lt;strong&gt;lazy loading&lt;/strong&gt;, a performance strategy to load resources (like images) only when needed. Fortunately, &lt;a href=&quot;https://caniuse.com/loading-lazy-attr&quot;&gt;most browsers&lt;/a&gt; support &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading&quot;&gt;native lazy loading&lt;/a&gt; for images:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;img&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;lazy&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;[...]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Third, the gallery serves &lt;strong&gt;WebP&lt;/strong&gt; images at 75% quality. This saves around 50% of space compared to the original 85% JPEG file. I accept the quality loss for the gallery, but I still use full quality JPEG files on the individual photo pages.&lt;/p&gt;
&lt;p&gt;The last technique is to provide &lt;strong&gt;a good fallback&lt;/strong&gt; while images are still being loaded. I provide two fallbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A fixed background color.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blurha.sh/&quot;&gt;BlurHash&lt;/a&gt;, a compact representation of an image placeholder (also called &amp;quot;low-quality image placeholder&amp;quot;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the JavaScript BlurHash script (1.6 kB) is loaded, we see a fixed background color:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/photography-website/loading-background.jpg&quot; srcset=&quot;https://darekkay.com/blog/photography-website/loading-background.jpg, https://darekkay.com/blog/photography-website/loading-background-2x.jpg 2x&quot; alt=&quot;Loading visualization 1: beige rectangle&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;330&quot; height=&quot;224&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;After the script has been loaded, the BlurHash placeholder is applied:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/photography-website/loading-blurhash.jpg&quot; srcset=&quot;https://darekkay.com/blog/photography-website/loading-blurhash.jpg, https://darekkay.com/blog/photography-website/loading-blurhash-2x.jpg 2x&quot; alt=&quot;Loading visualization 2: blurred picture&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;330&quot; height=&quot;224&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Finally, we see the actual image:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/photography-website/loading-done.jpg&quot; srcset=&quot;https://darekkay.com/blog/photography-website/loading-done.jpg, https://darekkay.com/blog/photography-website/loading-done-2x.jpg 2x&quot; alt=&quot;Loading visualization 3: example picture&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;329&quot; height=&quot;222&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.kryogenix.org/code/browser/everyonehasjs.html&quot;&gt;Not everyone has JavaScript&lt;/a&gt;, despite many developers ignoring this fact. The nice thing about BlurHash is that it&#39;s progressively enhanced: without JavaScript, only the fixed-color fallback will be displayed.&lt;/p&gt;
&lt;dk-alert-box type=&quot;info&quot;&gt;
If you like BlurHash, check out &lt;a href=&quot;https://evanw.github.io/thumbhash/&quot;&gt;ThumbHash&lt;/a&gt;, which returns more detailed placeholders.
&lt;/dk-alert-box&gt;
&lt;p&gt;How do we test the loading behavior? Browser network simulations are useful, but not so much for local images, as they will still load almost instantly. Instead, I&#39;ve created custom &lt;a href=&quot;https://darekkay.com/blog/eleventy-delay-middleware/&quot;&gt;Eleventy middleware&lt;/a&gt; to delay image loading artificially during testing.&lt;/p&gt;
&lt;h3 id=&quot;navigation&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#navigation&quot;&gt;
      Navigation&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;On each page, there is a link to the &amp;quot;previous&amp;quot; and &amp;quot;next&amp;quot; photo. I&#39;ve implemented this using a &lt;a href=&quot;https://github.com/11ty/eleventy/issues/529#issuecomment-568257426&quot;&gt;custom Eleventy collection&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;photos&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; photos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;content/photo/**&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;collection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; photos&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prevPost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; photos&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; photos&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;photos&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextPost &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; photos&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; photos&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    photos&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;previousPost&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; prevPost&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    photos&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;nextPost&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; nextPost&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; photos&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I can then access the page URLs in my layout file:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{{ previousPost.url }}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Previous photo&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;{{ nextPost.url }}&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Next photo&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;rss&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#rss&quot;&gt;
      RSS&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I consider RSS a must for any blog-like website. A photo gallery is no different. Here&#39;s &lt;a href=&quot;https://photos.darekkay.com/atom.xml&quot;&gt;my RSS feed&lt;/a&gt;. The RSS feed contains the entire post content and the small image preview. I&#39;ve also &lt;a href=&quot;https://darekkay.com/blog/rss-styling/&quot;&gt;styled the RSS feed&lt;/a&gt;, so it matches the website design.&lt;/p&gt;
&lt;dk-alert-box type=&quot;info&quot;&gt;
Pixelfed, Glass, Flickr and Pinterest also provide RSS feeds.
&lt;/dk-alert-box&gt;
&lt;h3 id=&quot;accessibility&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#accessibility&quot;&gt;
      Accessibility&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;I care a lot about web accessibility. I&#39;ve tried my best to make sure anyone can use my website, including people with impairments or disabilities.&lt;/p&gt;
&lt;p&gt;An important part is to provide descriptions for every image via an &lt;code&gt;alt&lt;/code&gt; attribute. As long as there is no AI to translate a picture and its essence into words (and I don&#39;t think we&#39;ll get there anytime soon), artists have to handle this themselves. I try to describe what a photo contains, but also the feeling it conveys. This has a great side effect, as it lets me think more about my photos. I must admit, I struggle with this as much as I do with photo titles, but I think this will become easier with more experience.&lt;/p&gt;
&lt;p&gt;Apart from that, I&#39;ve followed the usual path:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use my experience to ensure accessible implementation.&lt;/li&gt;
&lt;li&gt;Check the website with a keyboard and with a screen reader.&lt;/li&gt;
&lt;li&gt;Run &lt;a href=&quot;https://darekkay.com/evaluatory/&quot;&gt;Evaluatory&lt;/a&gt; to check if I&#39;ve made any obvious mistake.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;pipeline&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#pipeline&quot;&gt;
      Pipeline&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Here&#39;s my workflow pipeline for publishing a new photo, using &lt;code&gt;05.jpg&lt;/code&gt; as an example file:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/photography-website/photo-pipeline.png&quot; srcset=&quot;https://darekkay.com/blog/photography-website/photo-pipeline.png, https://darekkay.com/blog/photography-website/photo-pipeline-2x.png 2x&quot; alt=&quot;Diagram. &#39;05.jpg&#39; points to &#39;strip most Exif data&#39; points to &#39;resize and convert&#39; points to &#39;05-small.webp&#39;, &#39;05-small.jpg&#39; and &#39;05-medium.jpg&#39;. &#39;05-medium.jpg&#39; points to &#39;extract Exif data&#39; and &#39;calculate&#39; blurhash, both pointing to &#39;05.11tydata.json&#39;. &#39;add content&#39; points to &#39;05.md&#39;, which is connected to &#39;05.11tydata.json&#39;.&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;721&quot; height=&quot;341&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Most of those steps are automated.&lt;/p&gt;
&lt;h3 id=&quot;preparation&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#preparation&quot;&gt;
      Preparation&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;In the first step, I strip irrelevant photo metadata using &lt;a href=&quot;https://exiftool.org/&quot;&gt;ExifTool&lt;/a&gt;. I leave all the data that other photographers might be interested in, e.g. aperture, exposure and ISO:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;exiftool &lt;span class=&quot;token parameter variable&quot;&gt;-all&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-tagsfromfile&lt;/span&gt; @ &lt;span class=&quot;token parameter variable&quot;&gt;-AllDates&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Make&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Model&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-LensModel&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Artist&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-FNumber&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ISO&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ExposureTime&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ExposureProgram&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-ExposureMode&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token parameter variable&quot;&gt;-ExposureCompensation&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-FocalLength&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-WhiteBalance&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Flash&lt;/span&gt; 05.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, I use &lt;a href=&quot;https://imagemagick.org/index.php&quot;&gt;ImageMagick&lt;/a&gt; to create two files: a small thumbnail and a medium-size photo:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;magick convert &lt;span class=&quot;token parameter variable&quot;&gt;-resize&lt;/span&gt; x100&lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;0&lt;/span&gt;&gt;&lt;/span&gt; 05.jpg 05-medium.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;magick convert &lt;span class=&quot;token parameter variable&quot;&gt;-resize&lt;/span&gt; x37&lt;span class=&quot;token operator&quot;&gt;&lt;span class=&quot;token file-descriptor important&quot;&gt;5&lt;/span&gt;&gt;&lt;/span&gt; 05.jpg 05-small.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The entire pre-processing takes one click using &lt;a href=&quot;https://www.xyplorer.com/&quot;&gt;XYplorer&lt;/a&gt;, my indispensable Windows file manager.&lt;/p&gt;
&lt;h3 id=&quot;metadata-update&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#metadata-update&quot;&gt;
      Metadata update&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;The next workflow step creates an &lt;code&gt;11tydata.json&lt;/code&gt; file, which contains relevant Exif data and the blurhash.&lt;/p&gt;
&lt;p&gt;I use &lt;code&gt;exiftool&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; to create a temporary &lt;code&gt;exif.json&lt;/code&gt; file containing the Exif metadata from all photos:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;exiftool &lt;span class=&quot;token parameter variable&quot;&gt;-ext&lt;/span&gt; jpg &lt;span class=&quot;token parameter variable&quot;&gt;-json&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-FileName&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-all&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; %Y-%m-%d content/photo &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token string&quot;&gt;&#39;map(select(.FileName | contains (&quot;medium&quot;))) | map(.+{&quot;id&quot;: .FileName[0:4]}) | map(del(.SourceFile,.FileName))&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; temp/exif.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To calculate the blurhash, I use &lt;code&gt;blurhash&lt;/code&gt; and &lt;code&gt;sharp&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sharp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;sharp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; encode &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;blurhash&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;encodeImageToBlurhash&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;sharp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;raw&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ensureAlpha&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;fit&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;inside&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; buffer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Uint8ClampedArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; width&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; height&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both results are then normalized and piped into an &lt;code&gt;11tydata.json&lt;/code&gt; file.&lt;/p&gt;
&lt;dk-alert-box type=&quot;info&quot;&gt;
With Eleventy 2.0, it&#39;s possible to &lt;a href=&quot;https://www.11ty.dev/docs/data-custom/#feed-exif-image-data-into-the-data-cascade&quot;&gt;feed Exif image data into the data cascade&lt;/a&gt;. This approach eliminates the metadata synchronization step at the cost of having to parse all photos on every build.
&lt;/dk-alert-box&gt;
&lt;h3 id=&quot;content-update&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#content-update&quot;&gt;
      Content update&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Last but not least, I need to handle the content. A script creates a template markdown file that I then edit manually. While I don&#39;t always come up with an image title or description, I will always provide an alternative text as explained in the accessibility section. I include the photo location only if it&#39;s relevant.&lt;/p&gt;
&lt;h2 id=&quot;further-steps&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/photography-website/#further-steps&quot;&gt;
      Further steps&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I&#39;m happy to have a place to share my photos that I have full control over. It&#39;s also a nice way to see my progress as a photographer. While there are photography areas that I like more than others, I probably won&#39;t settle on a certain niche. I might introduce browsable categories someday &lt;em&gt;if&lt;/em&gt; the number of photos becomes too overwhelming. I might also introduce filtering photos by camera or lens.&lt;/p&gt;
&lt;p&gt;Check out my photos at &lt;a href=&quot;https://photos.darekkay.com/&quot;&gt;photos.darekkay.com&lt;/a&gt;.&lt;/p&gt;
</content>
      <category term="11ty" /><category term="photography" /><category term="project" />
    </entry>
    
    <entry>
      <title>Delaying asset requests in Eleventy</title>
      <summary>Simulate slow connections on your Eleventy website using dev server middleware.</summary>
      <link href="https://darekkay.com/blog/eleventy-delay-middleware/"/>
      <published>2023-09-29T16:33:48Z</published>
      <updated>2023-09-29T16:33:48Z</updated>
      <id>https://darekkay.com/blog/eleventy-delay-middleware/</id>
      <media:thumbnail url="https://darekkay.com/blog/eleventy-delay-middleware/cover.png" />
      <content type="html">&lt;p&gt;While building my &lt;a href=&quot;https://photos.darekkay.com/&quot;&gt;photography portfolio&lt;/a&gt;, I&#39;ve put much effort into optimizing the picture loading behavior. One technique is to provide a visual fallback as long as the images are still loading. First, a static background color included in the markup is displayed. As soon as a tiny &lt;a href=&quot;https://github.com/woltapp/blurhash&quot;&gt;script&lt;/a&gt; has been loaded, the color placeholder is replaced with a blurred picture representation. Finally, the actual image is shown.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/eleventy-delay-middleware/image-loading.jpg&quot; alt=&quot;Three website screenshots. The first contains elements with a brown background. The second contains blurred images. The third contains fully rendered images.&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;692&quot; height=&quot;122&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve noticed it was difficult to test my implementation. On a fast network, the fallback is only visible for a split of a second. The slow network simulation in the browser developer tools doesn&#39;t help much:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The JS script is small enough, so I still can&#39;t see the fixed color background.&lt;/li&gt;
&lt;li&gt;The HTML page itself is delayed as well, which slows down the testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My first solution was to write a small Express server, routing my assets and artificially delaying responses. Only later I&#39;ve learned that Eleventy 2.0 supports request middleware out of the box, so I&#39;ve migrated my solution. Notice that Eleventy middleware is not yet documented — a &lt;a href=&quot;https://github.com/11ty/eleventy-dev-server/issues/13&quot;&gt;GitHub issue&lt;/a&gt; was my main source of information.&lt;/p&gt;
&lt;p&gt;The middleware API is similar to Express. In the following example, each &lt;em&gt;asset&lt;/em&gt; request is delayed by one to five seconds:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;delayMiddleware&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// no delay necessary for HTML pages&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; timeoutInSeconds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;floor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeoutInSeconds &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use custom middleware, we have to include it in the Eleventy configuration file:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setServerOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;delayMiddleware&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;ve extracted the delay middleware into a &lt;a href=&quot;https://github.com/darekkay/darekkay-11ty/blob/master/lib/utils/middleware/delay.js&quot;&gt;reusable utility&lt;/a&gt; that I can use in all my Eleventy projects. In the following example, I&#39;m delaying only JPG files by one to three seconds:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; delayMiddleware &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@darekkay/11ty/lib/utils/middleware/delay&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setServerOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;middleware&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delayMiddleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;maxDelayInSeconds&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;applyFilter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;endsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
</content>
      <category term="11ty" /><category term="debugging" />
    </entry>
    
    <entry>
      <title>My personal one-pager</title>
      <summary>A short write-up about creating my portfolio website.</summary>
      <link href="https://darekkay.com/blog/personal-one-pager/"/>
      <published>2023-07-03T19:46:39Z</published>
      <updated>2023-07-03T19:46:39Z</updated>
      <id>https://darekkay.com/blog/personal-one-pager/</id>
      <media:thumbnail url="https://darekkay.com/blog/personal-one-pager/cover.png" />
      <content type="html">&lt;p&gt;I&#39;ve been using &lt;em&gt;&amp;quot;Darek Kay&amp;quot;&lt;/em&gt; as my pseudonym since school. My surname was long and difficult to pronounce, so I&#39;ve been mostly using it in a formal context. Last year, I changed my legal name, which also influenced my online presence. Due to the change, looking up my new name via search engines returned mostly irrelevant results. To make things worse, the top search results included pictures of a wanted fugitive (not kidding). I&#39;ve decided to build a personal one-pager to prevent identity mistakes. Fortunately, &lt;code&gt;firstnamelastname.com&lt;/code&gt; was still available, so here it is: &lt;a href=&quot;https://dariuszwinkler.com/&quot;&gt;dariuszwinkler.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://darekkay.com/blog/personal-one-pager/dariusz-winkler.avif, https://darekkay.com/blog/personal-one-pager/dariusz-winkler-2x.avif 2x&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/personal-one-pager/dariusz-winkler.jpg&quot; srcset=&quot;https://darekkay.com/blog/personal-one-pager/dariusz-winkler.jpg, https://darekkay.com/blog/personal-one-pager/dariusz-winkler-2x.jpg 2x&quot; alt=&quot;Screenshot from dariuszwinkler.com&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;530&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I like reading write-ups about building personal websites, so here&#39;s mine.&lt;/p&gt;
&lt;h2 id=&quot;content&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/personal-one-pager/#content&quot;&gt;
      Content&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I consider my new website an entry point for anyone who looks for a brief overview about who I am. The content is short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My legal name&lt;/li&gt;
&lt;li&gt;My pseudonym&lt;/li&gt;
&lt;li&gt;My main professions / hobbies with links&lt;/li&gt;
&lt;li&gt;A handful of social media links&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s strange to reduce myself to a few keywords, but the external links provide more information.&lt;/p&gt;
&lt;h2 id=&quot;tech-stack&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/personal-one-pager/#tech-stack&quot;&gt;
      Tech stack&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The website is made with pure HTML and CSS, without JavaScript. I&#39;m not a purist and I love all my over-engineered projects, but there is no need for anything special for a single web page that I&#39;m not planning to update frequently.&lt;/p&gt;
&lt;p&gt;I did opt-in for a custom web font, though: &amp;quot;Patrick Hand SC&amp;quot;. It fits the playful design.&lt;/p&gt;
&lt;h2 id=&quot;bubbles&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/personal-one-pager/#bubbles&quot;&gt;
      Bubbles&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The overall design is inspired by &lt;a href=&quot;https://ohhelloana.blog/&quot;&gt;the blog from Ana Rodrigues&lt;/a&gt;. I love the &lt;a href=&quot;https://whimsical.club/&quot;&gt;whimsical&lt;/a&gt;, hand-drawn look and feel:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://darekkay.com/blog/personal-one-pager/ana-rodrigues.avif, https://darekkay.com/blog/personal-one-pager/ana-rodrigues-2x.avif 2x&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/personal-one-pager/ana-rodrigues.jpg&quot; srcset=&quot;https://darekkay.com/blog/personal-one-pager/ana-rodrigues.jpg, https://darekkay.com/blog/personal-one-pager/ana-rodrigues-2x.jpg 2x&quot; alt=&quot;Screenshot from ohhelloana.blog&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;460&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I was especially intrigued by the bubbles. To my surprise, the first bubble is done with pure CSS. I&#39;ve &lt;a href=&quot;https://9elements.github.io/fancy-border-radius/&quot;&gt;learned&lt;/a&gt; that &lt;code&gt;border-radius&lt;/code&gt; supports a &lt;code&gt;/&lt;/code&gt; notation:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.bubble&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;border-radius&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 34% 60% 42% 72%/71% 40% 60% 40%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.w3.org/TR/css-backgrounds-3/#border-radius&quot;&gt;specification&lt;/a&gt; explains:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The first value is the horizontal radius, the second (is) the vertical radius. If the second value is omitted, it is copied from the first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://caniuse.com/mdn-css_properties_border-radius_elliptical_borders&quot;&gt;browser support&lt;/a&gt; is excellent, including IE9.&lt;/p&gt;
&lt;p&gt;My next thought was: can I animate the border radius? You bet!&lt;/p&gt;
&lt;dk-embed&gt;
  &lt;noscript&gt;This is an interactive example. Please enable JavaScript to use it.&lt;/noscript&gt;
  &lt;div class=&quot;flex flex-col items-center text-center&quot;&gt;
    &lt;output id=&quot;border-radius-bubble&quot; class=&quot;inline-flex items-center justify-center mb-6 font-mono text-2 font-bold&quot;&gt;40% 60% 28% 72% / 71% 40% 60% 29%&lt;/output&gt;
    &lt;button id=&quot;border-radius-btn-random&quot; class=&quot;btn&quot; type=&quot;button&quot;&gt;Random border radius&lt;/button&gt;
  &lt;/div&gt;
&lt;/dk-embed&gt;
&lt;style&gt;
  #border-radius-bubble {
    width: 100%;
    max-width: 330px;
    height: 170px;
    background-color: #e3c6b2;

    border-radius: 40% 60% 28% 72%/71% 40% 60% 29%;
    transition: border-radius 0.7s ease-out;
  }
&lt;/style&gt;
&lt;script&gt;
  document.querySelector(&quot;#border-radius-btn-random&quot;).addEventListener(&quot;click&quot;, () =&gt; {
    const randomA = Math.floor(Math.random() * 101);
    const randomB = Math.floor(Math.random() * 101);
    const randomC = Math.floor(Math.random() * 101);
    const randomD = Math.floor(Math.random() * 101);

    const borderRadiusString = `${randomA}% ${100 - randomA}% ${randomB}% ${100 - randomB}% / ${randomC}% ${randomD}% ${100 - randomD}% ${100 - randomC}%`;

    document.querySelector(&quot;#border-radius-bubble&quot;).innerText = borderRadiusString;
    document.querySelector(&quot;#border-radius-bubble&quot;).style.borderRadius = borderRadiusString;
  });
&lt;/script&gt;
&lt;p&gt;I use this technique to turn the bubbles into ellipses on hover/focus.&lt;/p&gt;
&lt;h2 id=&quot;background&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/personal-one-pager/#background&quot;&gt;
      Background&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I wanted a subtle yet interesting background. I like the triangle pattern from &lt;a href=&quot;http://davegamache.com/&quot;&gt;Dave Gamache&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/personal-one-pager/dave-gamache.jpg&quot; srcset=&quot;https://darekkay.com/blog/personal-one-pager/dave-gamache.jpg, https://darekkay.com/blog/personal-one-pager/dave-gamache-2x.jpg 2x&quot; alt=&quot;Screenshot from davegamache.com&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;715&quot; height=&quot;461&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I shamelessly copied their idea and drew an SVG pattern:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://darekkay.com/blog/personal-one-pager/triangles.avif&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/personal-one-pager/triangles.png&quot; srcset=&quot;https://darekkay.com/blog/personal-one-pager/triangles.png, https://darekkay.com/blog/personal-one-pager/triangles-2x.png 2x&quot; alt=&quot;A triangle pattern&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;591&quot; height=&quot;296&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;It took me a few tries to get a random yet balanced pattern. My tip is to draw big triangles first and then fill in smaller triangles. I also think isosceles triangles look better than equilateral triangles.&lt;/p&gt;
&lt;h2 id=&quot;seo&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/personal-one-pager/#seo&quot;&gt;
      SEO&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I haven&#39;t done anything special to boost my site, apart from following best practices. After a few months, Google ranks my website third, right after my LinkedIn and Xing profiles. Bing does better, putting my page first. All in all, I have achieved my goal of making my name searchable.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/personal-one-pager/#conclusion&quot;&gt;
      Conclusion&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I&#39;m happy with my little website, and I&#39;ve learned a few new things along the way. However, my digital garden will remain here at &lt;a href=&quot;https://darekkay.com/&quot;&gt;darekkay.com&lt;/a&gt;.&lt;/p&gt;
</content>
      <category term="project" />
    </entry>
    
    <entry>
      <title>Style your RSS feed</title>
      <summary>How to make your RSS feed look nice and provide useful information at the same time.</summary>
      <link href="https://darekkay.com/blog/rss-styling/"/>
      <published>2023-06-20T10:02:58Z</published>
      <updated>2024-01-21T12:46:43Z</updated>
      <id>https://darekkay.com/blog/rss-styling/</id>
      <media:thumbnail url="https://darekkay.com/blog/rss-styling/cover.png" />
      <content type="html">&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/RSS&quot;&gt;RSS&lt;/a&gt; is not dead. It is not &lt;em&gt;mainstream&lt;/em&gt;, but it&#39;s still a thriving protocol, especially among tech users. However, many people do not know what RSS feeds are or how to use them. Most browsers render RSS as raw XML files, which doesn&#39;t help users understand what it&#39;s all about:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/rss-styling/rss-unstyled.webp&quot; srcset=&quot;https://darekkay.com/blog/rss-styling/rss-unstyled.webp, https://darekkay.com/blog/rss-styling/rss-unstyled-2x.webp 2x&quot; alt=&quot;XML example as rendered in Chrome&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;604&quot; height=&quot;253&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;In this post, I&#39;ll explain how to style RSS feeds and educate readers at the same time.&lt;/p&gt;
&lt;h2 id=&quot;xslt-to-the-rescue&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/rss-styling/#xslt-to-the-rescue&quot;&gt;
      XSL(T) to the rescue&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;This is how the &lt;a href=&quot;https://darekkay.com/atom.xml&quot;&gt;RSS feed for this blog&lt;/a&gt; looks like:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/rss-styling/rss-styled.webp&quot; srcset=&quot;https://darekkay.com/blog/rss-styling/rss-styled.webp, https://darekkay.com/blog/rss-styling/rss-styled-2x.webp 2x&quot; alt=&quot;A styled RSS feed preview. It contains an alert box with a short description about RSS feeds and a list of blog post titles and dates.&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;517&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;To style a raw XML file in a browser, you have to provide styling information. You can do that by &lt;a href=&quot;https://www.w3.org/TR/2010/REC-xml-stylesheet-20101028/&quot;&gt;attaching&lt;/a&gt; an &lt;code&gt;xml-stylesheet&lt;/code&gt; processing instruction within the RSS feed:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml-stylesheet href=&quot;/rss.xsl&quot; type=&quot;text/xsl&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;feed&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2005/Atom&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;media&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://search.yahoo.com/mrss/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  ...
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;feed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;href&lt;/code&gt; attribute specifies a URL to a valid &lt;a href=&quot;https://www.w3.org/Style/XSL/WhatIsXSL.html&quot;&gt;XSL&lt;/a&gt; file. You can check out &lt;a href=&quot;https://darekkay.com/assets/xsl/rss-style.xsl&quot;&gt;mine&lt;/a&gt; for inspiration. Here&#39;s the gist:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;stylesheet&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;3.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
                &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;xsl&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/1999/XSL/Transform&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
                &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;atom&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2005/Atom&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;output&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;UTF-8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;yes&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;template&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/1999/xhtml&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        RSS Feed | &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/atom:feed/atom:title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/assets/styles.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        This is an RSS feed. Visit
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://aboutfeeds.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;About Feeds&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        to learn more and get started. It’s free.
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Recent blog posts&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;for-each&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/atom:feed/atom:entry&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;attribute&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;atom:link/@href&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;attribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;atom:title&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;atom:summary&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        Last updated:
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;substring(atom:updated, 0, 11)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;for-each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;stylesheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code is inspired by &lt;a href=&quot;https://github.com/genmon/aboutfeeds/blob/main/tools/pretty-feed-v3.xsl&quot;&gt;pretty-feed-v3&lt;/a&gt;, but I&#39;ve adopted it for the Atom specification.&lt;/p&gt;
&lt;p&gt;The XSL file transforms the XML feed into a valid HTML document that any browser can render. You can use any information included in the XML file and put it into a new markup structure. You can even include content that is not part of the XML feed. A perfect use case is to include a note about what RSS feeds are and how to use them (as shown in the example above).&lt;/p&gt;
&lt;p&gt;You can define inline CSS styles with a regular &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; element, but it&#39;s also possible to import external CSS files. I&#39;ve imported my blog&#39;s main CSS stylesheet, so I didn&#39;t have to write any new code to make the RSS feed match my blog&#39;s design.&lt;/p&gt;
&lt;p&gt;It&#39;s also possible to use &lt;a href=&quot;https://www.w3.org/TR/xpath-functions-30/&quot;&gt;XSLT functions&lt;/a&gt; to alter values. Here&#39;s how I truncate the timestamp to display only the date:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Full timestamp --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;atom:updated&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Date only --&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;substring(atom:updated, 0, 11)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://caniuse.com/?search=xslt&quot;&gt;browser support&lt;/a&gt; for XSL transformations is great; unsupported browsers will fall back to the default behavior (progressive enhancement).&lt;/p&gt;
&lt;h2 id=&quot;examples&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/rss-styling/#examples&quot;&gt;
      Examples&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Here are some styled RSS feeds you can check for inspiration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://darekkay.com/atom.xml&quot;&gt;This blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://photos.darekkay.com/atom.xml&quot;&gt;My photography website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://feeds.bbci.co.uk/news/world/europe/rss.xml&quot;&gt;BBC News&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lepture.com/feed.xml&quot;&gt;Hsiaoming Yang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://daverupert.com/atom.xml&quot;&gt;Dave Rupert&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://thepcspy.com/feeds/full.xml&quot;&gt;Oli Warner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chrismorgan.info/feed.xml&quot;&gt;Chris Morgan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The XSL files are public, so you can check how those pages have been implemented.&lt;/p&gt;
&lt;h2 id=&quot;sitemap&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/rss-styling/#sitemap&quot;&gt;
      Sitemap&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;You can apply XSL files to &lt;em&gt;any&lt;/em&gt; XML file. Another use case for styled XML files is a &lt;a href=&quot;https://www.sitemaps.org/&quot;&gt;website sitemap&lt;/a&gt;. Although sitemaps are meant to be consumed by machines (e.g. crawlers), you can style them with little effort. See &lt;a href=&quot;https://darekkay.com/sitemap.xml&quot;&gt;my sitemap&lt;/a&gt; and the &lt;a href=&quot;https://darekkay.com/assets/xsl/sitemap-style.xsl&quot;&gt;respective XSL file&lt;/a&gt; as an example. Here&#39;s the gist:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;stylesheet&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;3.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;xsl&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/1999/XSL/Transform&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
                &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;sitemap&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.sitemaps.org/schemas/sitemap/0.9&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;output&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;html&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;1.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;encoding&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;UTF-8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;indent&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;yes&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;template&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;xmlns&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/1999/xhtml&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;en&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Sitemap&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;for-each&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/sitemap:urlset/sitemap:url&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;attribute&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
              &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;sitemap:loc&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;attribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
            &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;sitemap:loc&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
          Last updated:
          &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;value-of&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;substring(sitemap:lastmod, 0, 11)&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
        &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;for-each&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;template&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;xsl:&lt;/span&gt;stylesheet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;caveat&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/rss-styling/#caveat&quot;&gt;
      Caveat&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;What happens if a user wants to save a styled XML file? Interestingly, the answer depends on the browser. Chrome and Safari will save the original &lt;em&gt;XML&lt;/em&gt; file, but Firefox will store the styled &lt;em&gt;HTML&lt;/em&gt; page instead. Most RSS readers expect a feed URL, so this behavior doesn&#39;t make a difference. However, I would be careful with XML files that are meant to be downloaded, like OPML files. Kudos to Robb Knight for the &lt;a href=&quot;https://social.lol/@robb/111793259765506845&quot;&gt;observation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/rss-styling/#conclusion&quot;&gt;
      Conclusion&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I love RSS, and — according to &lt;a href=&quot;https://darekkay.com/blog/rss-subscriber-count/&quot;&gt;my stats&lt;/a&gt; — many of my readers love it, too. With all the social media walled gardens, I hope for RSS to become more popular. By providing a custom XSL file, you can make your RSS feed look nice &lt;em&gt;and&lt;/em&gt; include some information about what RSS actually is.&lt;/p&gt;
</content>
      <category term="rss" /><category term="blog" />
    </entry>
    
    <entry>
      <title>Fixing long start-up times of the Eleventy dev server</title>
      <summary>Running Browsersync without internet connection.</summary>
      <link href="https://darekkay.com/blog/eleventy-browsersync-issue/"/>
      <published>2022-12-22T14:53:41Z</published>
      <updated>2022-12-22T14:53:41Z</updated>
      <id>https://darekkay.com/blog/eleventy-browsersync-issue/</id>
      <media:thumbnail url="https://darekkay.com/blog/eleventy-browsersync-issue/cover.png" />
      <content type="html">&lt;p&gt;Recently, I&#39;ve encountered a peculiar issue with Eleventy. The &lt;a href=&quot;https://www.11ty.dev/docs/watch-serve/&quot;&gt;development server&lt;/a&gt; stopped working:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--serve&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-log&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-log&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11ty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Wrote &lt;span class=&quot;token number&quot;&gt;92&lt;/span&gt; files in &lt;span class=&quot;token number&quot;&gt;0.48&lt;/span&gt; seconds &lt;span class=&quot;token operator&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5.2ms&lt;/span&gt; each&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;v1.0.2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;11ty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; Watching…&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There were no errors. Everything seemed fine, except for the dev server not being available. I&#39;ve checked my other Eleventy projects, and all of them shared the same behavior.&lt;/p&gt;
&lt;p&gt;I&#39;ve enabled the Eleventy debug log, but it didn&#39;t provide any further insights:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;cross-env &lt;span class=&quot;token assign-left variable&quot;&gt;DEBUG&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;* eleventy &lt;span class=&quot;token parameter variable&quot;&gt;--serve&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;ve decided to postpone further debugging, but I&#39;ve accidentally left the Eleventy process running. At some point I&#39;ve noticed the dev server was running again. Confused by this behavior, I&#39;ve restarted the server. It turned out that the dev server was actually working, but it required &lt;em&gt;several minutes&lt;/em&gt; to launch. What was going on? I couldn&#39;t research this issue, as I was sitting on a train with no internet connection. Wait, could this be the root cause? Indeed, later at home I no longer experienced the problem. After turning off my wi-fi I could again reproduce it.&lt;/p&gt;
&lt;p&gt;I have found out that this is not an Eleventy issue. It&#39;s an &lt;a href=&quot;https://stackoverflow.com/questions/33306452/browsersync-not-working-on-offline&quot;&gt;issue with Browsersync&lt;/a&gt;, which Eleventy &amp;lt; 2.0 uses under the hood. Fortunately, there is a &lt;a href=&quot;https://browsersync.io/docs/options/#option-online&quot;&gt;config option&lt;/a&gt; to fix the issue:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// .eleventy.js (&amp;lt; 2.0)&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setBrowserSyncConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;online&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This option will disable some network-related features, like exposing the server in the local network. While sometimes useful, I think it&#39;s better to use it as opt-in anyway.&lt;/p&gt;
&lt;p&gt;Eleventy 2.0 comes with a &lt;a href=&quot;https://www.11ty.dev/docs/dev-server/&quot;&gt;custom development server&lt;/a&gt;, which probably doesn&#39;t have this issue. If you&#39;re using &lt;code&gt;@11ty/eleventy-server-browsersync&lt;/code&gt; with Eleventy 2.0, use the new &lt;code&gt;setServerOptions&lt;/code&gt; function instead:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// .eleventy.js (&gt;= 2.0)&lt;/span&gt;
module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setServerOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy-server-browsersync&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;online&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
      <category term="11ty" /><category term="debugging" />
    </entry>
    
    <entry>
      <title>Handling Enzyme in React 18</title>
      <summary>How to make Enzyme unit tests work in a React 18 project.</summary>
      <link href="https://darekkay.com/blog/react-18-enzyme/"/>
      <published>2022-03-26T14:12:07Z</published>
      <updated>2023-06-17T00:00:00Z</updated>
      <id>https://darekkay.com/blog/react-18-enzyme/</id>
      <media:thumbnail url="https://darekkay.com/blog/react-18-enzyme/cover.png" />
      <content type="html">&lt;p&gt;&lt;a href=&quot;https://testing-library.com/&quot;&gt;React Testing Library&lt;/a&gt; (RTL) is arguably superior to &lt;a href=&quot;https://enzymejs.github.io/enzyme/&quot;&gt;Enzyme&lt;/a&gt;, due to a shift from testing implementation details to more &lt;a href=&quot;https://testing-library.com/docs/react-testing-library/migrate-from-enzyme/#why-should-i-use-react-testing-library&quot;&gt;user-centric unit tests&lt;/a&gt;. However, many projects still contain lots of Enzyme tests from the early days, making migration difficult and time-consuming. At my company, we&#39;ve decided to keep Enzyme for old tests and write all new tests using RTL. This worked out fine, until we had to update React. While the &lt;a href=&quot;https://github.com/enzymejs/enzyme/issues/2429&quot;&gt;missing support for React 17&lt;/a&gt; can be &lt;a href=&quot;https://www.npmjs.com/package/@wojtekmaj/enzyme-adapter-react-17&quot;&gt;worked around&lt;/a&gt;, making Enzyme ever work with React 18 is &lt;a href=&quot;https://dev.to/wojtekmaj/enzyme-is-dead-now-what-ekl&quot;&gt;unlikely to happen&lt;/a&gt;.&lt;/p&gt;
&lt;dk-alert-box type=&quot;info&quot;&gt;
&lt;strong&gt;Update:&lt;/strong&gt; An unofficial &lt;a href=&quot;https://www.npmjs.com/package/@cfaester/enzyme-adapter-react-18&quot;&gt;enzyme-adapter-react-18&lt;/a&gt; has been published in the meantime. It shouldn&#39;t be relied on, but it&#39;s another possible workaround while migrating away from Enzyme.
&lt;/dk-alert-box&gt;
&lt;p&gt;Migrating ~700 Enzyme tests at once was not a viable option for our project, so I&#39;ve explored other approaches to handle Enzyme tests with React 18.&lt;/p&gt;
&lt;h2 id=&quot;write-a-custom-adapter&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/react-18-enzyme/#write-a-custom-adapter&quot;&gt;
      Write a custom adapter&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;The first idea was to write a custom &lt;a href=&quot;https://refactoring.guru/design-patterns/adapter&quot;&gt;adapter&lt;/a&gt;: a middle-layer class that translates Enzyme API calls to RTL (not to be confused with &lt;code&gt;enzyme-adapter-react&lt;/code&gt;). However, the fundamental differences between both libraries make such an adapter difficult (or impossible) to implement.&lt;/p&gt;
&lt;p&gt;This is how far I&#39;ve got until abandoning this approach:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; render &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@testing-library/react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;mount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rerender &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;component&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;matcher&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nodes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;matcher&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token literal-property property&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; nodes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// NOTE: innerText is not implemented by jsdom&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; nodes&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token function-variable function&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token function-variable function&quot;&gt;setProps&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updatedComponent &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cloneElement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;component&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; props&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;rerender&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updatedComponent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;run-multiple-react-versions&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/react-18-enzyme/#run-multiple-react-versions&quot;&gt;
      Run multiple React versions&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Finally, I followed the &lt;a href=&quot;https://medium.com/welldone-software/two-ways-to-run-tests-on-different-versions-of-the-same-library-f-e-react-17-react-16-afb7f861d1e9&quot;&gt;guide from Vitali Zaidman&lt;/a&gt; to keep using React 16 for Jest, while running React 18 for the application itself.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;18.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;react-16&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm:react@16.14.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;react-dom&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;18.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;react-dom-16&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;npm:react-dom@16.14.0&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;jest.config.js&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;moduleNameMapper&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;^react-dom((&#92;&#92;/.*)?)$&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react-dom-16$1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;^react((&#92;&#92;/.*)?)$&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react-16$1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is not a real solution, but it allows us to gradually migrate our Enzyme tests to RTL while being able to use React 17+ without any code change.&lt;/p&gt;
&lt;p&gt;Note that this approach won&#39;t work if some of your unit tests rely on the React 18 API, e.g. &lt;code&gt;createRoot&lt;/code&gt;. You could split your unit tests into two Jest configurations, handling Enzyme tests with React 16 and RTL tests with React 18 respectively.&lt;/p&gt;
</content>
      <category term="javascript" /><category term="react" />
    </entry>
    
    <entry>
      <title>Running Storybook from a separate folder</title>
      <summary>How to configure Storybook outside the main app directory.</summary>
      <link href="https://darekkay.com/blog/storybook-separate-folder/"/>
      <published>2021-12-29T16:42:38Z</published>
      <updated>2021-12-29T16:42:38Z</updated>
      <id>https://darekkay.com/blog/storybook-separate-folder/</id>
      <media:thumbnail url="https://darekkay.com/blog/storybook-separate-folder/cover.png" />
      <content type="html">&lt;p&gt;After &lt;a href=&quot;https://darekkay.com/blog/create-react-app-to-vite/&quot;&gt;migrating a project to Vite&lt;/a&gt;, I&#39;ve moved my Storybook setup into a separate module — a folder next to the actual app:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 project
├─ 📁 app
|  ├─ 📁 src
|  └─ 📄 package.json
└─ 📁 storybook
   ├─ 📁 .storybook
   |  ├─ 📄 main.js
   |  └─ 📄 preview.js
   └─ 📄 package.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First, I&#39;ve moved all Storybook dependencies from &lt;code&gt;app/package.json&lt;/code&gt; to &lt;code&gt;storybook/package.json&lt;/code&gt; and called &lt;code&gt;yarn install&lt;/code&gt;. Next, I&#39;ve moved the &lt;code&gt;.storybook&lt;/code&gt; folder and adjusted all relative paths in &lt;code&gt;main.js&lt;/code&gt; and &lt;code&gt;preview.js&lt;/code&gt;, e.g.:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;module.exports = {
&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  stories: [&quot;../src/**/__stories__/*.stories.tsx&quot;],
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  stories: [&quot;../../app/src/**/__stories__/*.stories.tsx&quot;],
&lt;/span&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This setup was working fine locally. However, when trying to run &lt;code&gt;build-storybook&lt;/code&gt; on my server, I&#39;ve got the following error (also reported by &lt;a href=&quot;https://stackoverflow.com/q/68775157&quot;&gt;others&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ModuleParseError: Module parse failed: Unexpected token

You may need an appropriate loader to handle this file type,
  currently no loaders are configured to process this file.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storybook includes a webpack &lt;code&gt;babel-loader&lt;/code&gt; to handle TypeScript and/or JSX files. The issue is with how Storybook &lt;a href=&quot;https://github.com/storybookjs/storybook/blob/next/code/lib/core-common/src/utils/paths.ts&quot;&gt;determines the project root&lt;/a&gt;, which is the path that the &lt;code&gt;babel-loader&lt;/code&gt; &lt;a href=&quot;https://github.com/storybookjs/storybook/blob/next/code/lib/builder-webpack5/src/preview/babel-loader-preview.ts&quot;&gt;includes by default&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;When calculating the project root, Storybook first searches for the closest parent &lt;code&gt;.git&lt;/code&gt; folder. In my local development environment, this path was equal to the common parent of &lt;code&gt;app&lt;/code&gt; and &lt;code&gt;storybook&lt;/code&gt;. But on my server, I separate the Git repository from my working directory. As a result, the project root was incorrect and &lt;code&gt;babel-loader&lt;/code&gt; didn&#39;t include the &lt;code&gt;app&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;My workaround was to fix the &lt;code&gt;include&lt;/code&gt; value of the &lt;code&gt;babel-loader&lt;/code&gt; in the &lt;code&gt;main.js&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;token function-variable function&quot;&gt;webpackFinal&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;config&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; configType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; babelLoaderRule &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rules&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;rule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; rule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;.(mjs|tsx?|jsx?)$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// set correct project root&lt;/span&gt;
    babelLoaderRule&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;include &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;__dirname&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../..&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code first finds the &lt;code&gt;babel-loader&lt;/code&gt; config by searching for the &lt;a href=&quot;https://github.com/storybookjs/storybook/blob/next/code/lib/builder-webpack5/src/preview/babel-loader-preview.ts&quot;&gt;defined&lt;/a&gt; RegExp object (note: in non-TS projects, remove &lt;code&gt;|tsx?&lt;/code&gt; to match the Storybook definition). Then, &lt;code&gt;include&lt;/code&gt; is changed to the correct project root. In my case, I use &lt;code&gt;../..&lt;/code&gt; to get to the grandparent of the &lt;code&gt;.storybook&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href=&quot;https://github.com/darekkay/dashboard/commit/a267d8283ebc025abcc1a0d1ddcc4fcd2e7b54df&quot;&gt;this commit&lt;/a&gt; for the exact changes that I did to extract my Storybook module.&lt;/p&gt;
</content>
      <category term="javascript" /><category term="react" />
    </entry>
    
    <entry>
      <title>Separate Firefox Dark UI theme from website dark mode</title>
      <summary>Use different color theme settings between browser UI and websites.</summary>
      <link href="https://darekkay.com/blog/firefox-dark-mode/"/>
      <published>2021-12-07T11:46:49Z</published>
      <updated>2022-04-20T18:39:40Z</updated>
      <id>https://darekkay.com/blog/firefox-dark-mode/</id>
      <media:thumbnail url="https://darekkay.com/blog/firefox-dark-mode/cover.png" />
      <content type="html">&lt;p&gt;I like the default dark color theme in Firefox, but I prefer to view websites in light mode. This distinction has been working until recently, but with Firefox 95, the browser dark theme will enforce dark mode on websites as well. This sounds like a good default, but I want to separate the browser UI from website contents. Fortunately, there is an &lt;a href=&quot;https://lwn.net/Articles/872016/&quot;&gt;undocumented setting&lt;/a&gt; to change this behavior.&lt;/p&gt;
&lt;dk-alert-box type=&quot;info&quot;&gt;
&lt;strong&gt;Update:&lt;/strong&gt; Firefox 100 (desktop) added a UI option to adjust the dark theme behavior. 🎉
&lt;/dk-alert-box&gt;
&lt;p&gt;Go to &lt;em&gt;&amp;quot;Settings&amp;quot;&lt;/em&gt; → &lt;em&gt;&amp;quot;General&amp;quot;&lt;/em&gt; → &lt;em&gt;&amp;quot;Website appearance&amp;quot;&lt;/em&gt; and choose your preferred setting:&lt;/p&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://darekkay.com/blog/firefox-dark-mode/firefox-dark-theme-ui-setting.avif, https://darekkay.com/blog/firefox-dark-mode/firefox-dark-theme-ui-setting-2x.avif 2x&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://darekkay.com/blog/firefox-dark-mode/firefox-dark-theme-ui-setting.webp, https://darekkay.com/blog/firefox-dark-mode/firefox-dark-theme-ui-setting-2x.webp 2x&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/firefox-dark-mode/firefox-dark-theme-ui-setting.png&quot; srcset=&quot;https://darekkay.com/blog/firefox-dark-mode/firefox-dark-theme-ui-setting.png, https://darekkay.com/blog/firefox-dark-mode/firefox-dark-theme-ui-setting-2x.png 2x&quot; alt=&quot;Firefox settings: website appearance. Four radio button options: &#39;Firefox theme&#39;, &#39;System theme&#39;, &#39;Light&#39;, &#39;Dark&#39;.&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;717&quot; height=&quot;232&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Before Firefox 100 and within Firefox mobile apps, follow the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open &lt;code&gt;about:config&lt;/code&gt; &lt;a href=&quot;https://support.mozilla.org/en-US/kb/about-config-editor-firefox&quot;&gt;configuration page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Edit &lt;code&gt;layout.css.prefers-color-scheme.content-override&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This setting defines how &lt;em&gt;webpages&lt;/em&gt; should be rendered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt;: dark mode&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt;: light mode&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&lt;/code&gt;: system&#39;s color setting&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3&lt;/code&gt;: browser theme color&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By setting the value to &lt;code&gt;1&lt;/code&gt;, I can keep using the &amp;quot;Dark&amp;quot; Firefox UI theme and continue viewing websites in light mode.&lt;/p&gt;
</content>
      <category term="quick-tip" />
    </entry>
    
    <entry>
      <title>Npm vs. Yarn: Dependency resolution</title>
      <summary>Differences between npm and Yarn in resolving dependency versions.</summary>
      <link href="https://darekkay.com/blog/npm-yarn-dependency-resolution/"/>
      <published>2021-10-20T08:46:49Z</published>
      <updated>2021-10-20T08:46:49Z</updated>
      <id>https://darekkay.com/blog/npm-yarn-dependency-resolution/</id>
      <media:thumbnail url="https://darekkay.com/blog/npm-yarn-dependency-resolution/cover.png" />
      <content type="html">&lt;p&gt;Both npm and Yarn support &lt;a href=&quot;https://classic.yarnpkg.com/en/docs/dependency-versions/#toc-version-ranges&quot;&gt;dependency version ranges&lt;/a&gt; (e.g. &lt;code&gt;^4.1.1&lt;/code&gt;). However, there are some differences in how package managers resolve dependencies, which might lead to inconsistencies between different environments.&lt;/p&gt;
&lt;p&gt;In this post I will present the behavior of npm &lt;code&gt;7.15.1&lt;/code&gt; and Yarn &lt;code&gt;1.22.4&lt;/code&gt; / &lt;code&gt;3.0.2&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;changing-version-ranges&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/npm-yarn-dependency-resolution/#changing-version-ranges&quot;&gt;
      Changing version ranges&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I&#39;ve set up two projects with the same &lt;code&gt;package.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.x.x&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The version range &lt;code&gt;4.x.x&lt;/code&gt; will be satisfied by any major &lt;code&gt;4&lt;/code&gt; version, e.g. &lt;code&gt;4.0.0&lt;/code&gt;, &lt;code&gt;4.1.0&lt;/code&gt; or &lt;code&gt;4.1.4&lt;/code&gt;. In a project without any lock file (&lt;code&gt;package-lock.json&lt;/code&gt;, &lt;code&gt;yarn.lock&lt;/code&gt;), &lt;code&gt;npm install&lt;/code&gt; / &lt;code&gt;yarn install&lt;/code&gt; installs the latest &lt;code&gt;kleur&lt;/code&gt; version that satisfies our range (&lt;code&gt;4.1.4&lt;/code&gt; at the time of writing):&lt;/p&gt;
&lt;pre class=&quot;language-json5&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json5&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// package-lock.json&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.1.4&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// yarn.lock&lt;/span&gt;
kleur@&lt;span class=&quot;token number&quot;&gt;4.&lt;/span&gt;x.&lt;span class=&quot;token property unquoted&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  version &lt;span class=&quot;token string&quot;&gt;&quot;4.1.4&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s now downgrade the dependency version to &lt;code&gt;4.0.0&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.0.0&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After another &lt;code&gt;install&lt;/code&gt;, both package managers switch to version &lt;code&gt;4.0.0&lt;/code&gt;. This makes sense, as our installed version &lt;code&gt;4.1.4&lt;/code&gt; does not match &lt;code&gt;4.0.0&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json5&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json5&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// package-lock.json&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.0.0&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// yarn.lock&lt;/span&gt;
kleur@&lt;span class=&quot;token number&quot;&gt;4.0&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  version &lt;span class=&quot;token string&quot;&gt;&quot;4.0.0&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it will get interesting. Let&#39;s switch &lt;em&gt;back&lt;/em&gt; to &lt;code&gt;4.x.x&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.x.x&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s where the package managers handle things differently:&lt;/p&gt;
&lt;pre class=&quot;language-json5&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json5&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// package-lock.json&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.0.0&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// yarn.lock&lt;/span&gt;
kleur@&lt;span class=&quot;token number&quot;&gt;4.&lt;/span&gt;x.&lt;span class=&quot;token property unquoted&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  version &lt;span class=&quot;token string&quot;&gt;&quot;4.1.4&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Npm compares the installed version &lt;code&gt;4.0.0&lt;/code&gt; with the new version range &lt;code&gt;4.x.x&lt;/code&gt; and finds a match. Hence, the installed version remains at &lt;code&gt;4.0.0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On the other hand, Yarn installs &lt;code&gt;4.1.4&lt;/code&gt; again. I assume that Yarn cannot find a definition &lt;code&gt;&amp;quot;kleur@4.x.x&amp;quot;&lt;/code&gt; in &lt;code&gt;yarn.lock&lt;/code&gt;, so it treats &lt;code&gt;kleur&lt;/code&gt; as if it were a new dependency.&lt;/p&gt;
&lt;p&gt;In short, npm matches the actual &lt;em&gt;version&lt;/em&gt;, while Yarn matches the version &lt;em&gt;definition&lt;/em&gt;. I think this makes Yarn a little more consistent: the result is the same as when deleting &lt;code&gt;yarn.lock&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;workspaces&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/npm-yarn-dependency-resolution/#workspaces&quot;&gt;
      Workspaces&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;If you are using &lt;a href=&quot;https://docs.npmjs.com/cli/v7/using-npm/workspaces/&quot;&gt;workspaces&lt;/a&gt;, the resolution differences between &lt;code&gt;npm&lt;/code&gt; and &lt;code&gt;yarn&lt;/code&gt; will cause even more confusion.&lt;/p&gt;
&lt;p&gt;Let&#39;s set up a basic workspace project:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;├─ 📁 workspace-a
|  └─ 📄 package.json
└─ 📄 package.json&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./package.json&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.0.0&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;workspaces&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;workspace-a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./workspace-a/package.json&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-json&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.x.x&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this scenario, we get deviations right after the first &lt;code&gt;install&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm&lt;/code&gt; installs only &lt;code&gt;kleur@4.0.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;yarn&lt;/code&gt; installs both &lt;code&gt;kleur@4.0.0&lt;/code&gt; (main package) and &lt;code&gt;kleur@4.1.4&lt;/code&gt; (workspace)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-json5&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-json5&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// package-lock.json&lt;/span&gt;
&lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;kleur&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.0.0&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// yarn.lock&lt;/span&gt;
kleur@&lt;span class=&quot;token number&quot;&gt;4.0&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  version &lt;span class=&quot;token string&quot;&gt;&quot;4.0.0&quot;&lt;/span&gt;

kleur@&lt;span class=&quot;token number&quot;&gt;4.&lt;/span&gt;x.&lt;span class=&quot;token property unquoted&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  version &lt;span class=&quot;token string&quot;&gt;&quot;4.1.4&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we look into the directory tree of the Yarn v1 project, we can indeed see two distinct &lt;code&gt;kleur&lt;/code&gt; versions:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;├─ 📁 node_modules
|  └─ 📁 kleur (4.0.0)
├─ 📁 workspace-a
|  ├─ 📁 node_modules
|  |  └─ 📁 kleur (4.1.4)
|  └─ 📄 package.json
├─ 📄 package.json
└─ 📄 yarn.lock&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I guess Yarn enforces consistency by treating workspaces as separate projects, but npm&#39;s behavior still feels more reasonable to me.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a href=&quot;https://darekkay.com/blog/npm-yarn-dependency-resolution/#conclusion&quot;&gt;
      Conclusion&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Differences between a local development environment and a build pipeline can lead to all kinds of issues. One solution is dependency pinning, i.e., using fixed dependency versions. When using version ranges instead, lock files promise to solve the consistency problem. However, there are non-obvious differences between npm and Yarn to keep in mind.&lt;/p&gt;
</content>
      <category term="javascript" />
    </entry>
    
    <entry>
      <title>Countercheck unit tests</title>
      <summary>Pitfalls with partial unit test assertions.</summary>
      <link href="https://darekkay.com/blog/countercheck-unit-tests/"/>
      <published>2021-10-17T16:26:55Z</published>
      <updated>2021-10-17T16:26:55Z</updated>
      <id>https://darekkay.com/blog/countercheck-unit-tests/</id>
      <media:thumbnail url="https://darekkay.com/blog/countercheck-unit-tests/cover.png" />
      <content type="html">&lt;p&gt;Test-driven development (TDD) is a good technique for making sure that our code matches the requirements. With frontend unit tests, it is often necessary to &lt;em&gt;countercheck&lt;/em&gt; our requirements. In this post I will use React and &lt;a href=&quot;https://testing-library.com/&quot;&gt;testing-library&lt;/a&gt;, but the underlying problem can be applied to any language and framework.&lt;/p&gt;
&lt;p&gt;Let&#39;s write a &lt;code&gt;Fruits&lt;/code&gt; component that expects an &lt;code&gt;items&lt;/code&gt; prop. Here are our requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If the list of fruits is empty, render &amp;quot;No results&amp;quot;.&lt;/li&gt;
&lt;li&gt;Otherwise, render the list of fruits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;picture class=&quot;block text-center&quot;&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://darekkay.com/blog/countercheck-unit-tests/fruits.avif, https://darekkay.com/blog/countercheck-unit-tests/fruits-2x.avif 2x&quot;&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://darekkay.com/blog/countercheck-unit-tests/fruits.webp, https://darekkay.com/blog/countercheck-unit-tests/fruits-2x.webp 2x&quot;&gt;&lt;img src=&quot;https://darekkay.com/blog/countercheck-unit-tests/fruits.png&quot; srcset=&quot;https://darekkay.com/blog/countercheck-unit-tests/fruits.png, https://darekkay.com/blog/countercheck-unit-tests/fruits-2x.png 2x&quot; alt=&quot;Visualization of the requirements: 1. No results 2. List of fruits&quot; decoding=&quot;async&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;174&quot;&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Let&#39;s start with some unit tests to cover both use cases:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;displays a message when the fruit list is empty&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Fruits&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;No results&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;displays a list of fruits&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Fruits&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Apple&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Kiwi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Apple&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you see any issue with those unit tests? We&#39;ve covered both requirements, so everything seems fine. Let&#39;s continue with the implementation:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Fruits&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; items &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;No results&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fruit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;fruit&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;fruit&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you see the issue now? This component always renders a &amp;quot;No results&amp;quot; message &lt;em&gt;and&lt;/em&gt; all provided fruits. The implementation is incorrect, yet our unit tests didn&#39;t catch it. Worse yet, our tests gave us a false sense of security with a 100% test coverage.&lt;/p&gt;
&lt;p&gt;The issue is that we have only covered the &lt;em&gt;explicit&lt;/em&gt; part of our requirements:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If the list of fruits is empty, render &amp;quot;No results&amp;quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, this statement includes an &lt;em&gt;implicit&lt;/em&gt; requirement that we didn&#39;t verify:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If the list of fruits is &lt;strong&gt;not&lt;/strong&gt; empty, &lt;strong&gt;don&#39;t&lt;/strong&gt; render &amp;quot;No results&amp;quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I call this an &lt;em&gt;inverse&lt;/em&gt; or &lt;em&gt;countercheck&lt;/em&gt; test. Here&#39;s how such a unit test can look like:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;doesn&#39;t display a message when the fruit list is non-empty&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Fruits&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Apple&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Kiwi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;No results&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;not&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;dk-alert-box type=&quot;warning&quot;&gt;
Be extra-careful when testing for the &lt;em&gt;absence&lt;/em&gt; of elements. A typo in the tested string won&#39;t make the test fail, yet the counter-check will become useless. One solution is to extract the string into a constant that is used by multiple tests.
&lt;/dk-alert-box&gt;
&lt;p&gt;When dealing with binary cases, I like to include countercheck assertions within regular tests to prevent setup duplication, e.g.:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;displays a list of fruits&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;render&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Fruits&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Apple&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Kiwi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;queryByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;No results&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;not&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;screen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getByText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Apple&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeInTheDocument&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, here&#39;s a proper implementation of the &lt;code&gt;Fruits&lt;/code&gt; component that will pass our regular and countercheck tests:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Fruits&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; items &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Loading&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    items&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;fruit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;fruit&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;fruit&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This issue happens whenever we assert a result &lt;em&gt;partially&lt;/em&gt;. It&#39;s not unique to frontend unit tests, but it&#39;s common with the &lt;code&gt;testing-library&lt;/code&gt; approach of checking if a page contains a certain element (&lt;a href=&quot;https://testing-library.com/docs/queries/about/&quot;&gt;queries&lt;/a&gt;). Keep counterchecks in mind when writing unit tests.&lt;/p&gt;
</content>
      <category term="clean-code" /><category term="javascript" /><category term="react" />
    </entry>
</feed>
