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 17KB

  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>Building with Friction (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://timkadlec.com/remembers/2020-03-18-building-with-friction/">
  55. <body class="remarkdown h1-underline h2-underline h3-underline hr-center ul-star pre-tick">
  56. <article>
  57. <header>
  58. <h1>Building with Friction</h1>
  59. </header>
  60. <nav>
  61. <p class="center">
  62. <a href="/david/" title="Aller à l’accueil">🏠</a> •
  63. <a href="https://timkadlec.com/remembers/2020-03-18-building-with-friction/" title="Lien vers le contenu original">Source originale</a>
  64. </p>
  65. </nav>
  66. <hr>
  67. <main>
  68. <p>I <a href="https://timkadlec.com/remembers/2020-01-02-making-the-right-thing-easy/">recently wrote about how important it is to make the right thing easy</a>. The opposite is also true: it’s important to make the wrong things difficult. I did allude to it in that post a little bit, but I thought it was worth calling out explicitly. It’s important to introduce some friction in our workflow to help prevent the wrong actions.</p>
  69. <p>Continuing on the health-related analogies, friction is a big part of how I manage my sweet tooth. I work by myself in a small office. Nothing is preventing me from constantly snacking on a bunch of sweets, and <em>wow</em> would I ever love to. I’m a sucker for just about anything with sugar.</p>
  70. <p>But I discovered something else about myself: I’m also kinda lazy. So I take a two-part approach. The first is to make the right thing easy. I have apples, oranges, almonds, dried cranberries, and all sorts of healthier snacking options right next to my desk. If I’m hungry, I don’t even have to move. I reach out my arm, and there they are.</p>
  71. <p>But the second part of that process is just as important. I make the wrong thing harder. I do have some sweets, but they’re tucked away in an adjacent room. It’s not difficult to get to them, but it does require more effort than the healthy alternatives right next to me. It doesn’t stop me from having sweets, but it means that reaching for that chocolate involves a conscious decision to put in more work than if I decide to have an apple. It’s just enough friction to change the way I snack.</p>
  72. <p>A lot of modern workflow improvements have been around <em>removing</em> friction. We want to make it easier to deploy rapidly. Tools like npm make it <em>very</em> easy to gain access to any and all modules we could think of. Tag management enables folks to very quickly add another third-party service.</p>
  73. <p>All of these things, on the surface, provide some value, but the consequences are tremendous. Because these processes remove friction, they don’t ever really give us a chance to pause and consider what we’re doing.</p>
  74. <p>Re-introducing some healthy friction, some moments of pause, in our processes is critical to ensuring a higher level of quality overall.</p>
  75. <p>For example, let’s tackle the trouble with <a href="https://www.npmjs.com/">npm</a>.</p>
  76. <p>npm transformed the way we build, but I don’t think anyone can argue that it hasn’t wreaked some serious havoc in the process. The ready availability of a JavaScript module for pretty much anything you can imagine has lead to security issues, accessibility concerns and overall bloat. It’s made it <em>too easy</em> to add more code to our sites without ever considering the trade-offs.</p>
  77. <p>I’m with <a href="https://twitter.com/slightlylate/status/1239404640656998400">Alex on this one</a>. Adding more code should be a very intentional decision:</p>
  78. <blockquote>
  79. <p>JavaScript should be a *deeply” intentional choice on the client. Tools that remove intentionality, whatever else they may have done for your team, probably sunk your perf battleship.</p>
  80. </blockquote>
  81. <p>Here’s an example of how we could introduce some friction into the process to help with the performance challenges by focusing on two critical points in our workflow: install and build/deploy.</p>
  82. <h2 id="during-install">During install</h2>
  83. <p>The first thing we can do is introduce some friction when we first install a script. After all, the easiest issues to fix are the ones that haven’t happened yet.</p>
  84. <p>I like <a href="https://github.com/AdrieanKhisbe/bundle-phobia-cli#bundle-phobia-install"><code>bundle-phobia-install</code></a> for this. <code>bundle-phobia-install</code> is a wrapper around <code>npm install</code> that uses information from <a href="https://bundlephobia.com/">Bundlephobia</a> to conditionally install npm modules. It does this by comparing the size of the package against some predetermined limits. It defaults to a size limit of 100kB overall (as in, the total of all dependencies), but you can configure that however you would like.</p>
  85. <p>You can also set up limits on individual packages.</p>
  86. <p>For example, the following settings (configured in a <code>package.json</code> file) would ensure that no individual package with a size of over 20kB could be installed, and that the total size of all dependencies can be no more than 100kB.</p>
  87. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript">...
  88. <span>"bundle-phobia"</span><span>:</span> {
  89. <span>"max-size"</span><span>:</span> <span>"20kB"</span>,
  90. <span>"max-overall-size"</span><span>:</span> <span>"100kB"</span>
  91. },
  92. ...
  93. </code></pre></div>
  94. <p>Now, if we were to try to install, say, <code>lodash</code>, the install would fail because <code>lodash</code> exceeds our individual package size limit.</p>
  95. <figure class="banner">
  96. <img src="https://timkadlec.com/images/friction-bundle-phobia.png" alt="Running bundle-phobia-install instead of npm install lets us enforce size limits on npm modules, preventing us from adding significantly heavy dependencies to our site."/> <figcaption>
  97. <p>Running <code>bundle-phobia-install</code> instead of <code>npm install</code> lets us enforce size limits on npm modules, preventing us from adding significantly heavy dependencies to our site.</p>
  98. </figcaption>
  99. </figure>
  100. <p>You could still install <code>lodash</code>, but that now requires you to run <code>bundle-phobia-install</code> with the interactive flag (<code>-i</code>) and manually approve the install despite the fact that it exceeds your limits. It turns an unconscious decision into a conscious one.</p>
  101. <h2 id="during-build-deploy">During build/deploy</h2>
  102. <p>By having some friction on the install process, we help to provide a better base for size of our JavaScript. It’s still critical to put some friction on the build and deploy process, though. For one, our install approach is only limiting npm modules, not really our own code. We also don’t really know the exact shape of our bundles at install—that comes later.</p>
  103. <p>For webpack-driven projects, you can take advantage of webpack’s <a href="https://webpack.js.org/configuration/performance/">performance hints</a>. There are two hints available to us: <code>performance.maxEntrypointSize</code> and <code>performance.maxAssetSize</code>. <code>performance.maxEntrypointSize</code> lets us set a limit for all webpack produced assets for a given route. <code>performance.maxAssetSize</code> lets us set a limit for any individual webpack produced assets.</p>
  104. <p>By default, the hints are just that—hints. They show up as warnings but don’t do anything concrete. You can change that by setting the <code>peformance.hints</code> property to <code>error</code>.</p>
  105. <p>So, given the following configuration, webpack would throw errors whenever an individual asset exceeds 100kB or all total assets for a given route exceed 150kB.</p>
  106. <div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span>module</span>.<span>exports</span> <span>=</span> {
  107. <span>//...
  108. </span><span/> <span>performance</span><span>:</span> {
  109. <span>hints</span><span>:</span> <span>'error'</span>,
  110. <span>maxEntrypointSize</span><span>:</span> <span>100000</span>,
  111. <span>maxAssetSize</span><span>:</span> <span>150000</span>
  112. }
  113. };
  114. </code></pre></div>
  115. <figure class="banner">
  116. <img src="https://timkadlec.com/images/friction-webpack.png" alt="webpack&amp;rsquo;s performance hints let us throw errors if individual assets are too large, or if all assets for a given route get too heavy."/> <figcaption>
  117. <p>webpack’s performance hints let us throw errors if individual assets are too large, or if all assets for a given route get too heavy.</p>
  118. </figcaption>
  119. </figure>
  120. <p>If you’re not using webpack, or if you are and still want to augment these hints, we can also introduce some bundle size checking at the pull request or deploy levels. <a href="https://github.com/siddharthkp/bundlesize">Bundlesize</a> is a common choice here.</p>
  121. <p>With Bundlesize, we setup maximum sizes for each bundle we want to track. Then we can run Bundlesize against those limits on every pull request or during our continuous integration process to stop us from deploying if any of those bundle sizes have been exceeded.</p>
  122. <figure class="banner">
  123. <img src="https://timkadlec.com/images/friction-bundlesize.png" alt="Bundlesize will check each bundle against the limits we set so that we can break the build if any of those limits are exceeded."/> <figcaption>
  124. <p>Bundlesize will check each bundle against the limits we set so that we can break the build if any of those limits are exceeded.</p>
  125. </figcaption>
  126. </figure>
  127. <h2 id="building-with-friction">Building with friction</h2>
  128. <p>Healthy friction in our processes, paired with automation and reporting where appropriate, can have a substantial impact on what we ship. When we force ourselves to take these moments to consider the implications of what we’re about to add to our codebase, when we make it hard to add more bloat to our applications by default, we not only change the way we build, but we change the way we <em>think</em> about building. It’s the observer effect applied to the way we code.</p>
  129. <p>When we have to consider the weight of every module we add to our project (or which vulnerabilities are included or what accessibility concerns they bring along), we start to inherently pay a little more attention to at least a part of performance every single day. It won’t magically fix all our performance woes by itself, but it certainly gets us pointed in the right direction.</p>
  130. </main>
  131. </article>
  132. <hr>
  133. <footer>
  134. <p>
  135. <a href="/david/" title="Aller à l’accueil">🏠</a> •
  136. <a href="/david/log/" title="Accès au flux RSS">🤖</a> •
  137. <a href="http://larlet.com" title="Go to my English profile" data-instant>🇨🇦</a> •
  138. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">📮</a> •
  139. <abbr title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340">🧚</abbr>
  140. </p>
  141. <template id="theme-selector">
  142. <form>
  143. <fieldset>
  144. <legend>Thème</legend>
  145. <label>
  146. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  147. </label>
  148. <label>
  149. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  150. </label>
  151. <label>
  152. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  153. </label>
  154. </fieldset>
  155. </form>
  156. </template>
  157. </footer>
  158. <script type="text/javascript">
  159. function loadThemeForm(templateName) {
  160. const themeSelectorTemplate = document.querySelector(templateName)
  161. const form = themeSelectorTemplate.content.firstElementChild
  162. themeSelectorTemplate.replaceWith(form)
  163. form.addEventListener('change', (e) => {
  164. const chosenColorScheme = e.target.value
  165. localStorage.setItem('theme', chosenColorScheme)
  166. toggleTheme(chosenColorScheme)
  167. })
  168. const selectedTheme = localStorage.getItem('theme')
  169. if (selectedTheme && selectedTheme !== 'undefined') {
  170. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  171. }
  172. }
  173. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  174. window.addEventListener('load', () => {
  175. let hasDarkRules = false
  176. for (const styleSheet of Array.from(document.styleSheets)) {
  177. let mediaRules = []
  178. for (const cssRule of styleSheet.cssRules) {
  179. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  180. continue
  181. }
  182. // WARNING: Safari does not have/supports `conditionText`.
  183. if (cssRule.conditionText) {
  184. if (cssRule.conditionText !== prefersColorSchemeDark) {
  185. continue
  186. }
  187. } else {
  188. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  189. continue
  190. }
  191. }
  192. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  193. }
  194. // WARNING: do not try to insert a Rule to a styleSheet you are
  195. // currently iterating on, otherwise the browser will be stuck
  196. // in a infinite loop…
  197. for (const mediaRule of mediaRules) {
  198. styleSheet.insertRule(mediaRule.cssText)
  199. hasDarkRules = true
  200. }
  201. }
  202. if (hasDarkRules) {
  203. loadThemeForm('#theme-selector')
  204. }
  205. })
  206. </script>
  207. </body>
  208. </html>