A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 15KB

4 년 전
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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>The tangled webs we weave (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://daverupert.com/2020/09/tangled-webs/">
  55. <body class="remarkdown h1-underline h2-underline h3-underline hr-center ul-star pre-tick">
  56. <article>
  57. <header>
  58. <h1>The tangled webs we weave</h1>
  59. </header>
  60. <nav>
  61. <p class="center">
  62. <a href="/david/" title="Aller à l’accueil">🏠</a> •
  63. <a href="https://daverupert.com/2020/09/tangled-webs/" title="Lien vers le contenu original">Source originale</a>
  64. </p>
  65. </nav>
  66. <hr>
  67. <main>
  68. <p class="tldr">Another reflection on modern web development</p>
  69. <p><p class="meta">September 11, 2020</p>
  70. <p>Last month I worked on a prototype of three simple pieces of technology: <code>Eleventy</code> + <code>Tailwind</code> + <code>Netlify CMS</code>. I love a good mashup. Those are fairly distinct technologies with well defined roles, so I didn’t anticipate too many hiccups. I was more on the lookout for limitations or deal breakers. The first week was filled with excellent velocity and momentum, but I hit a wall during the second week. The preview portion of <code>Netlify CMS</code> started falling apart. So I started investigating…</p>
  71. <p>Here’s a breakdown of the technologies I used to build the prototype:</p>
  72. <ul>
  73. <li>Use <code>Eleventy</code> CLI to compile <code>Markdown</code> and <code>Nunjucks</code></li>
  74. <li>Use <code>Tailwind</code> to make those look nice</li>
  75. <li>Use <code>TailwindUI</code> for some nice prefab styled components</li>
  76. <li>Use <code>PurgeCSS</code> to make <code>Tailwind</code> smaller</li>
  77. <li>Use <code>PostCSS</code> to run the <code>Tailwind</code>, <code>Purge</code>, and <code>Autoprefixer</code></li>
  78. <li>Use <code>AlpineJS</code> to make <code>Tailwind</code> interactive</li>
  79. </ul>
  80. <p>This was functioning as intended with a few <code>npm scripts</code> working in tandem. But the next step got a little slippery.</p>
  81. <ul>
  82. <li>Use <code>NetlifyCMS</code> to make updating markdown easier for content authors</li>
  83. <li>Use <code>netlify-cms-proxy-server</code> CLI so that I can test the CMS locally</li>
  84. <li>Use <code>nunjucks-precompile</code> CLI so that <code>Netlify CMS</code>’s Preview can use my <code>Nunjucks</code> templates</li>
  85. <li>Use <code>rollup</code> to bundle content filters so <code>Netlify CMS</code> can fully render the <code>Markdown</code> content (stolen from Hylia)</li>
  86. <li>Use <code>React</code> to create <code>&lt;Preview/&gt;</code> render components in <code>NetlifyCMS</code></li>
  87. <li>Use <code>Babel</code> standalone to transpile the <code>JSX</code> components.</li>
  88. </ul>
  89. <p>This is where things started to break down. <code>Alpine</code> wasn’t working within the <code>React</code> preview component of <code>Netlify CMS</code>. So all my menus were exploded and none of the interactive bits worked. I tried a few avenues for a quick fix like rewriting my <code>&lt;Preview/&gt;</code> component <code>class</code> with <code>componentDidMount</code> as a <code>function</code> with <code>useEffect</code>. This produced more errors as <code>JSX</code> got very mad because it doesn’t like the custom directives that <code>Alpine</code> uses, but those were all red herrings, however. It smelled like the problem was between <code>Alpine</code> and the <code>React</code> portion of <code>Netlify CMS</code> because the <code>&lt;Preview/&gt;</code> frame had no knowledge of <code>Alpine</code>.</p>
  90. <p>I decided the next best thing I could do is isolate the problem. I took <code>Markdown,</code> <code>Nunjucks</code>, <code>rollup</code>, and <code>Netlify CMS</code> out of the equation entirely by writing <a href="https://codepen.io/davatron5000/pen/YzqyOKz">a reduced test case in <code>CodePen</code></a> to prove that I could get <code>React</code> and <code>Alpine</code> working together. Seeing it worked in <code>CodePen</code> validated the hypothesis that the problem was on the <code>Netlify CMS</code> side of things (or however it was rendering previews). I ported my simple <code>CodePen</code> over to <code>Netlify CMS</code> with a bit of modification to inject <code>Alpine</code> and then I finally saw my problem.</p>
  91. <p>I didn’t see it before because I had too much template code in the <code>iframe</code>, but reducing the amount of code helped me finally pinpoint the issue. <code>Alpine</code> was being injected, but it was on the parent <code>window</code> context, not the <code>document</code> context of the <code>iframe</code>. Now I had to figure out how to get <code>Alpine</code> inside the preview <code>iframe</code>. Curiously, the <code>iframe</code> didn’t have a <code>src</code> or <code>srcdoc</code> attribute, so it must be some quirky <code>DocumentFragment</code> thing I’ve never really used before. Pinpointing this might be tough. So I started digging into the <code>Netlify CMS</code> source code (this pretty far down the rabbit hole for a prototype, btw). In <a href="https://github.com/netlify/netlify-cms/blob/master/packages/netlify-cms-core/src/components/Editor/EditorPreviewPane/EditorPreviewPane.js#L188-L233">EditorPreviewPane.js#L188-L233</a> you can see where the <code>iframe</code> is generated with <code>&lt;FrameContextConsumer&gt;</code> from <code>react-frame-component</code>. I know nothing about that, but you can see a way to pass preview styles into the <code>&lt;FrameContextConsumer&gt;</code>, I wondered if you could pass scripts that way as well.</p>
  92. <p>Before I embarked on a patch to <code>Netlify CMS</code>, <a href="https://github.com/netlify/netlify-cms/issues/4142">I filed a feature request</a> with a mock solution. Thankfully, Erez Rokah (maintainer) figured out a way to get access to the <code>document</code> from <code>react-frame-component</code>, with a much smaller fix than I was proposing. A patch landed in a few days. That’s an amazing turnaround and an open source success. 🎉 Thanks, Netlify!</p>
  93. <h2>LEGO, plumbing, and cattle herding</h2>
  94. <p>So my little mashup, which was supposed to be just 3 technologies ended up exposing me to ~20 different technologies and had me digging into nth-level dependency source code after midnight. If there’s an allegory for what I don’t like about modern day web development, this is it. You want to use three tools, but you have to know how to use twenty tools instead. If modules and components are like LEGO, then this is dumping out the entire bin on the floor just to find one tiny piece you need.</p>
  95. <p>This experience was flavored with a recent post by Jessica Joy Kerr “<a href="https://jessitron.com/2020/08/04/back-when-software-was-a-craft/">Back when software was craft</a>” (and <a href="https://twitter.com/searls/status/1293933781171286016">a thread by Justin Searls</a>) talking about the industrialization of our industry. Over the years we’ve made the software industry <em>even more</em> of a knowledge-based industry. We’ve moved away from a bespoke “craft”-like industry with custom hewn boards and we now have a process that resembles a system of standardized parts.</p>
  96. <blockquote>
  97. <p>Software feels more like assembly than craft.<br/>
  98. — Jessica Joy Kerr, <a href="https://jessitron.com/2020/08/04/back-when-software-was-a-craft/">Back when software was craft</a></p>
  99. </blockquote>
  100. <p>I definitely have felt this shift in my own life but have been unable to express it so simply. It feels like the job of programming has shifted from “Can you make this?” to “Do you have the knowledge to staple these two technologies together?” Kerr is more accepting of this reality than I am. The plumbing and glue code are not my favorite parts of the job. And often, you don’t truly know the limitations of any given dependency until you’re five thousand lines of code into a project. Massive sunk costs and the promise of rapid application development can come screeching to a halt when you run out of short cuts.</p>
  101. <p>It reminds me of a parable I once heard:</p>
  102. <blockquote>
  103. <p>One day a farmer, tired of plowing his field by hand, decided to build a barn and buy a bunch of cows to help tend the field. The field plowing did get easier, but eventually cows gave birth to more cows, and that farmer spent the rest of their life cutting hay to feed the cattle and shoveling their shit.</p>
  104. </blockquote>
  105. <p>Tradeoffs, man.</p></p>
  106. </main>
  107. </article>
  108. <hr>
  109. <footer>
  110. <p>
  111. <a href="/david/" title="Aller à l’accueil">🏠</a> •
  112. <a href="/david/log/" title="Accès au flux RSS">🤖</a> •
  113. <a href="http://larlet.com" title="Go to my English profile" data-instant>🇨🇦</a> •
  114. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">📮</a> •
  115. <abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">🧚</abbr>
  116. </p>
  117. <template id="theme-selector">
  118. <form>
  119. <fieldset>
  120. <legend>Thème</legend>
  121. <label>
  122. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  123. </label>
  124. <label>
  125. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  126. </label>
  127. <label>
  128. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  129. </label>
  130. </fieldset>
  131. </form>
  132. </template>
  133. </footer>
  134. <script type="text/javascript">
  135. function loadThemeForm(templateName) {
  136. const themeSelectorTemplate = document.querySelector(templateName)
  137. const form = themeSelectorTemplate.content.firstElementChild
  138. themeSelectorTemplate.replaceWith(form)
  139. form.addEventListener('change', (e) => {
  140. const chosenColorScheme = e.target.value
  141. localStorage.setItem('theme', chosenColorScheme)
  142. toggleTheme(chosenColorScheme)
  143. })
  144. const selectedTheme = localStorage.getItem('theme')
  145. if (selectedTheme && selectedTheme !== 'undefined') {
  146. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  147. }
  148. }
  149. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  150. window.addEventListener('load', () => {
  151. let hasDarkRules = false
  152. for (const styleSheet of Array.from(document.styleSheets)) {
  153. let mediaRules = []
  154. for (const cssRule of styleSheet.cssRules) {
  155. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  156. continue
  157. }
  158. // WARNING: Safari does not have/supports `conditionText`.
  159. if (cssRule.conditionText) {
  160. if (cssRule.conditionText !== prefersColorSchemeDark) {
  161. continue
  162. }
  163. } else {
  164. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  165. continue
  166. }
  167. }
  168. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  169. }
  170. // WARNING: do not try to insert a Rule to a styleSheet you are
  171. // currently iterating on, otherwise the browser will be stuck
  172. // in a infinite loop…
  173. for (const mediaRule of mediaRules) {
  174. styleSheet.insertRule(mediaRule.cssText)
  175. hasDarkRules = true
  176. }
  177. }
  178. if (hasDarkRules) {
  179. loadThemeForm('#theme-selector')
  180. }
  181. })
  182. </script>
  183. </body>
  184. </html>