A place to cache linked articles (think custom and personal wayback machine)
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.html 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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>
  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>Deno is a Browser for Code (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="#f0f0ea">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f0f0ea">
  26. <!-- Documented, feel free to shoot an email. -->
  27. <link rel="stylesheet" href="/static/david/css/style_2020-06-19.css">
  28. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  29. <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>
  30. <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>
  31. <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>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <script type="text/javascript">
  36. function toggleTheme(themeName) {
  37. document.documentElement.classList.toggle(
  38. 'forced-dark',
  39. themeName === 'dark'
  40. )
  41. document.documentElement.classList.toggle(
  42. 'forced-light',
  43. themeName === 'light'
  44. )
  45. }
  46. const selectedTheme = localStorage.getItem('theme')
  47. if (selectedTheme !== 'undefined') {
  48. toggleTheme(selectedTheme)
  49. }
  50. </script>
  51. <meta name="robots" content="noindex, nofollow">
  52. <meta content="origin-when-cross-origin" name="referrer">
  53. <!-- Canonical URL for SEO purposes -->
  54. <link rel="canonical" href="https://kitsonkelly.com/posts/deno-is-a-browser-for-code/">
  55. <body class="remarkdown h1-underline h2-underline h3-underline hr-center ul-star pre-tick">
  56. <article>
  57. <header>
  58. <h1>Deno is a Browser for Code</h1>
  59. </header>
  60. <nav>
  61. <p class="center">
  62. <a href="/david/" title="Aller à l’accueil">🏠</a> •
  63. <a href="https://kitsonkelly.com/posts/deno-is-a-browser-for-code/" title="Lien vers le contenu original">Source originale</a>
  64. </p>
  65. </nav>
  66. <hr>
  67. <main>
  68. <p>I started contributing to Deno soon after Ry made the prototype visible in
  69. May 2018. The most frequent question that people have is “where is the package
  70. manager?” which often times isn’t even in the form of a question. It is
  71. statements like “I thought Deno took security seriously, and just downloading
  72. resources off the internet is insecure.” or “How can I possibly manage my
  73. dependencies?”</p>
  74. <p>In my opinion, we need to shift our mental model. Lots of folks take the
  75. ubiquity of package managers and centralized code registries as a requirement
  76. to have a package manager and a centralized code registries. Because they exist
  77. doesn’t mean they are required. They came into existence because they solved
  78. problems in a particular way, and we have just accepted them as the only way to
  79. solve that problem. I would argue that isn’t true.</p>
  80. <h2 id="browsers">Browsers</h2>
  81. <p>In order to publish a website, we don’t login to a central Google server, and
  82. upload our website to the registry. Then if someone wants to view our website,
  83. they use a command line tool, which adds an entry to our <code>browser.json</code> file on
  84. our local machine and goes and fetches the whole website, plus any other
  85. websites that the one website links to to our local <code>websites</code> directory before
  86. we then fire up our browser to actually look at the website. That would be
  87. insane, right? So why accept that model for running code?</p>
  88. <p>The Deno CLI works like a browser, but for code. You import a URL in the code
  89. and Deno will go and fetch that code and cache it locally, just like a browser.
  90. Also, like a browser, your code runs in a sandbox, which has zero trust of the
  91. code you are running, irrespective of the source. You, the person invoking the
  92. code, get to tell that code what it can and can’t do, externally. Also, like a
  93. browser, code can ask you permission to do things, which you can choose to grant
  94. or deny.</p>
  95. <p>The HTTP protocol provides everything that is needed to provide information
  96. about the code, and Deno tries to fully leverage that protocol, without having
  97. to create a new protocol.</p>
  98. <h2 id="discovering-code">Discovering code</h2>
  99. <p>The first thing to think about is that, like a browser, the Deno CLI doesn’t
  100. want to have any opinions about what code you run. It lays out the rules of how
  101. code is fetched, and how it sandboxes itself from the machine it runs on. In my
  102. opinion, that is as much of an opinion a runtime should have.</p>
  103. <p>In the Node.js/npm ecosystem, we have conflated the management of code on our
  104. local machine, with a centralized registry of code to help facilitate discovery.
  105. In my opinion, both have really bad flaws.</p>
  106. <p>Back in the early days of the internet, we experimented with npm type of
  107. discoverability. You would go add your website to Yahoo! under the right
  108. categorization and people would come along, maybe use the search function, but
  109. it was all structured based on the opinions of those providing the content, not
  110. really based on optimizing for the needs of the consumer. Eventually along came
  111. Google. Why did Google win? Because it was useful. It indexed websites in a
  112. way that matched simple expressions of need (search terms) with the most
  113. relevant web pages that met that need, looking at multiple factors, including
  114. meta data provided the content provider as one factor in the mix.</p>
  115. <p>While we don’t have that model quite yet for code for Deno, it is a model that
  116. works. In addition, we use Google because it solves problems for us, instead of
  117. being told “you must use Google”, as well as there are also other viable
  118. alternatives to Google.</p>
  119. <p>I got into a bit of a debate with Laurie Voss on twitter, someone who knows a
  120. fair deal about the npm ecosystem I would say. He argued that Deno needed a
  121. package manager, and this blog post is a longer winded version of the thoughts
  122. I wanted to express, but Laurie raised a very valid point.</p>
  123. <p>GitHub has become the home for open source code, because it was useful and
  124. solved problems, and built on top of the <em>de facto</em> source code versioning tool,
  125. git. From the Deno CLI perspective, there should be no technical restrictions
  126. to where you source code from, it is up to the wider eco-system to create and
  127. evolve ways to make code for Deno discoverable, probably in innovative ways that
  128. could never have been conceived by those of us creating the CLI.</p>
  129. <h2 id="repeatable-builds">Repeatable builds</h2>
  130. <p>In the npm eco-system, this became a problem. Because of the heavy reliance on
  131. semantic versioning, and the complex dependency graphs that tend to come from
  132. the Node.js/npm eco-system, having a repeatable build became a real problem.
  133. Yarn introduced the concept of lock files, of which npm followed suit.</p>
  134. <p>My personal feeling is it was a bit of the tail wagging the dog, in that the
  135. behaviours of developers in the eco-system created a problem that then needed
  136. an imperfect solution to fix it. Any of us that have lived with the eco-system
  137. for a long time know that the fix to a lot of issues is
  138. <code>rm -rf node_modules package-lock.json &amp;&amp; npm install</code>.</p>
  139. <p><img src="https://memegenerator.net/img/instances/75583685/have-you-tried-rm-rf-node-modules-npm-install.jpg" alt=""/></p>
  140. <p>That being said, Deno has two solutions for that. First, is that Deno caches
  141. modules. That cache can be checked into your source control, and the
  142. <code>--cached-only</code> flag will ensure that there is not attempts to retrieve remote
  143. modules. The <code>DENO_DIR</code> environment variable can be used to specify where the
  144. cache is located to provide further flexibility.</p>
  145. <p>Second, Deno supports lock files. <code>--lock lock.json --lock-write</code> would write
  146. out a lock file with hashes of all the dependencies for a given workload. This
  147. would be used to validate future runs when the <code>--lock lock.json</code> is used.</p>
  148. <p>There are also a couple other commands that make managing repeatable builds.
  149. <code>deno cache</code> would resolve all the dependencies for a supplied module and
  150. populate the Deno cache. <code>deno bundle</code> can be used to generate a single file
  151. “build” of a workload which all the dependencies are resolved and included in
  152. that file, so only that single file is needed for future <code>deno run</code> commands.</p>
  153. <h2 id="trusting-code">Trusting code</h2>
  154. <p>This is another area where I think we have a skewed mental model. For whatever
  155. reason, we put trust in code that is in a centralized registry. We don’t even
  156. think about it. Not only that, we trust that that code has fully vetted all of
  157. its dependencies and that those are to be trusted to. We do a quick search and
  158. type in <code>npm install some-random-package</code> and think “This is Fine!” I argue the
  159. rich npm package eco-system has lulled is into a sense of complacency.</p>
  160. <p>To compensate for this laxness and complacency, we implement security monitoring
  161. software in our tool chains, to analyse our dependencies and the thousands upon
  162. thousand lines of code to let us know that maybe some of the code is
  163. exploitable. Corporations setup private registries to host packages that might
  164. be vetted slightly more than the single public registry.</p>
  165. <p>It feels like there is an elephant in the room here. The best strategy is we
  166. shouldn’t trust any code. Once we have that established, then opening it back
  167. up becomes a little be easier. But we are lying to ourselves if we think a
  168. package manager and a centralised registry solve this problem, or even
  169. substantially help with this problem. In fact, I argue they make use let our
  170. guards down. “Well it is on npm, if it were bad for me, surely someone would
  171. take it down.”</p>
  172. <p>Deno in this aspect isn’t quite as done as I think it should be, but it is
  173. starting from a good position. It has zero trust at startup, and provides
  174. fairly fine grained permissions. One of the things I personally dislike is that
  175. there is the <code>-A</code> flag, which is basically saying “oh yeah allow everything”
  176. which is such an easy thing for a frustrated developer to do instead of figuring
  177. out what they really need.</p>
  178. <p>It is also hard to break down those permissions, to say “this code can do this,
  179. but this other code over here can’t” or when code prompts to escalate privileges
  180. where is that code coming from. Hopefully we can figure out an easy to use
  181. mechanism coupled with something that would be effective and performant at
  182. runtime to try to solve those challenges.</p>
  183. <p>A recent change though, which is a good one, in my opinion, is that Deno no
  184. longer allows you to downgrade your imports. If something is imported from
  185. <code>https://</code> then it can only import from other <code>https://</code> locations. This
  186. follows the browser model of not being able to downgrade transport. I still
  187. think longer term it would be good to kill off any remote imports that aren’t
  188. over <code>https://</code>, much like Service Workers require HTTPS, so we will see what
  189. the future holds.</p>
  190. <h2 id="dependency-management">Dependency management</h2>
  191. <p>I think we need to talk frankly about dependencies in the npm ecosystem. To be
  192. honest, it is broken. An ecosystem that enables
  193. <a href="https://github.com/juliangruber/isarray/blob/master/index.js" target="_blank">5 lines of code</a>
  194. to be downloaded and installed
  195. <a href="https://www.npmjs.com/package/isarray" target="_blank"><em>30 million</em> times a week</a> for code that
  196. has been in every browser for the last 9 years and never was needed in Node.js
  197. is a broken ecosystem. This one example, the actual code is 132 bytes, but the
  198. package size is 3.4kb. The runnable code is 3.8% of the package size. “This is
  199. Fine!”</p>
  200. <p>My opinion is that there are several factors involved in this. A big part of it
  201. is that we have the model inverted, which I talked about Deno being a browser
  202. for code. The problem is that this backwards model has infected how we create
  203. websites. While we don’t have a central registry, when we build a website,
  204. we download all the code we depend up and bake it into something that we load
  205. up on a server, and then each user downloads a bunch of code to their local
  206. machine. Some evidence is that only around 10% of that code that is downloaded
  207. is unique to that site or web application, the rest is all that code we are
  208. downloading to our development workstation and bundling up. This model being
  209. broken are some of the problems solutions like
  210. <a href="https://www.snowpack.dev/" target="_blank">Snowpack</a> are trying to solve.</p>
  211. <p>Another significant problem is that our dependencies are not coupled with our
  212. code. We put dependencies in our <code>package.json</code> but if our code actually uses
  213. that code or not is totally decoupled. While our code expresses what we are
  214. using out of that other code, it is very loosely coupled to the version of that
  215. code. That is contained in the <code>package.json</code>, though it has the biggest impact
  216. on the code we write, because it is the code that is actually consuming the
  217. dependent code.</p>
  218. <p>This leads us to the Deno model, which I like to call <em>Deps-in-JS</em>, since all
  219. the cool kids are doing <em>*-in-JS</em> things. Explicitly stating our external
  220. dependencies as URLs means that the code depends upon the other code is concise
  221. and clear, and our code and dependencies are tightly coupled together. If you
  222. want to see that dependency graph, you simply need to use <code>deno info</code> with a
  223. local or remote module:</p>
  224. <pre><code class="language-shell">$ deno info https://deno.land/x/oak/examples/server.ts
  225. local: $deno/deps/https/deno.land/d355242ae8430f3116c34165bdae5c156dca21aeef521e45acb51fcd21c9f724
  226. type: TypeScript
  227. compiled: $deno/gen/https/deno.land/x/oak/examples/server.ts.js
  228. map: $deno/gen/https/deno.land/x/oak/examples/server.ts.js.map
  229. deps:
  230. https://deno.land/x/oak/examples/server.ts
  231. ├── https://deno.land/std@0.53.0/fmt/colors.ts
  232. └─┬ https://deno.land/x/oak/mod.ts
  233. ├─┬ https://deno.land/x/oak/application.ts
  234. │ ├─┬ https://deno.land/x/oak/context.ts
  235. │ │ ├── https://deno.land/x/oak/cookies.ts
  236. │ │ ├─┬ https://deno.land/x/oak/httpError.ts
  237. │ │ │ └─┬ https://deno.land/x/oak/deps.ts
  238. │ │ │ ├── https://deno.land/std@0.53.0/hash/sha256.ts
  239. │ │ │ ├─┬ https://deno.land/std@0.53.0/http/server.ts
  240. │ │ │ │ ├── https://deno.land/std@0.53.0/encoding/utf8.ts
  241. │ │ │ │ ├─┬ https://deno.land/std@0.53.0/io/bufio.ts
  242. │ │ │ │ │ ├─┬ https://deno.land/std@0.53.0/io/util.ts
  243. --snip--
  244. </code></pre>
  245. <p>Deno has no strong opinions around “versions” of code. A URL is a URL is a URL.
  246. While Deno requires an appropriate media type in order to understand how to
  247. treat code, all the “opinions” about what code to serve up is left up to the
  248. web server. A server can implement semantic versioning to its hearts content,
  249. or do any sort of “magical” mapping of URLs to resources it wants. Deno doesn’t
  250. care. For example <code>https://deno.land/x/</code> is effectively nothing but a URL
  251. redirect server, where it rewrites URLs to include a git commit-ish reference
  252. in the redirected URL. So <code>https://deno.land/x/oak@v4.0.0/mod.ts</code> becomes
  253. <code>https://raw.githubusercontent.com/oakserver/oak/v4.0.0/mod.ts</code>, which GitHub
  254. serves up a nice versioned module.</p>
  255. <p>Of course spreading “versioned” remote URLs throughout your codebase doesn’t
  256. make a lot of sense, so don’t do that. The great thing about the dependencies
  257. just being code though is that you can structure them any way you want to. A
  258. common convention is to use a <code>deps.ts</code> which re-exports all the dependencies
  259. you might want. Take a look at the one for
  260. <a href="https://deno.land/x/oak@v4.0.0/deps.ts" target="_blank">oak server</a>:</p>
  261. <pre><code class="language-ts">// Copyright 2018-2020 the oak authors. All rights reserved. MIT license.
  262. // This file contains the external dependencies that oak depends upon
  263. // `std` dependencies
  264. export { HmacSha256 } from "https://deno.land/std@0.51.0/hash/sha256.ts";
  265. export {
  266. Response,
  267. serve,
  268. Server,
  269. ServerRequest,
  270. serveTLS,
  271. } from "https://deno.land/std@0.51.0/http/server.ts";
  272. export {
  273. Status,
  274. STATUS_TEXT,
  275. } from "https://deno.land/std@0.51.0/http/http_status.ts";
  276. export {
  277. Cookies,
  278. Cookie,
  279. setCookie,
  280. getCookies,
  281. delCookie,
  282. } from "https://deno.land/std@0.51.0/http/cookie.ts";
  283. export {
  284. basename,
  285. extname,
  286. join,
  287. isAbsolute,
  288. normalize,
  289. parse,
  290. resolve,
  291. sep,
  292. } from "https://deno.land/std@0.51.0/path/mod.ts";
  293. export { assert } from "https://deno.land/std@0.51.0/testing/asserts.ts";
  294. // 3rd party dependencies
  295. export {
  296. contentType,
  297. lookup,
  298. } from "https://deno.land/x/media_types@v2.3.1/mod.ts";
  299. </code></pre>
  300. <p>I created oak server and maintained for 18 months through about 40 releases of
  301. Deno and the Deno <code>std</code> library, including moving of <code>media_types</code> from internal
  302. to oak, out to the <code>std</code> library, to only have it be “ejected” from the <code>std</code>
  303. library to be its own thing. Not once did I think to myself “hey, I need a
  304. package manager to manage this for me”.</p>
  305. <p>One of the benefits of TypeScript is that you can get comprehensive validation
  306. of compatibility of your code with other code. If your dependencies are “raw”
  307. TypeScript written for Deno, this is great, but let’s say that you want to take
  308. advantage of pre-processing of the TypeScript to JavaScript, but still have the
  309. ability to consume that remote code safely. Deno supports a couple different
  310. ways to allow that to happen, but the most seamless is the support for the
  311. <code>X-TypeScript-Types</code> header. This header indicates to Deno where a types file
  312. is located which can be used when type checking the JavaScript file that you
  313. are depending upon. <a href="https://pika.dev/cdn" target="_blank">Pika CDN</a> supports this. Any
  314. packages that are available on the CDN that have types associated with them will
  315. serve up that header and Deno will also fetch those types and use that when
  316. type checking the file.</p>
  317. <p>All this being said, there may still be a need to “remap” a remote (or local)
  318. dependency to what is expressed in the code. In this case, the unstable
  319. implementation of <a href="https://github.com/WICG/import-maps" target="_blank">import-maps</a> can be
  320. used. It is a proposal specification that is part of the W3C incubator where
  321. browser standards come out of. It allows a map to be provided which will map
  322. a particular dependency in code to another resource, be it a local file or a
  323. remote module.</p>
  324. <p>We had it implemented in Deno for an extended period of time, as we had really
  325. hoped that it would become adopted widely. Sadly, it was only an
  326. <a href="https://chromestatus.com/feature/5315286962012160" target="_blank">origin trial in Chrome</a> and
  327. hasn’t gotten wider adoption yet. This led us to putting it behind the
  328. <code>--unstable</code> flag for Deno 1.0. My personal opinion is that it is still a big
  329. risk of being a dead end, and should be avoided.</p>
  330. <h2 id="but-but-but">But, but, but…</h2>
  331. <p>I suspect a lot of people are still coming with a list of objections to the
  332. model that Deno has. I think the strategy Deno has tried to take, which I am
  333. very aligned to, is to deal with real problems when they arise. A lot of the
  334. objections I hear are from people who are new to Deno, who haven’t worked with
  335. it, who haven’t tried to understand that there might be a different way.</p>
  336. <p>All that being said, if we collectively run into a problem and there is a
  337. compelling need to change something in the Deno CLI, I am confident that it will
  338. happen, but a lot of problems simply don’t exist, or there are other ways to
  339. solve them that don’t require your runtime to have strong opinions or be coupled
  340. to an external programme to manage your code.</p>
  341. <p>So my challenge to you is, flirt a bit with not having a package manager or
  342. a centralised package repository and see how it goes. You might never go back!</p>
  343. </main>
  344. </article>
  345. <hr>
  346. <footer>
  347. <p>
  348. <a href="/david/" title="Aller à l’accueil">🏠</a> •
  349. <a href="/david/log/" title="Accès au flux RSS">🤖</a> •
  350. <a href="http://larlet.com" title="Go to my English profile" data-instant>🇨🇦</a> •
  351. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">📮</a> •
  352. <abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">🧚</abbr>
  353. </p>
  354. <template id="theme-selector">
  355. <form>
  356. <fieldset>
  357. <legend>Thème</legend>
  358. <label>
  359. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  360. </label>
  361. <label>
  362. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  363. </label>
  364. <label>
  365. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  366. </label>
  367. </fieldset>
  368. </form>
  369. </template>
  370. </footer>
  371. <script type="text/javascript">
  372. function loadThemeForm(templateName) {
  373. const themeSelectorTemplate = document.querySelector(templateName)
  374. const form = themeSelectorTemplate.content.firstElementChild
  375. themeSelectorTemplate.replaceWith(form)
  376. form.addEventListener('change', (e) => {
  377. const chosenColorScheme = e.target.value
  378. localStorage.setItem('theme', chosenColorScheme)
  379. toggleTheme(chosenColorScheme)
  380. })
  381. const selectedTheme = localStorage.getItem('theme')
  382. if (selectedTheme && selectedTheme !== 'undefined') {
  383. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  384. }
  385. }
  386. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  387. window.addEventListener('load', () => {
  388. let hasDarkRules = false
  389. for (const styleSheet of Array.from(document.styleSheets)) {
  390. let mediaRules = []
  391. for (const cssRule of styleSheet.cssRules) {
  392. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  393. continue
  394. }
  395. // WARNING: Safari does not have/supports `conditionText`.
  396. if (cssRule.conditionText) {
  397. if (cssRule.conditionText !== prefersColorSchemeDark) {
  398. continue
  399. }
  400. } else {
  401. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  402. continue
  403. }
  404. }
  405. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  406. }
  407. // WARNING: do not try to insert a Rule to a styleSheet you are
  408. // currently iterating on, otherwise the browser will be stuck
  409. // in a infinite loop…
  410. for (const mediaRule of mediaRules) {
  411. styleSheet.insertRule(mediaRule.cssText)
  412. hasDarkRules = true
  413. }
  414. }
  415. if (hasDarkRules) {
  416. loadThemeForm('#theme-selector')
  417. }
  418. })
  419. </script>
  420. </body>
  421. </html>