A place to cache linked articles (think custom and personal wayback machine)
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

2 роки тому
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Progressively Enhanced Builds (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Documented, feel free to shoot an email. -->
  28. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  29. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  30. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  31. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  36. <script>
  37. function toggleTheme(themeName) {
  38. document.documentElement.classList.toggle(
  39. 'forced-dark',
  40. themeName === 'dark'
  41. )
  42. document.documentElement.classList.toggle(
  43. 'forced-light',
  44. themeName === 'light'
  45. )
  46. }
  47. const selectedTheme = localStorage.getItem('theme')
  48. if (selectedTheme !== 'undefined') {
  49. toggleTheme(selectedTheme)
  50. }
  51. </script>
  52. <meta name="robots" content="noindex, nofollow">
  53. <meta content="origin-when-cross-origin" name="referrer">
  54. <!-- Canonical URL for SEO purposes -->
  55. <link rel="canonical" href="https://blog.jim-nielsen.com/2022/progressively-enhanced-builds/">
  56. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  57. <article>
  58. <header>
  59. <h1>Progressively Enhanced Builds</h1>
  60. </header>
  61. <nav>
  62. <p class="center">
  63. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  64. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  65. </svg> Accueil</a> •
  66. <a href="https://blog.jim-nielsen.com/2022/progressively-enhanced-builds/" title="Lien vers le contenu original">Source originale</a>
  67. </p>
  68. </nav>
  69. <hr>
  70. <p>With the advent of the Jamstack, so many of websites require a build step of some kind before a functioning website can be born.</p>
  71. <p>Build steps are great. I use them all the time. But they do come at a cost.</p>
  72. <p>One of those costs is fragility—builds are prone to breaking. It’s not uncommon to setup a project, return to it six weeks later, run <code>npm install &amp;&amp; npm start</code>, and <em>something</em> breaks. Then begins the troubleshooting. A project from 6 days ago, 6 weeks ago, or 6 years ago, each one of those could present its own set of build problems. Often, you end up with a non-functioning website you cannot edit, test, and update until your build is fixed.</p>
  73. <p>We have a strategy for delivering robust, <a href="https://resilientwebdesign.com">resilient</a> experiences to users in the browser: progressive enhancement. Start with the most basic functionality that lets people do what they need, then enhance from there. In this way, lots of things could go wrong — JavaScript doesn’t load, network calls fail, the user is on an old browser, etc. — but the website remains functional and accessible.</p>
  74. <p>What if we applied a mindset of progressive enhancement to builds and not only user agents? What if build processes were thought of as enhancements and not dependencies? Can you have a progressively enhanced build?</p>
  75. <h2 id="conceptual-underpinnings">Conceptual Underpinnings</h2>
  76. <p>I’ve had a few thoughts swirling in my head around this idea.</p>
  77. <p>First there’s <a href="https://www.baldurbjarnason.com/2021/100-things-every-web-developer-should-know/">Baldur Bjarnason</a> who speaks on the value of tight feedback loops:</p>
  78. <blockquote>
  79. <p>Tight feedback loops are magic: build processes suck. They will always suck because they always loosen up the feedback loop. You can make builds quicker, but until they are virtually instantaneous, they will always suck…If you can make something without a build step, do so. If you can make something without compilation or building, do that. </p>
  80. </blockquote>
  81. <p>Then there’s <a href="https://inoads.com/articles/2021-01-09-Next-Gen-Static-Blogging">this proof-of-concept post from Maximilian Mackh</a> (with some <a href="https://news.ycombinator.com/item?id=25701053">intriguing hackernews comments</a>) which shows how to write a blog post and skip the build process using nifty tricks from HTML and CSS.</p>
  82. <blockquote>
  83. <p>My workflow consists of pressing CMD+S, switching to Safari and reloading the page. What you see now is what I get.</p>
  84. </blockquote>
  85. <p>Then there’s <a href="https://www.robinrendle.com/notes/blogging-and-the-heat-death-of-the-universe/">this post from Robin</a> who notes that entropy acts on all things, including websites and build processes until the last thing left standing is the markup.</p>
  86. <blockquote>
  87. <p>That complex build process? That’s a dependency…</p>
  88. <p>the thing that lasts longest with our websites is probably the part that we spend the least time thinking about—the markup…</p>
  89. <p>This is the second law of thermodynamics made clear on the web: the entropy of any isolated system always increases and, at some point or another, all that’s left of a website is the markup.</p>
  90. </blockquote>
  91. <p>Then there’s <a href="https://adactio.com/journal/17537">this post from Jeremy</a> which details how to think about your website’s features as an enhancements rather than dependencies.</p>
  92. <blockquote>
  93. <p>You certainly couldn’t use an experimental feature for anything mission critical…but you could use it as an enhancement.</p>
  94. <p>And that is a pretty great way to think about all web features, experimental or otherwise. Don’t assume the feature will be supported. Use feature detection (or @supports in the case of CSS). Try to use the feature as an enhancement rather than a dependency.</p>
  95. </blockquote>
  96. <p>The key is to, as Jeremy says, “acknowledge and embrace unpredictability”. That seems like solid advice, whether you’re building on the client <em>or the server</em>.</p>
  97. <blockquote>
  98. <p>Instead of looking at the support table for something on caniuse.com and thinking, “I wish more browsers supported this feature so that I could use it!”, you can instead think “I’m going to use this feature today, but treat it as an experimental feature.”</p>
  99. </blockquote>
  100. <p>In a similar vein, what if you could look at building a website in a different light? Rather than thinking, “how do I combine a bunch of disparate content, templates, and tooling into a functioning website?”, you might think “how do I start at a functioning website with content and then use templates and build tooling to enhance it?”</p>
  101. <p>[Granted: this is not a silver bullet for building every website. Lots of websites can’t start here—but maybe some could?]</p>
  102. <h2 id="technical-ideas">Technical Ideas</h2>
  103. <p>What could this look like? Honestly, I don’t know. But I’ll take a moment to try and imagine <em>something</em>.</p>
  104. <p>I want to be able to view, edit, <em>and if need be ship</em> a website, even if the build process fails. In essence, if the build does fail I can still take all the source files, put them on a server, and the website remains functional (however crude).</p>
  105. <p>I think this kind of approach begs for an HTML-first solution. Your content starts as something that works in the browser — in a less-than-ideal way, sure, but still functional — and the build process parses the base content, injecting additional “features” as enhancements (global header and footer, meta tags, links to CSS and JS, etc.). In this manner, your website starts as content at a URL. Everything else is an enhancement.</p>
  106. <p>For example, think of a classic blog post as a markdown file.</p>
  107. <pre><code class="language language-md">---
  108. title: This is the title of my blog post
  109. date: 2020-09-02
  110. tags:
  111. <span class="hljs-bullet"> -</span> readingNotes
  112. <span class="hljs-section"> - somethingElse
  113. ---</span>
  114. <span class="hljs-section"># This is the title of my post</span>
  115. I just want to start writing here whatever it is i'm about.
  116. This is just another example of how you might do something that is really light and minimal. [<span class="hljs-string">Here’s a link</span>](<span class="hljs-link">./another-post.md</span>)
  117. ![<span class="hljs-string">An image for my blog post</span>](<span class="hljs-link">../images/my-image.jpg</span>)
  118. </code></pre>
  119. <p>What if you rewrote that using HTML for the front-matter semantics? If you want to continue to author the content of the post in markdown, do it, but embed it in a web component (which would get “enhanced” and rendered to HTML somewhere further down the line).</p>
  120. <p>What’s beautiful about markdown is that it’s a kind of progressively-enhanced syntax for writing: link and image URLs are still accessible even when unparsed.</p>
  121. <pre><code class="language language-html"><span class="hljs-meta">&lt;!doctype <span class="hljs-keyword">html</span>&gt;</span>
  122. <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>This is the title of my blog post<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  123. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"date"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"2020-09-02"</span>&gt;</span>
  124. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"tags"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"readingNotes,somethingElse"</span>&gt;</span>
  125. <span class="hljs-tag">&lt;<span class="hljs-name">wc-markdown</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"white-space: pre-line"</span>&gt;</span>
  126. # This is the title of my post
  127. I just want to start writing here whatever it is i'm about.
  128. This is just another example of how you might do something that is really light and minimal. [Here is a link](./another-post.html)
  129. ![An image for my blog post](../images/my-image.jpg)
  130. <span class="hljs-tag">&lt;/<span class="hljs-name">wc-markdown</span>&gt;</span>
  131. </code></pre>
  132. <p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/progressive-build-markdown.png" alt="">
  133. </p>
  134. <p>There are trade-offs here: authoring in markdown is a convenience for me, the author, and results in a more crude initial experience (if a build enhancement fails later).</p>
  135. <p>Given that markdown supports HTML, you could trade the image syntax (<code>![]()</code>) for <code>&lt;img&gt;</code> tags or the link syntax (<code>[]()</code>) for <code>&lt;a&gt;</code> tags. That’ll improve the core experience a tiny bit in the browser, as links would be functionally clickable and images would display directly inline.</p>
  136. <pre><code class="language language-html"><span class="hljs-meta">&lt;!doctype <span class="hljs-keyword">html</span>&gt;</span>
  137. <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>This is the title of my blog post<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  138. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"date"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"2020-09-02"</span>&gt;</span>
  139. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"tags"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"readingNotes,somethingElse"</span>&gt;</span>
  140. <span class="hljs-tag">&lt;<span class="hljs-name">wc-markdown</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"white-space: pre-line"</span>&gt;</span>
  141. # This is the title of my post
  142. I just want to start writing here whatever it is i'm about.
  143. This is just another example of how you might do something that is really light and minimal. <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"./another-post.html"</span>&gt;</span>Here is a link<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  144. ![An image for my blog post](../images/my-image.jpg)
  145. <span class="hljs-tag">&lt;/<span class="hljs-name">wc-markdown</span>&gt;</span>
  146. </code></pre>
  147. <p>Or, as inspired by <a href="https://news.ycombinator.com/item?id=25701842">this comment</a>, you could author your content directly in as sparse a take on HTML as is possible.</p>
  148. <pre><code class="language language-html"><span class="hljs-meta">&lt;!doctype <span class="hljs-keyword">html</span>&gt;</span>
  149. <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>This is the title of my blog post<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  150. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"date"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"2020-09-02"</span>&gt;</span>
  151. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"tags"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"readingNotes,somethingElse"</span>&gt;</span>
  152. <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>This is the title of my post<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  153. <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
  154. I just want to start writing here whatever it is i'm about.
  155. <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
  156. This is just another example of how you might do something that is really light and minimal. <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"./another-post.html"</span>&gt;</span>Here is a link<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  157. <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
  158. <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./image.jpg"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"An image for my blog post"</span>&gt;</span>
  159. </code></pre>
  160. <p>You can see how trading authoring conveniences begins to enhance the base browser experience.</p>
  161. <p class="image-container"><img src="https://cdn.jim-nielsen.com/blog/2022/progressive-build-html.png" alt="">
  162. </p>
  163. <p>These files could be opened in a browser, without a build process or server (i.e. at a URL of <code>file:///Users/Me/Path/To/my-post.html</code>) and the core content would be accessible. And if you markup all the page’s included resources (links, images, styles, scripts, etc.) as relative links, everything will work even without a server. (Granted you’d have to give some thought to structuring your files accordingly.)</p>
  164. <pre><code>.
  165. ├── index.html
  166. ├── styles.css
  167. ├── scripts.js
  168. ├── posts/
  169. │ ├── 2020-02-05-my-post.html
  170. │ ├── 2020-02-05-my-post-image.jpg
  171. │ └── 2020-03-21-another-post.html
  172. </code></pre>
  173. <p>If you want the base experience you author to be even slightly better, you could write the stylesheet links and script includes in every single file (rather than append them to each document through a build process). Going back to the markdown example:</p>
  174. <pre><code class="language language-html"><span class="hljs-meta">&lt;!doctype <span class="hljs-keyword">html</span>&gt;</span>
  175. <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>This is the title of my blog post<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  176. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"date"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"2020-09-02"</span>&gt;</span>
  177. <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"tags"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"readingNotes,somethingElse"</span>&gt;</span>
  178. <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"./web-component-markdown.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  179. <span class="hljs-tag">&lt;<span class="hljs-name">wc-markdown</span>&gt;</span>
  180. # This is the title of my post
  181. I just want to start writing here whatever it is i'm about.
  182. This is just another example of how you might do something that is really light and minimal. [Here is a link](./another-post.html)
  183. ![An image for my blog post](../images/my-image.jpg)
  184. <span class="hljs-tag">&lt;/<span class="hljs-name">wc-markdown</span>&gt;</span>
  185. </code></pre>
  186. <p>In this example, the trade-off is: overhead to include the <code>&lt;script&gt;</code> tag in every post, but the core experience in the browser is now more enhanced (if JavaScript runs) because the <code>&lt;wc-markdown&gt;</code> component will turn the markdown to HTML in the browser. An additional enhancement from here would be to have the build process (again, if it runs) render the markdown to HTML so the client doesn’t even need the web component!</p>
  187. <p>The beauty is the build process works like progressive enhancement in the browser: the core experience is functional and accessible, then the build process enhances whatever you want to add by injecting those enhancements to the original, accessible HTML documents (things like style tags, script tags, or global header/footer HTML — I could imagine something like <a href="https://cheerio.js.org/">cheerio</a> being useful here).</p>
  188. <p>The build process is a progressive enhancement to the core content of your website. Whether it runs or not, your site remains accessible and deployable at any point in time.</p>
  189. <p>I get it, this won’t work for every website. And things like permalinks get tricky. But I still think it’s an intriguing mindset to start from: don’t assume the presence of a build, treat it as an enhancement rather than a dependency.</p>
  190. <p>I should prototype the idea. I could imagine doing this with a tool like Metalsmith:</p>
  191. <ul>
  192. <li>Put your source files in directory (which works and remains deployable if everything else fails).</li>
  193. <li>Metalsmith reads in all the files and produces a data structure of all the content</li>
  194. <li>Additional files and markup are generated from your site’s content, like index listings, “related posts” sections, etc., while other necessary elements like styles, scripts, or global header/footer markup are injected to each page.</li>
  195. <li>The new site is output as an “enhanced” version of the site <em>before your build started</em>.</li>
  196. </ul>
  197. <p>Are there any build tools advocating this kind of mindset? If you know of any, hit me up. </p>
  198. </article>
  199. <hr>
  200. <footer>
  201. <p>
  202. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  203. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  204. </svg> Accueil</a> •
  205. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  206. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  207. </svg> Suivre</a> •
  208. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  209. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  210. </svg> Pro</a> •
  211. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  212. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  213. </svg> Email</a> •
  214. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  215. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  216. </svg> Légal</abbr>
  217. </p>
  218. <template id="theme-selector">
  219. <form>
  220. <fieldset>
  221. <legend><svg class="icon icon-brightness-contrast">
  222. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  223. </svg> Thème</legend>
  224. <label>
  225. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  226. </label>
  227. <label>
  228. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  229. </label>
  230. <label>
  231. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  232. </label>
  233. </fieldset>
  234. </form>
  235. </template>
  236. </footer>
  237. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  238. <script>
  239. function loadThemeForm(templateName) {
  240. const themeSelectorTemplate = document.querySelector(templateName)
  241. const form = themeSelectorTemplate.content.firstElementChild
  242. themeSelectorTemplate.replaceWith(form)
  243. form.addEventListener('change', (e) => {
  244. const chosenColorScheme = e.target.value
  245. localStorage.setItem('theme', chosenColorScheme)
  246. toggleTheme(chosenColorScheme)
  247. })
  248. const selectedTheme = localStorage.getItem('theme')
  249. if (selectedTheme && selectedTheme !== 'undefined') {
  250. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  251. }
  252. }
  253. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  254. window.addEventListener('load', () => {
  255. let hasDarkRules = false
  256. for (const styleSheet of Array.from(document.styleSheets)) {
  257. let mediaRules = []
  258. for (const cssRule of styleSheet.cssRules) {
  259. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  260. continue
  261. }
  262. // WARNING: Safari does not have/supports `conditionText`.
  263. if (cssRule.conditionText) {
  264. if (cssRule.conditionText !== prefersColorSchemeDark) {
  265. continue
  266. }
  267. } else {
  268. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  269. continue
  270. }
  271. }
  272. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  273. }
  274. // WARNING: do not try to insert a Rule to a styleSheet you are
  275. // currently iterating on, otherwise the browser will be stuck
  276. // in a infinite loop…
  277. for (const mediaRule of mediaRules) {
  278. styleSheet.insertRule(mediaRule.cssText)
  279. hasDarkRules = true
  280. }
  281. }
  282. if (hasDarkRules) {
  283. loadThemeForm('#theme-selector')
  284. }
  285. })
  286. </script>
  287. </body>
  288. </html>