<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hotjar</title>
    <description>The latest articles on Forem by Hotjar (@hotjar).</description>
    <link>https://forem.com/hotjar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F4124%2F854029bc-046c-4a5a-9805-72fdaefed12a.jpeg</url>
      <title>Forem: Hotjar</title>
      <link>https://forem.com/hotjar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hotjar"/>
    <language>en</language>
    <item>
      <title>Temporal Dead Zone - the less obvious part and how to avoid it</title>
      <dc:creator>Eryk Napierała</dc:creator>
      <pubDate>Thu, 02 Mar 2023 10:33:06 +0000</pubDate>
      <link>https://forem.com/hotjar/temporal-dead-zone-the-less-obvious-part-and-how-to-avoid-it-3k56</link>
      <guid>https://forem.com/hotjar/temporal-dead-zone-the-less-obvious-part-and-how-to-avoid-it-3k56</guid>
      <description>&lt;p&gt;&lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt; bindings in JavaScript have a property called Temporal Dead Zone (TDZ). It’s a deadly trap indeed, and often not easy to spot. I learned this the hard way some time ago when working on JavaScript bundle optimization at Hotjar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Temporal Dead Zone?
&lt;/h2&gt;

&lt;p&gt;According to MDN:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;code&gt;let&lt;/code&gt; or &lt;code&gt;const&lt;/code&gt; variable is said to be in a ‘Temporal Dead Zone’ from the start of the block until code execution reaches the line where the variable is declared and initialized.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// TDZ starts at beginning of scope&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// undefined&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// End of TDZ (for foo)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, this is obvious—you cannot access a constant before you declare it. But nobody writes code like this!, you say. Moreover, ESLint will yell at you if you try. What’s the big deal, then? &lt;/p&gt;

&lt;p&gt;Consider a more complex example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recurse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recurse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// throws an error, it's a TDZ for recurse&lt;/span&gt;
    &lt;span class="nf"&gt;recurse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expressions in JavaScript are evaluated in a certain order. Assigning a value is an expression evaluated from right to left. That’s why &lt;code&gt;const a = 1 + 2&lt;/code&gt; equals &lt;code&gt;a === 3&lt;/code&gt;. So, in the example above, first, the IIFE is evaluated, then the recurse name is initialized. That makes the usage of recurse inside the IIFE forbidden. Even if the name can be seen earlier in the code, from the program evaluation flow, it’s no go. But, again, nobody writes code like this, right? And ESLint is smart enough to warn you if you try. What’s your problem, dude?&lt;/p&gt;

&lt;h2&gt;
  
  
  When ESLint can’t help you
&lt;/h2&gt;

&lt;p&gt;ESLint analyzes the code statically. It’s smart, but because it doesn’t understand all the nuances of the runtime flow, a code like the one below is perfectly valid for the checker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callCallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Returned function called!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;returned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;callCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;returned&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// throws an error, it's a TDZ for returned&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A real-life example
&lt;/h2&gt;

&lt;p&gt;It all seems made-up: do we use patterns like this in our codebase? It turns out they’re rare, but they’re there. And they look completely innocent. &lt;/p&gt;

&lt;p&gt;Take a look at what I found in Hotjar’s codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code may feel illogical, but the &lt;code&gt;subscribe&lt;/code&gt; method is a bit more complex than the &lt;code&gt;callCallback&lt;/code&gt; you could see above. It calls the callback asynchronously most of the time, but in certain cases, it does it synchronously. And this leads to a runtime error about an uninitialized variable when you try to unsubscribe inside the callback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56juotydyt8gh2h6w94x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F56juotydyt8gh2h6w94x.png" alt="A screenshot from browser DevTools showing an error trigger when a binding is accessed while in TDZ" width="697" height="56"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  But… I’ve never encountered such an error
&lt;/h2&gt;

&lt;p&gt;How could a code like this even slip into the main branch? It’s broken, and loading it in the browser or running a test would detect it immediately, right? It would—if we actually used &lt;code&gt;const&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt; bindings. But for a long time, this wasn’t the case at Hotjar, and we didn’t realize it!&lt;/p&gt;

&lt;p&gt;Despite this syntax having been the norm here for years now, with several  such bindings present in our codebase, so far, all of them have been transpiled to &lt;code&gt;var&lt;/code&gt; before they reached the browser. For these, hoisting mechanism kicks in and silently evaluates the uninitialized binding to &lt;code&gt;undefined&lt;/code&gt;. If you’ve only read ‘JavaScript: The Good Parts’, you may even forget this mechanism exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funuek4s6zn9pz54iamml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funuek4s6zn9pz54iamml.png" alt="Illustration of two books - JavaScript: The Good Parts and JavaScript: The Definitive Guide - put on top of each other" width="500" height="299"&gt;&lt;/a&gt;&lt;br&gt;
(&lt;em&gt;image source:&lt;/em&gt; &lt;a href="https://blog.klipse.tech/javascript/2016/09/21/valueOf-js.html" rel="noopener noreferrer"&gt;https://blog.klipse.tech/javascript/2016/09/21/valueOf-js.html&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;At Hotjar, the transpilation step was there because we were supporting IE 11 and other ‘odd, old browsers’ that didn’t understand the &lt;code&gt;const&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt; syntax. It was replaced with &lt;code&gt;var&lt;/code&gt; on CI (via Babel), in a way engineers working in product squads weren’t noticing. Noone could see errors like the one above when testing the application.&lt;/p&gt;

&lt;p&gt;Things changed, though. Last year we officially dropped support for IE 11 in our product, which opened possibilities for dropping some compilation steps. It not only made our CI and development environment faster, but also had benefits for customers. ES6+ code is more terse, thus takes less time to load. The side-effect is that &lt;code&gt;const&lt;/code&gt;s and &lt;code&gt;let&lt;/code&gt;s are directly executed in the browser. So now, Hotjar engineers can, and certainly will, spot these errors occasionally. They’ve always been there, but hidden!&lt;/p&gt;
&lt;h2&gt;
  
  
  How to detect TDZ access
&lt;/h2&gt;

&lt;p&gt;In the example above, failing E2E (we use Cypress) tests for some areas of our codebase uncovered the issue. This was great, but also strange (and a bit concerning) at the same time: no other check—linter, type check, or unit tests—failed. This proves that some mistakes can be only spotted in runtime. Different test types are complementary and cannot replace each other.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to prevent TDZ access
&lt;/h2&gt;

&lt;p&gt;Use let, whenever there is a risk of accessing a binding before its initialization ends.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// first, initialize empty let binding with null&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ReturnType&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// then, assign it &lt;/span&gt;
&lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is now even clearer and more explicit. Looking at the original version, you might not understand why we need to check if the &lt;code&gt;unsubscribe&lt;/code&gt; isn’t empty before calling it. After all, its type, according to TypeScript, would always be &lt;code&gt;() =&amp;gt; void&lt;/code&gt;, no? But as we’ve proven, this wasn’t true. After switching to &lt;code&gt;let&lt;/code&gt;, it’s obvious that, in some particular moment, this name could contain &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Watch out for Temporal Dead Zones even if ESLint doesn’t warn you! Write tests, including E2E ones. And make sure you’re&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvknr4dgsxtbz0plry5qk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvknr4dgsxtbz0plry5qk.png" alt="🎶 Stayin’ alive 🎶 - a fragment of " width="480" height="350"&gt;&lt;/a&gt;&lt;br&gt;
(&lt;em&gt;image source:&lt;/em&gt; &lt;a href="https://giphy.com" rel="noopener noreferrer"&gt;https://giphy.com&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Some funky parts of Typescript</title>
      <dc:creator>Conrad Holtzhausen</dc:creator>
      <pubDate>Tue, 21 Feb 2023 09:24:11 +0000</pubDate>
      <link>https://forem.com/hotjar/some-funky-parts-of-typescript-1bh8</link>
      <guid>https://forem.com/hotjar/some-funky-parts-of-typescript-1bh8</guid>
      <description>&lt;p&gt;And by funky I don’t mean like the smell of chunky milk. I mean funky like the &lt;a href="https://www.youtube.com/watch?v=OAC4ItP0xWM"&gt;sweet music&lt;/a&gt; 💃.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.hotjar.com/"&gt;Hotjar&lt;/a&gt;, we have bi-weekly Typescript pair programming sessions where we try and solve a few of the &lt;a href="https://github.com/type-challenges/type-challenges"&gt;type challenges&lt;/a&gt; together. In one session after solving the challenges we set out to, we chose a &lt;a href="https://github.com/type-challenges/type-challenges/blob/main/questions/00020-medium-promise-all/README.md"&gt;random challenge&lt;/a&gt; as a last hurrah and boy was it a doozy.&lt;/p&gt;

&lt;p&gt;So why are you reading this? Well let me tell you what you can expect if you take the red pill with this &lt;strong&gt;TL;DR&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First I will take you down the rabbit hole we went on together in the session until we ran out of time.&lt;/li&gt;
&lt;li&gt;Secondly, I will take you deeper down the same rabbit hole I went on alone the day after.&lt;/li&gt;
&lt;li&gt;I will then finally share the solution that I shamelessly looked at after being thoroughly defeated by the challenge&lt;/li&gt;
&lt;li&gt;The main reason I share all this with you is that it contains some interesting nuggets on how the type system works underneath.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The challenge
&lt;/h3&gt;

&lt;p&gt;As I mentioned already, we chose to do the &lt;a href="https://github.com/type-challenges/type-challenges/blob/main/questions/00020-medium-promise-all/README.md"&gt;Promise.all &lt;/a&gt; challenge. Basically this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Type the function &lt;code&gt;PromiseAll&lt;/code&gt; that accepts an array of PromiseLike objects, the returning value should be &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt; where &lt;code&gt;T&lt;/code&gt; is the resolved result array.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// when passing this array&lt;/span&gt;
&lt;span class="nx"&gt;PromiseAll&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// PromiseAll's return type should be this&lt;/span&gt;
&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Recursion
&lt;/h3&gt;

&lt;p&gt;Our first thought was to recursively go through the passed array and return the awaited value of each for the result. Something like &lt;a href="https://www.typescriptlang.org/play?#code/PQKgBA+l09YJoHsCuAnMBhRATApmACV1X1jKjBGAChqAXATwAd8BBAdwEMBLO3bAAqpEAW24BnXKwA20gEq5xyaXQA8AFTC4AHnwB22cWBKdsiPdIZhOehgG0AugD4wAXjCad+w2Dvc9AGbEHgBi3KjidAA0YAB08f5B6OoKkQ5g1GBgAPy+HDx82BphEXROMfGx+bz8QqISUrKpymopimXpmWAAXL6V1YUaTg4A3LR4AMbSnCRgAch6E3Tc5mB1YpIy0hpaurgGRiZmFlY29s4AFABunNLIir3qAJS96w2qA7XCG43yii1DJxjaigSDkWAedqYTiSIzgiFUajcERMRCoOhgRgsMAAbzAAFEAI7IW4xfHaFhLMAAXzm3zAAHIAAJY3AAWgmAAtbtJ9gBzRTAZDLaTiBm0CbmSJgJjfBpbdTtACMbjWcs2sgudiVMQATDEAMzpGFgSV6SJPahm6Wy+oa6SKyK61Vve1anVgfVqu24WIkcSIaRXXAXA1PY1Ga10S1RmXq36OugGl3xrbuvUxV2+-2B4Oh8MxqUY20-BXtAAsKZ9Ww+qFQnAYqj0yBEACNggAfb0-Jst9uoJyD9Oew0OS30Zj4CYwxSquxdcmUtREknbVmIAJx6uyRMerOqbUZsBGwflBcU3BLVQr26qdebkvynftL37w8jsDNtvEZyDqLnpdr2JW97y3Utn0iA1M3jA8v37GI4OIBC+x-U9-yyRdL2XYC10nDcwKfB0K2gn1e2-VBHDQ6gHGoIA"&gt;this&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PromiseAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AwaitedPromiseAllResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s break this down:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Introduce a generic &lt;code&gt;T&lt;/code&gt; with a &lt;code&gt;any[]&lt;/code&gt; constraint that we can manipulate&lt;/li&gt;
&lt;li&gt;Assign &lt;code&gt;values&lt;/code&gt; to &lt;code&gt;T&lt;/code&gt; and wrap the result of &lt;code&gt;T&lt;/code&gt; in our custom &lt;code&gt;AwaitedPromiseAllResult&lt;/code&gt; type&lt;/li&gt;
&lt;li&gt;Within &lt;code&gt;AwaitedPromiseAllResult&lt;/code&gt; we do the following
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// spread T so we can get the first item in the array and the rest&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AwaitedPromiseAllResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;TFirst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;TRest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
 &lt;span class="c1"&gt;// Unwrap the promise on the first item and repeat this process for the rest&lt;/span&gt;
 &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Awaited&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TFirst&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;AwaitedPromiseAllResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TRest&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; 
 &lt;span class="c1"&gt;// when there is only one item left in the array, unwrap it and you are done&lt;/span&gt;
 &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;Awaited&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When testing our &lt;code&gt;AwaitedPromiseAllResult&lt;/code&gt; type separately it seemed to work:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bkHr-Lna--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8n8pscq9kk6bl8kxim6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bkHr-Lna--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8n8pscq9kk6bl8kxim6.png" alt="Result when testing AwaitedPromiseAllResult in isolation" width="690" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;but when using the type in the function we get a whole different result… why, Why, WHY 😡&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5cBXBzJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pp6eiax0mf9l3z4i4obs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5cBXBzJv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pp6eiax0mf9l3z4i4obs.png" alt="Result when testing PromiseAll function" width="690" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Here is why:
&lt;/h4&gt;

&lt;p&gt;Note that we constrain our generic as an &lt;code&gt;any[]&lt;/code&gt; and then assign our &lt;code&gt;values&lt;/code&gt; param as that. In other words &lt;code&gt;[1, 2, Promise&amp;lt;3&amp;gt;]&lt;/code&gt; would become &lt;code&gt;number | Promise&amp;lt;number&amp;gt;[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Okay, that makes sense. So what if we spread the &lt;code&gt;any[]&lt;/code&gt; as the &lt;code&gt;values&lt;/code&gt; type instead? &lt;a href="https://www.typescriptlang.org/play?#code/PQKgBA+l09YJoHsCuAnMBhRATApmACV1X1jKjBGAChqAXATwAd8BBAdwEMBLO3bAAqpEAW24BnXKwA20gEq5xyaXQA8AFTC4AHnwB22cWBKdsiPdIZhOehgG0AugD4wAXjCad+w2Dvc9AGbEHgBi3KjidAA0YAB08f5B6OoKkQ5g1GBgAPy+HDx82BphEXROMfGx+bz8QqISUrKpymopimXpmWAAXL6V1YUaTg4A3LTUeADG0pwkYAHIepN03OZgdWKSMtIaWrq4BkYmZhZWNvbOABQAbpzSyIq9x+aWffHqDgCUvRsNqgO1YSbRryRQtIZOMbUUCQciwDztTCcSRGOHwqjUbgiJiIVB0MCMFhgADeYAAogBHZB3GJk7QsZZgAC+8yBYAA5AABQm4AC0kwAFndpAcAOaKYDIFbScTs2iTcyRMBMIENbbqdoARjc61VW1klzsmpiACYYgBmdLIsAKvSRT7UW1KlX1fXSDWRE0635uw3GsBm3Wu3CxEjiRDSa64S7mz5WoxOugOxPKvUgj10c3etPbP2mmI+kNhiNRmNx5OK-Eu4Hq9oAFmzwe2-1QqE4DFUemQIgARsEAD5B4Gd7t91BOCd5gMWr60Hk25GKHV2Lp0hlqSnUnY8xABVNN2QZ-2F1RG-NgS0T8qr+m4ZaqTd3VQ7vfVtWH9qBk9n6dgLu94hnAnKIb3XB8qSfF99xrD9InNAs01Pf8xxiZDiFQ0dAKvECsjXO8Nwg7dmFwXdoPfd16wQ4MRwA1BHGw6gHGoIA"&gt;Well&lt;/a&gt; 🙂...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PromiseAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gN_Ea_ia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sx8oobeehhb3ogxx79dy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gN_Ea_ia--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sx8oobeehhb3ogxx79dy.png" alt="Spread type as a tuple" width="690" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q5jjo8Vs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6g4nnn391ampxb1m2a0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q5jjo8Vs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6g4nnn391ampxb1m2a0.gif" alt="Yes" width="200" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Awesome that works, but what happens if I override the generic constraint with something else?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9HyuUMP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1p6uk87wnzned452bb06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9HyuUMP8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1p6uk87wnzned452bb06.png" alt="Pass generic override" width="690" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fmcEnjaJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1uqw282zvcdm88dwwmok.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fmcEnjaJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1uqw282zvcdm88dwwmok.gif" alt="Flipping table" width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;This is where I gave up and looked at the solutions. All of them were basically a different flavour of the same thing. But before I just show you the solution allow me to stretch this post unnecessarily out a little more.&lt;/p&gt;

&lt;p&gt;We’ve come to the reason that I was implored to share this with you all. As you’ve seen in the previous section, by spreading an &lt;code&gt;any[]&lt;/code&gt; type you get a tuple. Which is an array of known types. And as you might know, an array is an object. Why is this important? Because Typescript is smart enough to treat it as such and allow you to map over the indexes of an array like you would the properties of a key/value pair object 🤯.&lt;/p&gt;

&lt;p&gt;Or in code words… &lt;a href="https://www.typescriptlang.org/play?#code/C4TwDgpgBAsghmAggJ2XEiDOB5ARgKwgGNgAVAewEkA7AEwgA8JMAeUqR4COzKOakAG0AugD4oAXigBvAFBQog0gGkIIKAEtqUANZryAMyilhALmOqQsgL6zZoSFC6Zgk2AhRoMOAsTJU6RmYWQQBGABooACZIgHJMcgBbCABaADc4ABsAVwhYsQBuIA"&gt;basically this&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MapArrayAsObjectToIndexes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;TKey&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;TKey&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2hlA5mz8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8t52gw27im7ozhjthqw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2hlA5mz8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x8t52gw27im7ozhjthqw.png" alt="demo of MapArrayAsObjectToIndexes result" width="690" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t5SanQL9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6p0wjh7uu490d1n2jvdg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t5SanQL9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6p0wjh7uu490d1n2jvdg.gif" alt="WTF" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yup 🙂&lt;/p&gt;

&lt;p&gt;And finally &lt;a href="https://www.typescriptlang.org/play?#code/PQKgBA+l09YJoHsCuAnMBhRATApmACV1X1jKjBGACg8BjAGwEMSwAzZAOzoBcBLRJzAAFVIgC2fAM64AggwYAeACphcADx65O2KWCacAngG0AugD4AFADcmDZLikAuMCSbZBDQ2GMA6f8qmAJQuohLSuIoA3tRgPgDWYHxC8biGiGxggS6yAO5MfFrYKsbxFtQAvuZg1NSgkOSwWY48mEwyeo1NVNR84gAOiKitPIb9+FFgAKIAjsh2ADTT6uO8YBXsYuJgAOQAAqPjALR0ABZ2DNoA5o7AyPwMUju1dIJSrf1bEfIMyi0AjGAALwiL4yH6WYz-JYAJiWAGZTPo9K9OO8gtRUe8wJ9wuCFH93jDgaC8XIFJDoWA4aTJDJfCQpIgGNZcJZ4UEke0wFieBjeTiweTfi14SSwnThZTYUsJREGY5maz2Zz+W8PkKfoSeAAWcWahSKWSoVBMQyKTjIcQAI2IYAAPrSIharbbUOYPdLqQjgrVDvg6O1HCTjLFlqseIpZvMlP6MoKyVqAbKhYooTKwIiPeYFmGpitcLwo3M7Io45lcZKk0SU2S01SaZabcQLB7c3F8xHizGy2NcPHK98CaLa5K0023UsJ8Qp66W9n2+HC5Ho6Xywmq8P3jrR87p6gzAvqKZqEA"&gt;the solution&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// as before use a generic `T` and convert it to a tuple for `values`'s type&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PromiseAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// map over the indexes of the array and return their unwrapped values &lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="nx"&gt;Awaited&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Typescript is fun to play with. And I know a lot of people might have already known all the things that I wrote about, but if there were only one or two people who found this interesting I will feel satisfied. But the bigger reason I shared this is to encourage you to play around. Understanding how Typescript's type system works has greatly benefited our day-to-day work even though we don't implement things in this way directly. So have fun and keep learning.&lt;/p&gt;

</description>
      <category>typescript</category>
    </item>
    <item>
      <title>Prioritization: you don’t need a framework, just frame your work</title>
      <dc:creator>Joshua Ordehi</dc:creator>
      <pubDate>Thu, 02 Feb 2023 13:16:33 +0000</pubDate>
      <link>https://forem.com/hotjar/prioritization-you-dont-need-a-framework-just-frame-your-work-1bn0</link>
      <guid>https://forem.com/hotjar/prioritization-you-dont-need-a-framework-just-frame-your-work-1bn0</guid>
      <description>&lt;p&gt;This is the first of what may become a series of posts about &lt;strong&gt;goal-setting, prioritization, and teamwork&lt;/strong&gt;. During my time at Hotjar, I’ve realized the need to implement better practices for these fundamental aspects of life and work. Sharing a few of my learnings (and failures) is an excellent way to pay it forward and keep myself accountable. Let’s do this!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR: often, doing less is more.&lt;/strong&gt; Challenge yourself to go back to basics, prioritizing urgent and important tasks every morning to defog your mind before you dive into your day. Use the &lt;a href="https://www.eisenhower.me/eisenhower-matrix/" rel="noopener noreferrer"&gt;Eisenhower Matrix&lt;/a&gt; or a similar method to surface critical tasks and shed the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;I’m setting a challenge for myself and anyone else who’s interested in trying it out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Over the next month, choose a productivity strategy that makes sense for you (I settled on the &lt;a href="https://jamesclear.com/eisenhower-box" rel="noopener noreferrer"&gt;Eisenhower Matrix&lt;/a&gt;.) If you already have an approach, share how you use it and how it helps you.&lt;/li&gt;
&lt;li&gt;Prioritize your tasks at the &lt;em&gt;beginning&lt;/em&gt; of the day, before any work. Pick tasks you’ll realistically complete today or tomorrow, at the latest, and think hard about what’s worth doing first.&lt;/li&gt;
&lt;li&gt;If you commit to this challenge, drop us a comment. We’ll catch up again in a month to share our experiences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nice to have:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep tasks to a minimum, stay within eight per quadrant, and, ideally, don’t exceed five&lt;/li&gt;
&lt;li&gt;Only spend up to 15 minutes prioritizing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Growing pains
&lt;/h2&gt;

&lt;p&gt;As companies grow to the point where people don’t know how to pronounce each other’s names anymore, something must unify our efforts toward a common north. Otherwise, we run the risk of rowing in as many directions as there are teams or even contributors!&lt;/p&gt;

&lt;p&gt;I find it helpful to go back to the essence of a concept when I want to improve my approach to it: by definition, a team works together toward a common goal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you’re working on something that doesn’t contribute to a goal that’s commonly accepted by a group of people, you’re not working in a team&lt;/strong&gt;—even if you’re under the name of one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Essential aspects of good teamwork:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal-setting:&lt;/strong&gt; how else can we make sense of our work?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular alignment:&lt;/strong&gt; this is to ensure our work approaches those goals and each teammate is accountable for tasks that best suit their abilities and needs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task tracking:&lt;/strong&gt; everyone gets a task list they’re accountable for completing, which is visible to everyone else and scored for priority&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These measures also help avoid burnout—you’ll notice when someone has too much on their plate, is blocked, or if their productivity has suddenly diminished, all of which can prompt you to provide timely assistance.&lt;/p&gt;

&lt;p&gt;I’ve been researching goal-setting and prioritization frameworks recently, familiar concepts like SMART, RICE, and SWOT. I mean, &lt;a href="https://www.productplan.com/learn/product-management-frameworks/" rel="noopener noreferrer"&gt;there are a lot out there!&lt;/a&gt;. But one trend I noticed is that many of these approaches need to be simplified if a team wants to reduce time to success and maximize productivity.&lt;/p&gt;

&lt;p&gt;Do you know how hard it is to get people out of their particular habits, ways of working, and day-to-day idiosyncrasies? It's not fun. So how can I get them to eat the RICE when they need to plug in their pressure cooker first, or learn how to operate it?&lt;/p&gt;

&lt;p&gt;That’s why I’m going back to basics, starting with the simplest, most straightforward approach: prioritizing daily work. &lt;strong&gt;Rather than using a framework, I’m framing priorities before I work.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frame priorities before work
&lt;/h2&gt;

&lt;p&gt;Before you do anything else, get in the right mindset every day. Ask yourself something straightforward, for instance:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What’s urgent, and what do I need to do now?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I try to use this mental model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Time-sensitive &amp;gt; Multiple categories of users/teams impacted &amp;gt; Users impacted &amp;gt; My department impacted &amp;gt; My team impacted &amp;gt; Only myself impacted&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have less reason to prioritize a task the further I move right when there’s work in a category to the left.&lt;/p&gt;

&lt;p&gt;As you set tasks, ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Is this really what I should be doing now? Why?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Notice how this approach is still influenced by goals.&lt;/strong&gt; While you should ideally have planned your goals before prioritizing your work, my aim is to simplify organizing tasks, so it’s easier for me to shed the deadwood later. &lt;/p&gt;

&lt;p&gt;In other words, rather than asking you to make sudden changes to your routine and set new goals, which you’re less likely to do, I’m proposing you keep your pace but clear some of the fog by adding organization and clarity to your schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Urgency vs. importance
&lt;/h3&gt;

&lt;p&gt;First, stop fooling yourself that every fantastic idea that comes to mind is urgent or important. We like to feel productive and to keep ourselves busy—ideally visibly so. I like this &lt;a href="https://jamesclear.com/eisenhower-box" rel="noopener noreferrer"&gt;quote by Tim Ferris&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Being busy is a form of laziness—lazy thinking and indiscriminate action.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The bottom line is that you want to be relaxed, focused, and working on the most impactful initiatives daily. &lt;a href="https://www.mohannadali.com/p/youre-probably-doing-prioritization" rel="noopener noreferrer"&gt;In the words of our very own Mo&lt;/a&gt;, Hotjar’s CEO:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Everyone, working on the most important things, every day.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mo also says, &lt;em&gt;“If everything is important, then nothing is.”&lt;/em&gt; We need to differentiate between &lt;strong&gt;urgent, important&lt;/strong&gt;, and stuff we just shouldn’t do. As an aid, you can use a method like the Eisenhower Matrix, as I am:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foebstokmtq3a9nphgnkf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foebstokmtq3a9nphgnkf.png" alt="Eisenhower Matrix" width="800" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Eisenhower Matrix (&lt;a href="https://hypercontext.com/blog/management-skills/how-to-prioritize-tasks" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Do first&lt;/strong&gt;: tasks that are important for your life and career and need to be done today or tomorrow at the latest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt;: important tasks but less urgent—list tasks that you need to put in your calendar here&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delegate&lt;/strong&gt;: less important to you than they are to others, usually ad-hoc. Keep track of delegated tasks by email, phone, or within a meeting to check back on their progress later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t do&lt;/strong&gt;: initiatives that are unnecessary and unimportant, and you should not be doing them at all&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At its core, the Urgent vs. Important matrix, as it’s also called, tries to help you &lt;em&gt;finish&lt;/em&gt; tasks rather than collect them. Remember: you should only list tasks you will complete within the next couple of days or this week.&lt;/p&gt;

&lt;p&gt;Here are some templates for this matrix in some of the common project tools I’ve seen folks use here at Hotjar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://trello.com/templates/project-management/eisenhower-matrix-task-board-DZVysUiF" rel="noopener noreferrer"&gt;Trello&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.notion.so/templates/eisenhower-matrix" rel="noopener noreferrer"&gt;Notion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://asana.com/templates/eisenhower-matrix" rel="noopener noreferrer"&gt;Asana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://miro.com/templates/eisenhower-matrix/" rel="noopener noreferrer"&gt;Miro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://monday.com/templates/eisenhower-matrix" rel="noopener noreferrer"&gt;Monday&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.eisenhower.me/eisenhower-matrix-canvas/" rel="noopener noreferrer"&gt;A PDF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;In my next post, I’ll discuss how you can further differentiate between urgent and important tasks (such as the &lt;a href="https://www.briantracy.com/blog/time-management/the-abcde-list-technique-for-setting-priorities/" rel="noopener noreferrer"&gt;ABCDE method&lt;/a&gt;). I’ll also bring some learnings about how my usage of the Eisenhower Matrix helps or hinders me moving forward.&lt;/p&gt;

&lt;p&gt;I hope we can make one another more efficient and happier as we remove unnecessary weight from our days. Feel free to share your experiences and criticisms below. Developing more data-driven approaches is important, but first, we must defog what we already do every day.&lt;/p&gt;

</description>
      <category>cryptocurrency</category>
      <category>crypto</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>Monorepos - pnpm: a performant npm</title>
      <dc:creator>Mario Beltrán</dc:creator>
      <pubDate>Tue, 27 Dec 2022 17:00:00 +0000</pubDate>
      <link>https://forem.com/hotjar/monorepos-with-pnpm-part-1-a-performant-package-manager-5g41</link>
      <guid>https://forem.com/hotjar/monorepos-with-pnpm-part-1-a-performant-package-manager-5g41</guid>
      <description>&lt;p&gt;By the end of 2020, &lt;a href="https://github.blog/2020-10-13-presenting-v7-0-0-of-the-npm-cli/" rel="noopener noreferrer"&gt;npm released the v7 of their package manager&lt;/a&gt;. The most noticeable change was that it now offered support for workspaces. That meant you could finally &lt;strong&gt;manage a monorepo with the Node built-in package manager—without the need for extra steps or tools&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At Hotjar, a platform that gives people insights into user and customer behavior, we decided to switch back from yarn to npm to simplify our tooling—so, npm would become our package manager again, now that it gave our team the ability to our team &lt;a href="https://dev.to/hotjar/monorepos-sharding-jest-in-ci-47bc"&gt;manage a monorepo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But, it didn’t take long for us to realize that it probably wasn’t the best decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is npm workspaces?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.npmjs.com/cli/v7/using-npm/workspaces" rel="noopener noreferrer"&gt;Npm workspaces&lt;/a&gt; provide the tooling you need to manage your dependencies in a monorepo&lt;/strong&gt;. The setup is pretty simple with a new property in your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace-example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"./packages/*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It automates the installation and linking process, and there’s a special &lt;code&gt;--workspace&lt;/code&gt; flag to run commands in the context of monorepo projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# example: run the tests for the workspace "foo"&lt;/span&gt;
npm run &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--workspace&lt;/span&gt; foo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Hotjar started using npm workspaces, we had less than 20 workspaces in our monorepo. Today, we have more than 50! &lt;/p&gt;

&lt;p&gt;So, we realized npm wasn’t a scalable monorepo tool—the hard way. Our CI started taking way too long (where we got to the point of surpassing our max average runtime), the &lt;code&gt;node_modules&lt;/code&gt; size got out of hand, and a simple npm install became a dense daily process. We actually weren’t adding that many dependencies to our monorepo, but working with the workspaces and installing their dependencies became tiring and tedious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For reference&lt;/strong&gt;: our &lt;code&gt;node_modules&lt;/code&gt; size took 3.2GB of space disk, and installing the dependencies on CI took around 4 minutes. This wasn’t desirable at all, so we started digging into how npm CLI works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The disadvantages of npm
&lt;/h2&gt;

&lt;p&gt;Npm docs are great in general. However, when it comes to understanding how their CLI works under the hood, you’re going in blind. After many undesirable hours of research, we came to the conclusion that the npm workspace was a great approach for simple monorepos, but not for massive repos like ours at Hotjar. Here’s why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Npm holds a copy of a dependency every time it’s installed&lt;/strong&gt;: if we have 100 projects using a dependency, we’ll have 100 copies of that dependency saved on disk. This doesn’t work exactly like that for monorepos, since npm can dedupe dependencies with the same version, but it’ll still duplicate some sub-dependencies with different versions across the dependencies tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Npm has blocking stages of installation&lt;/strong&gt;: for example, it can’t parallelize the installation, so each dependency follows the Resolving → Fetching → Writing flow, and each dependency must finish one stage before we can start another&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Npm doesn’t have a proper mechanism to realize a dependency is another workspace from your monorepo&lt;/strong&gt;: this means that every time you want to install your dependencies, npm will request such a dependency to the registry, then will try to link it locally when no dependency was found there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Npm hoists dependencies you install to the root of the modules directory&lt;/strong&gt;: as a result, the source code has access to dependencies that are not added as dependencies to the project. This is known as ‘Phantom Dependencies’ and might break your dependencies or code in several unexpected ways.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A performant npm
&lt;/h2&gt;

&lt;p&gt;The way npm works makes it complicated (if not impossible) to escalate properly for workspaces, causing our dependencies process to slow down, and become unmanageable and unsafe. That’s why Hotjar needed to find a faster, lighter, and safer alternative to npm that also scaled properly for our monorepo—so adding more workspaces didn’t degrade the whole dependencies installation process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;‘Performant npm’ (Pnpm)&lt;/a&gt;, which is sponsored by Vercel and Prisma and is making a name for itself within the Node ecosystem, is an alternative to npm that caught Hotjar’s special attention. With its promise of being  ‘fast, efficient, strict, and supporting monorepos’, it looked like the perfect package manager candidate.&lt;/p&gt;

&lt;p&gt;You can see a &lt;a href="https://pnpm.io/benchmarks" rel="noopener noreferrer"&gt;full benchmark comparison of JavaScript package managers here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Switching to fast monorepos
&lt;/h2&gt;

&lt;p&gt;After running some tests, we estimated that the improvement in terms of installation, speed, and disk space usage would be massive. It was time to leave npm behind again, so we got down to work. We spent a few weeks working on this migration, and then finally made the switch to pnpm.&lt;/p&gt;

&lt;p&gt;The setup is quite simple too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pnpm-workspace.yaml&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;packages/*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The whole installation and linking are intelligently automated, and you can run all commands in the context of workspaces with the &lt;code&gt;--filter&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# example: run the tests for the workspace "foo"&lt;/span&gt;
pnpm &lt;span class="nt"&gt;--filter&lt;/span&gt; foo &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pnpm also provides advanced features like &lt;a href="https://pnpm.io/workspaces#workspace-protocol-workspace" rel="noopener noreferrer"&gt;workspace protocol&lt;/a&gt;, or doing partial installs for your workspaces, giving you granular control over your monorepo.&lt;/p&gt;

&lt;p&gt;However, the best improvement came in the form of performance. Let’s take a look at what changed after Hotjar made the switch:&lt;/p&gt;

&lt;h3&gt;
  
  
  Disk space comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NPM (before)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;PNPM (after)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Δ delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker image size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4.08GB&lt;/td&gt;
&lt;td&gt;2.54GB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;37% ⬇️&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;node_modules size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.2GB&lt;/td&gt;
&lt;td&gt;1.3GB&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;59%&lt;/strong&gt; ⬇️&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Installation time comparison (in local dev env)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;NPM (before)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;PNPM (after)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Δ delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;without cache, without node_modules&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4min 50sec&lt;/td&gt;
&lt;td&gt;1min 30sec&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;69%&lt;/strong&gt; ⬇️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;with cache, without node_modules&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5min&lt;/td&gt;
&lt;td&gt;48sec&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;84%&lt;/strong&gt; ⬇️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;with cache and node_modules&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1min&lt;/td&gt;
&lt;td&gt;8sec&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;86%&lt;/strong&gt; ⬇️&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The result? We eventually got our CI back into its average runtime.&lt;/p&gt;




&lt;p&gt;We couldn’t be happier about pnpm and can’t recommend it enough as we continue to use it—&lt;strong&gt;it makes your monorepo scalable and maintainable without adding any complexity&lt;/strong&gt;. Even if you have a small or simple monorepo, you can take advantage of pnpm speed installation improvements. In our next post, we’ll talk about Phantom Dependencies and how they complicated our migration to pnpm.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@mak_jp?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Mak&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/8wy9mGgmGoU?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>Importance of Lazy Loading content</title>
      <dc:creator>Ryan Calleja</dc:creator>
      <pubDate>Mon, 12 Dec 2022 09:00:00 +0000</pubDate>
      <link>https://forem.com/hotjar/importance-of-lazy-loading-content-56ck</link>
      <guid>https://forem.com/hotjar/importance-of-lazy-loading-content-56ck</guid>
      <description>&lt;h2&gt;
  
  
  What is lazy loading?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lazy loading&lt;/strong&gt; or &lt;strong&gt;loading content on demand&lt;/strong&gt; is the process of identifying resources that are non-blocking to a website and delaying their loading or initialization until the page needs them. Common resources to lazy load include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bundle JS files&lt;/li&gt;
&lt;li&gt;Vendor or third-party JS files&lt;/li&gt;
&lt;li&gt;CSS files&lt;/li&gt;
&lt;li&gt;Images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcszch9q24zsfb6g6edti.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcszch9q24zsfb6g6edti.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Reused image from &lt;a href="https://stock.adobe.com/it/search?k=%22progress+bar%22" rel="noopener noreferrer"&gt;AdobeStock&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This article outlines &lt;strong&gt;the benefits of loading content on demand, how to do it, and when the technique should be applied&lt;/strong&gt;. We’ll also share how we use lazy loading at Hotjar. &lt;/p&gt;

&lt;p&gt;A quick caveat to manage expectations: in providing these tips, we assume all essential performance-related implementations already exist on your website and that, like us, your goal is to optimize page load time and &lt;a href="https://web.dev/lcp/" rel="noopener noreferrer"&gt;Largest Contentful Paint (LCP)&lt;/a&gt; further. That said, let’s dive in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why is lazy loading important?
&lt;/h2&gt;

&lt;p&gt;You’ve done everything right: content delivery network (CDN) set-up, file compression enablement, static JS and CSS file minification, and resource caching. You’ve been sure to follow good coding standards and promote the reusability of components. Now, you’re monitoring your website’s performance and hoping for the best.&lt;/p&gt;

&lt;p&gt;But even after all this work, your page still takes too long to load.&lt;/p&gt;

&lt;p&gt;You measure your loading time using a performance monitoring tool. As you suspected, it returns a poor score. But the problem is that none of your website resources can be removed—they're all crucial elements of your site in one way or another. &lt;/p&gt;

&lt;p&gt;This is when lazy loading comes in. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Freruci50jdfs9ss0hume.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Freruci50jdfs9ss0hume.jpg" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Reused image from &lt;a href="https://mobile.twitter.com/myhero" rel="noopener noreferrer"&gt;My Hero project&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Four key benefits of lazy loading
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Reducing the initial web page load time by reducing the total size of resources downloaded&lt;/li&gt;
&lt;li&gt;Conserving the user's bandwidth, especially keeping in mind mobile data plans on mobile devices&lt;/li&gt;
&lt;li&gt;Conserving system resources, as requests to the server are made only when needed&lt;/li&gt;
&lt;li&gt;Avoiding unnecessary code execution&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might be wondering: don’t I need all the resources included in our website? Is there anything we could load on demand?&lt;/p&gt;

&lt;p&gt;Answer: &lt;strong&gt;there's almost always something you don't need immediately.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start by identifying the level of importance of the resources. Important resources might not be ideal to lazy load, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Main JS Bundle&lt;/li&gt;
&lt;li&gt;Main CSS Styles&lt;/li&gt;
&lt;li&gt;Fonts &lt;/li&gt;
&lt;li&gt;Analytics packages that need to be available on the initial website load&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  When to implement lazy loading
&lt;/h2&gt;

&lt;p&gt;As your website grows, so does the final bundle of JS and CSS files. To use this technique effectively, you need to split the JS bundles and CSS files as much as possible. The traditionally bulky file holding JS, and another hefty file containing CSS, are no longer an option for the modern front-end tech world we live in. Once you’ve completed this critical step, you’re ready to reap the benefits of lazy loading.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seven scenarios where lazy loading is beneficial:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loading JS bundles that aren't used on the currently viewed page but are used on other pages of the website&lt;/li&gt;
&lt;li&gt;Loading JS components and any library backing them up immediately instead of when that component is viewable on the page&lt;/li&gt;
&lt;li&gt;Using a third-party JS library which is only required on a specific page. Such libraries can be for ready-made features, JS utility libraries, animations, analytics, or monitoring purposes.&lt;/li&gt;
&lt;li&gt;Loading CSS-style files for the entire website rather than loading only the style files needed for the viewable page&lt;/li&gt;
&lt;li&gt;Loading a collection of images that aren't visible to the user until the user scrolls to a particular part of the page&lt;/li&gt;
&lt;li&gt;Loading of a resource when a specific DOM event has been triggered, such as resize or click&lt;/li&gt;
&lt;li&gt;At a specific state within your website, such as after submitting a form and showing a submission success component that might be animation-heavy&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  How Hotjar uses lazy loading in a React application
&lt;/h2&gt;

&lt;p&gt;At Hotjar, we constantly strive to keep our website—our most important sales rep—at a solid performance score.&lt;/p&gt;

&lt;p&gt;Why do we care so much about performance? Because it results in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increased conversion rates&lt;/li&gt;
&lt;li&gt;More page views&lt;/li&gt;
&lt;li&gt;Higher search engine rankings&lt;/li&gt;
&lt;li&gt;Lower server bandwidth costs&lt;/li&gt;
&lt;li&gt;Smoother usability on different devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our website is a &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; application. Thus, we rely heavily on the ReactJS library and its available components. &lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;images&lt;/strong&gt;, we use the &lt;a href="https://nextjs.org/docs/api-reference/next/image" rel="noopener noreferrer"&gt;Next.js-provided&lt;/a&gt; component, which by default lazy loads automatically. This has meant we no longer have to worry that our website is suffering from overheads not displayed at that moment in time. Instead, images automatically load and display once the scroll position of a specific image reaches the browser's viewport.&lt;/p&gt;
&lt;h3&gt;
  
  
  Our milestone
&lt;/h3&gt;

&lt;p&gt;When analyzing our resources on page load, &lt;a href="https://www.npmjs.com/package/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;Webpack Bundle Analyzer&lt;/a&gt; helped us identify a specific external library used by our website that was adding an overhead of 67.3kB on every page refresh. As you can see below, Webpack Bundle Analyzer’s output reveals lottie.js as one of the large libraries immediately downloaded on our page load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2edq2507z9wc1dlarhem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2edq2507z9wc1dlarhem.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(We also discovered this library was only used at the bottom of the page, to render a nice animation once a subset of components was in the viewport.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Lazy loader implementation
&lt;/h2&gt;

&lt;p&gt;Our idea was to create a new component (called &lt;strong&gt;LazyLoadOnScroll&lt;/strong&gt;) to wrap other React components. The logic behind it uses a hook that benefits from the browser’s &lt;strong&gt;Observer&lt;/strong&gt; tools, specifically the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" rel="noopener noreferrer"&gt;IntersectionObserver&lt;/a&gt;. Any components wrapped with this won’t be rendered, and any underlying external files, libraries, or JS files won’t be downloaded. &lt;/p&gt;

&lt;p&gt;Once the user starts to scroll and this component is about to come into view, the  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" rel="noopener noreferrer"&gt;IntersectionObserver&lt;/a&gt; returns a value of 'true', meaning the component is about to intersect the viewport. At this point in time of scrolling, everything begins downloading, and React renders the component accordingly, making it appear to the user as if items are seamlessly being served on demand. &lt;/p&gt;

&lt;p&gt;**&lt;br&gt;
The new files introduced are the following:**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;useIntersectionObserver&lt;/strong&gt;: the hook with the responsibility of observing the provided component ref via an &lt;a href="[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)"&gt;IntersectionObserver&lt;/a&gt; instance to return 'true' or 'false' as to whether the ref has intersected the viewport. 
This can also be extended by providing additional config props, but our use case required only the most basic implementation. This also includes a check to make sure the browser supports &lt;a href="[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)"&gt;IntersectionObserver&lt;/a&gt;; otherwise, it will return 'true' to render the content immediately every time.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Providing a useRef to a component, this hook will return a value of true or false, based on whether
 * the ref prop, is visible in the viewport or not
 * @param ref
 * @returns {boolean}
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useIntersectionObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIntersecting&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supportsObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;IntersectionObserver&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;supportsObserver&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setIntersecting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setIntersecting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;useIntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LazyLoadOnScroll&lt;/strong&gt;: this is a React functional component that uses the &lt;strong&gt;useIntersectionObserver&lt;/strong&gt; hook. It contains a statement checking whether the hook is being run on a server. If it is on a server, Next.js ‘Server Side Renderer’ will present the page to the client. The hook always returns 'true' so as not to lazy load anything, and the full page is generated for SEO purposes. When this component is initiated, it sets the children to render once the hook returns 'true'. The process occurs once, and the component does nothing else but keep the children in the rendered document.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useIntersectionObserver&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;useIntersectionObserver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 *
 * @param props             Child JSX element
 * @returns {JSX.Element}
 * @constructor
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LazyLoadOnScroll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isNotBrowser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refIntersected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useIntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setVisible&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNotBrowser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refIntersected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refIntersected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;refIntersected&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Putting it into practice
&lt;/h2&gt;

&lt;p&gt;Using it is as simple as wrapping any component you want to lazy load. Plus, there’s no need to worry about extra overheads on page load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LazyLoadOnScroll&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;BigComponentToBeLazyLoaded&lt;/span&gt; &lt;span class="nx"&gt;prop1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="nx"&gt;prop2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="nx"&gt;etc&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/LazyLoadOnScroll&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The beauty of measuring
&lt;/h2&gt;

&lt;p&gt;For our performance measurement, we've used Chrome's devtool, &lt;a href="https://developer.chrome.com/docs/lighthouse/overview/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt;, to generate a report before and after implementation. Results were fascinating, and I’m really excited to share the &lt;strong&gt;Desktop&lt;/strong&gt; scores with you.&lt;/p&gt;

&lt;p&gt;From an overall score of &lt;strong&gt;87 on performance&lt;/strong&gt; and an &lt;strong&gt;&lt;a href="https://web.dev/lcp/" rel="noopener noreferrer"&gt;LCP&lt;/a&gt; of 1.9 seconds…&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpai36xj0fjua4rx0jyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpai36xj0fjua4rx0jyl.png" alt="Prior to lazy loading"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…we managed to go up to an overall score of &lt;strong&gt;93 on performance&lt;/strong&gt; and an &lt;strong&gt;&lt;a href="https://web.dev/lcp/" rel="noopener noreferrer"&gt;LCP&lt;/a&gt; of 1.5 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzf6bv5bao99fyb7hnkrr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzf6bv5bao99fyb7hnkrr.png" alt="Post lazy loading"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These results gave us the assurance that loading content on demand does impact our overall website experience. Applying it further to other corners of our site will ensure that no unnecessary overheads block our pages from loading as fast as possible—helping us give our users an experience they’ll love.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>5 easy steps for choosing the right goals</title>
      <dc:creator>Stephanie Mifsud</dc:creator>
      <pubDate>Mon, 03 Oct 2022 06:13:17 +0000</pubDate>
      <link>https://forem.com/hotjar/5-easy-steps-for-choosing-the-right-goals-1ej4</link>
      <guid>https://forem.com/hotjar/5-easy-steps-for-choosing-the-right-goals-1ej4</guid>
      <description>&lt;p&gt;Thinking about goals you want to achieve in six months can be challenging. Here are some questions and suggestions that can help you find the mental space for paving the way to a better version of yourself. &lt;/p&gt;

&lt;p&gt;At Hotjar we have performance reviews every six months. This process generates a lot of peer feedback on one’s strengths and career opportunities—plus it provides a space for thinking about future growth. &lt;a href="https://dev.to/susanamartins"&gt;Susanna&lt;/a&gt; has posted &lt;a href="https://dev.to/hotjar/best-self-review-how-to-process-feedback-in-an-actionable-way-1jai"&gt;some great tips&lt;/a&gt; on how to digest this feedback and make it actionable. As peer feedback tends to get more generic as one becomes more senior, this post outlines some resources that are helpful in specific goal setting for the next few months.&lt;/p&gt;

&lt;p&gt;I invite you to grab a cuppa and some pen and paper and follow these few steps to identify which goals matter for your career progression.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. [5m] Brain dump—what’s on your mind?
&lt;/h3&gt;

&lt;p&gt;Our mind is usually cluttered with thoughts about the present or near future. Let’s start by listing those thoughts so we can consider them later. &lt;/p&gt;

&lt;p&gt;Make a list of the things on your mind, things you have been postponing, things that worry you because they are close to their breaking point, pending follow-ups, etc. Write it all down. Now put that list aside for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. [15m] Tech laundry list—what needs to be done
&lt;/h3&gt;

&lt;p&gt;Write out (or type out) a laundry list of tasks/projects you think the team needs to work on in the near future. &lt;/p&gt;

&lt;p&gt;Think of it as a backlog of tech tasks to bring the code and infra to a better place where you can be proud of how well it serves our users. &lt;/p&gt;

&lt;p&gt;You might want to look into the team backlog for inspiration. If you have a North Star document for your team, now is a good time to resurrect it and see what steps you need to take to reach it. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. [20m] Focus on the destination — keep your eye on the prize
&lt;/h3&gt;

&lt;p&gt;Now it’s time to think about how you want to grow in your current role—or what you need to do to switch roles if you are aiming for that. &lt;/p&gt;

&lt;p&gt;Consider these questions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are your career aspirations? &lt;/li&gt;
&lt;li&gt;What do you want to know in a year that you don’t know now?&lt;/li&gt;
&lt;li&gt;What interests you? What articles/books do you read?&lt;/li&gt;
&lt;li&gt;Look at the seniority criteria for your current role and your aspired role. What skills do you need to hone to help your growth? &lt;/li&gt;
&lt;li&gt;What opportunities came out of your last performance review?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You guessed it, put all these responses in a list, we’ll need it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. [25m] Look for the opportunities — watch for blind spots
&lt;/h3&gt;

&lt;p&gt;After this introspective phase, it’s time to zoom out, to take into consideration higher-level goals. Different companies have different ways of communicating strategy and focus areas. Whether it’s KPIs, OKRs, KPAs or something else, dig up strategic guidance and investigate how it dribbles down to your team and role. &lt;/p&gt;

&lt;p&gt;There might also be more horizontal initiatives to become familiar with such as Chapter Group Projects, North Star Initiatives and Golden Path direction.&lt;/p&gt;

&lt;p&gt;Here you may see gaps that you are interested in filling, for example. suggesting a new chapter topic or pitching a new initiative for your team in alignment with the Department OKRs. These are excellent material for open-ended goals (see section 5)&lt;/p&gt;

&lt;h3&gt;
  
  
  5. [30m] Write your plan — pulling it all together
&lt;/h3&gt;

&lt;h4&gt;
  
  
  The plan
&lt;/h4&gt;

&lt;p&gt;The follow up of a performance review is a development plan. A development plan is how we  describe and track goals for the next six months.  It provides a basis for one-on-one conversations on growth and helps your lead highlight practical opportunities for reaching those goals.&lt;/p&gt;

&lt;p&gt;A development plan is in essence a set of personal OKRs. It includes a couple of broad goals — think of these as the areas you want to focus on. For each goal, we set two outcomes, where we describe the better situation we want to be in in a few months' time. These outcomes are split into specific actions that will lead to the improved situation we want to achieve.&lt;/p&gt;

&lt;h4&gt;
  
  
  Broad goals
&lt;/h4&gt;

&lt;p&gt;Take a look at your answers and ideas in numbers three and four. From this,  try to come up with two broad and open-ended goals.&lt;/p&gt;

&lt;p&gt;The best goals will overlap between what you want to achieve and what the company is trying to achieve. Try to find aligning themes, as it will increase your chances of success.&lt;/p&gt;

&lt;p&gt;Examples of broad open-ended goals&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identify opportunities for improvement/innovation in our area of ownership&lt;/li&gt;
&lt;li&gt;Improve knowledge sharing and collaboration around data visualization&lt;/li&gt;
&lt;li&gt;Invest more time in increasing technical expertise&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Outcomes and outputs
&lt;/h4&gt;

&lt;p&gt;Now try to come up with an outcome-based objective. This is probably the hardest part. &lt;/p&gt;

&lt;p&gt;So I suggest you try to aim for a direction here, rather than a specific output. This allows for flexibility in your approach—and as time goes by you can adapt the outputs which achieve the outcome if the original ones no longer become outdated.&lt;/p&gt;

&lt;p&gt;The list you made in step two will help you here. Your list likely contains actions or tasks, but you can group them if they achieve a common outcome, for example: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increasing our test coverage by 50% &lt;/li&gt;
&lt;li&gt;Working with multiple teams on a shared vision for DataViz&lt;/li&gt;
&lt;li&gt;Build awareness to be able to propose a NorthStar direction&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Putting it all together
&lt;/h4&gt;

&lt;p&gt;The result will look like this:&lt;/p&gt;

&lt;p&gt;Invest more time in topics related to the expertise criteria&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build awareness to be able to propose a North Star direction (outcome-based objective)&lt;/li&gt;
&lt;li&gt;Complete a Micro FE course (specific task)&lt;/li&gt;
&lt;li&gt;Document modern libraries and how our code can benefit from them (specific task)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you get stuck in the writing part, just list the open-ended goals and specific tasks, and reach out to your lead or manager to help you put it all together in a coherent way.&lt;/p&gt;

&lt;p&gt;If you made it to this point, you might have noticed that you haven’t used the list you made in stage one. Take a look at it again — did any long-term themes surface in your goals? Do you need to tweak anything in your development plan based on this list? Probably not, the aim of this list was mostly to help clear your mind of the imminent needs and focus a little bit more on the long term!&lt;/p&gt;

</description>
      <category>performance</category>
      <category>career</category>
      <category>leadership</category>
    </item>
    <item>
      <title>Monorepos - sharding Jest in CI</title>
      <dc:creator>Mario Beltrán</dc:creator>
      <pubDate>Tue, 27 Sep 2022 15:47:42 +0000</pubDate>
      <link>https://forem.com/hotjar/monorepos-sharding-jest-in-ci-47bc</link>
      <guid>https://forem.com/hotjar/monorepos-sharding-jest-in-ci-47bc</guid>
      <description>&lt;p&gt;&lt;a href="https://monorepo.tools/" rel="noopener noreferrer"&gt;Monorepos&lt;/a&gt;—single repositories that store code and assets for multiple software projects—are trendy in the frontend engineering scene. We at Hotjar are splitting our main app (Insights Webapp) from one huge monolith into small projects within a monorepo. One of the reasons we’ve done this is to make project development more scalable, regardless of the number of areas involved and the engineers working in them.&lt;/p&gt;

&lt;p&gt;We mainly instrument our monorepo with &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt; for the dependencies and &lt;a href="https://nx.dev/" rel="noopener noreferrer"&gt;Nx&lt;/a&gt; for the tooling and running tasks. However, orchestrating a monorepo is challenging, and scaling it correctly is more complicated than you’d think.&lt;/p&gt;

&lt;p&gt;Running our tests in CI within a monorepo in the optimal amount of time was a complex process that took many iterations to refine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old approach
&lt;/h2&gt;

&lt;p&gt;We run the tests only for monorepo workspaces affected by the changes in a branch, so we don’t waste time running tests for code that is not affected at all. &lt;a href="https://nx.dev/concepts/mental-model#affected-commands" rel="noopener noreferrer"&gt;Nx is great at this&lt;/a&gt;—it identifies the different workspaces of the monorepo affected by the files changed in our PRs.&lt;/p&gt;

&lt;p&gt;However, as mentioned earlier, we are in the process of breaking down the monolith into small pieces. We have many small, modern monorepo workspaces (let’s call them &lt;code&gt;modernA&lt;/code&gt;, &lt;code&gt;modernB&lt;/code&gt;, etc.), and two massive legacy workspaces (we’ll refer to them as &lt;code&gt;legacyX&lt;/code&gt; and &lt;code&gt;legacyY&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The runtime difference between modern and legacy tests is massive&lt;/strong&gt;: the former have only a few tests that are specific to their scope, while the latter have hundreds of tests still covering many areas owned by various squads.&lt;/p&gt;

&lt;p&gt;So, this was our approach: we parallelized our test runs in six jobs within GitLab CI, distributing the tests by workspaces. Since &lt;code&gt;legacyX&lt;/code&gt; and &lt;code&gt;legacyY&lt;/code&gt; are the slowest test runs—and almost always affected by Nx (because they still contain the core and shell of the webapp)—we isolate them in their own jobs. Then, we have four jobs remaining for the rest of the modern workspaces affected by the PR changes.&lt;/p&gt;

&lt;p&gt;With this in mind, let’s assume a PR where our changes affected &lt;code&gt;legacyX&lt;/code&gt;, &lt;code&gt;legacyY&lt;/code&gt;, &lt;code&gt;modernA&lt;/code&gt;, &lt;code&gt;modernB&lt;/code&gt;, &lt;code&gt;modernC&lt;/code&gt;, &lt;code&gt;modernD&lt;/code&gt;, &lt;code&gt;modernE&lt;/code&gt;, &lt;code&gt;modernF&lt;/code&gt;, &lt;code&gt;modernG&lt;/code&gt;, and &lt;code&gt;modernH&lt;/code&gt;. The tests would be grouped like this in CI jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;legacyX&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;legacyY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;modernA&lt;/code&gt;, &lt;code&gt;modernB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;modernC&lt;/code&gt;, &lt;code&gt;modernD&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;modernE&lt;/code&gt;, &lt;code&gt;modernF&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;modernG&lt;/code&gt;, &lt;code&gt;modernH&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So far, so good. The problem comes when we take a look at the runtimes in CI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj55jrvqua6ms4hrntgmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj55jrvqua6ms4hrntgmv.png" alt="Runtimes for the 6 jobs ran in CI with the old approach: job #2 takes more than eight minutes, while jobs #3 and #4 last less than 2 minutes each"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see that jobs 3-6 are reasonably fast, but 1 and 2 became a bottleneck! We can’t break them down any further (to do that, we need to keep extracting functionality into its own workspace). Our CI won’t be completed until the slowest test job is finished. So, in this case, our CI will take eight minutes, no matter how fast the remaining workspace tests are.&lt;/p&gt;

&lt;p&gt;This is because all our monorepo workspaces contain different numbers of tests–jobs 1 and 2 are running many, many tests for the legacy workspaces, while the others are running relatively small modern workspaces with fewer tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new approach
&lt;/h2&gt;

&lt;p&gt;Our test framework and runner for JS/TS is Jest. In &lt;a href="https://jestjs.io/blog/2022/04/25/jest-28#sharding-of-test-run" rel="noopener noreferrer"&gt;v28, they introduced a new feature for sharding test runs&lt;/a&gt;: Jest now includes a new &lt;code&gt;--shard&lt;/code&gt; CLI option that allows you to run parts of your test across different machines—it’s one of Jest’s oldest feature requests.&lt;/p&gt;

&lt;p&gt;This feature is handy for distributing our test suites in homogeneous chunks across our parallel jobs. We were already running tests split by workspace through Nx, but this didn’t give us granular control over how many tests we could run per job. Some ran hundreds of tests (e.g., the legacy workspaces), taking eight minutes to complete; others executed fewer than 20 tests (e.g., recently-created modern workspaces), taking only a minute to complete.&lt;/p&gt;

&lt;p&gt;Although the tests are no longer grouped by monorepo workspaces on each job, we still keep the ability to run the tests only for affected workspaces. Thanks to the &lt;a href="https://nx.dev/nx/print-affected" rel="noopener noreferrer"&gt;Nx print-affected command&lt;/a&gt;, we can use the list of projects affected to manually filter workspaces that need to be checked.&lt;/p&gt;

&lt;p&gt;With this new approach in mind, and the same PR used as an example above, the tests would be distributed in CI jobs like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;legacyX 1/6&lt;/code&gt;, &lt;code&gt;legacyY 1/6&lt;/code&gt;, &lt;code&gt;modernA 1/6&lt;/code&gt;, &lt;code&gt;modernB 1/6&lt;/code&gt;, &lt;code&gt;modernC 1/6&lt;/code&gt;, &lt;code&gt;modernD 1/6&lt;/code&gt;, &lt;code&gt;modernE 1/6&lt;/code&gt;, &lt;code&gt;modernF 1/6&lt;/code&gt;, &lt;code&gt;modernG 1/6&lt;/code&gt;, &lt;code&gt;modernH 1/6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;legacyX 2/6&lt;/code&gt;, &lt;code&gt;legacyY 2/6&lt;/code&gt;, &lt;code&gt;modernA 2/6&lt;/code&gt;, &lt;code&gt;modernB 2/6&lt;/code&gt;, &lt;code&gt;modernC 2/6&lt;/code&gt;, &lt;code&gt;modernD 2/6&lt;/code&gt;, &lt;code&gt;modernE 2/6&lt;/code&gt;, &lt;code&gt;modernF 2/6&lt;/code&gt;, &lt;code&gt;modernG 2/6&lt;/code&gt;, &lt;code&gt;modernH 2/6&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;legacyX 6/6&lt;/code&gt;, &lt;code&gt;legacyY 6/6&lt;/code&gt;, &lt;code&gt;modernA 6/6&lt;/code&gt;, &lt;code&gt;modernB 6/6&lt;/code&gt;, &lt;code&gt;modernC 6/6&lt;/code&gt;, &lt;code&gt;modernD 6/6&lt;/code&gt;, &lt;code&gt;modernE 6/6&lt;/code&gt;, &lt;code&gt;modernF 6/6&lt;/code&gt;, &lt;code&gt;modernG 6/6&lt;/code&gt;, &lt;code&gt;modernH 6/6&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This leads to a pipeline result like the one below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbzk97f7iueu9js6mc7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcbzk97f7iueu9js6mc7x.png" alt="Runtimes for the 6 jobs ran in CI with the new approach: they are more consistent now, taking between 5 and 6 minutes each"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The runtimes are more consistent simply because we mixed tests from all the monorepo workspaces together!&lt;/strong&gt; Now, the slowest test job takes just six minutes, saving us a solid two minutes from the CI runtime.&lt;/p&gt;

&lt;p&gt;These numbers are not just examples—they’re real runtimes from our CI. This, in particular, was our worst-case scenario: running the tests for &lt;em&gt;all&lt;/em&gt; the workspaces in our monorepo. For the average scenario (seven to eight workspaces affected, including the two legacy ones), we can save almost four minutes per pipeline—a potential save of 50%!&lt;/p&gt;

&lt;p&gt;The Jest shard feature didn’t just make our tests’ runtime more consistent in CI; it also made it more scalable. &lt;strong&gt;Now, it doesn’t matter if our tests grow by the number of tests per workspace, or by new workspaces introduced to the monorepo: they’ll be distributed consistently between available parallel jobs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is a huge win, especially compared to the previous approach. In the past, we would &lt;em&gt;always&lt;/em&gt; have a job taking eight minutes to complete, and the remaining jobs taking different, haphazard completion times on each PR, according to the number of tests assigned.&lt;/p&gt;

&lt;p&gt;This new approach was also surprisingly easy to set up in GitLab CI with a custom script, which looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// calculate-affected-projects.js (snippet simplified for convenience)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:child_process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nx print-affected --target=test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// do something if `err`&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;affectedProjects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filterAffectedProjects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;affectedProjects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`--filter &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filterAffectedProjects&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitlab-cy.yml (snippet simplified for convenience)&lt;/span&gt;
&lt;span class="na"&gt;js-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;parallel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export FILTER_AFFECTED_PROJECTS=$(node calculate-affected-projects.js)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pnpm --no-bail --if-present $FILTER_AFFECTED_PROJECTS test --ci --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Trade-offs
&lt;/h2&gt;

&lt;p&gt;Everything that glitters isn’t gold, though! This new approach with Jest shard has some implications worth mentioning:&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenging upgrade to Jest v28
&lt;/h3&gt;

&lt;p&gt;This is dependent on your codebase and your current version of Jest. At the time, we were still on Jest v26. Upgrading two majors was hard, not only because of the breaking changes introduced in v27 and v28, but also due to Jest and js-dom working more closely to a real browser on every upgrade. This led to flaky tests, side effects, and unexpected behaviors during the upgrade that were complicated to debug and fix. The result was worth it, but if you’re a few majors behind Jest v28, remember: you may need to put in more effort than you think.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inability to take advantage of Nx computation cache
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nx.dev/core-features/cache-task-results" rel="noopener noreferrer"&gt;Nx uses a computation cache internally, so it never rebuilds or re-runs the same code twice&lt;/a&gt;. This can save a lot of time if the affected monorepo workspaces didn’t change, so Nx can directly output the previous test results.&lt;/p&gt;

&lt;p&gt;But with the new Jest shard approach, we can’t take advantage of it. This isn’t a &lt;em&gt;huge&lt;/em&gt; deal for us because we weren’t using this functionality yet. Even if we did, the two legacies’ monorepo workspaces are constantly changing, invalidating the computation cache. It was easy for &lt;em&gt;us&lt;/em&gt; to ditch the feature, but perhaps &lt;em&gt;you&lt;/em&gt; rely on it heavily. If so, we suggest measuring the impact of using Nx computation cache vs. Jest shard for running your tests before changing the way you run them on CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of coverage threshold
&lt;/h3&gt;

&lt;p&gt;For us, this has been the biggest downside: &lt;strong&gt;the Jest shard option is not compatible with the global coverage threshold&lt;/strong&gt;. Surprisingly, this is not mentioned anywhere in the docs, but &lt;a href="https://github.com/facebook/jest/issues/12751" rel="noopener noreferrer"&gt;there is this issue open in their repo&lt;/a&gt;. Hopefully, this feature will become compatible with the shard flag soon. In the meantime, we’ve had to disable the global coverage threshold while we explore other ways to set it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Longer total computed runtime on CI
&lt;/h3&gt;

&lt;p&gt;If you compare the total time spent by the six jobs in the old approach (~23 minutes) and the new approach (~30 minutes), the former took less computation time from our CI. This is not causing a significant impact for us, but it could in other cases, especially if you have a tightly restricted minutes-per-month plan to run your CI.&lt;/p&gt;




&lt;p&gt;That’s it! We hope you found this post interesting—not just because of the new Jest shard feature, but also the learnings around our take on monorepos and the trade-offs that come with different approaches.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover photo by &lt;a href="https://unsplash.com/@marcinjozwiak?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Marcin Jozwiak&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/distribute?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>tooling</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Best-self review: how to process feedback in an actionable way</title>
      <dc:creator>Susana Martins</dc:creator>
      <pubDate>Tue, 13 Sep 2022 08:49:10 +0000</pubDate>
      <link>https://forem.com/hotjar/best-self-review-how-to-process-feedback-in-an-actionable-way-1jai</link>
      <guid>https://forem.com/hotjar/best-self-review-how-to-process-feedback-in-an-actionable-way-1jai</guid>
      <description>&lt;p&gt;Every six months at Hotjar, employees have their &lt;strong&gt;best-self review&lt;/strong&gt;, which consists of a self and peer review. The review forces us all to reflect on two things: how well we’re living up to &lt;a href="https://www.hotjar.com/blog/how-values-guide-everything-we-do/"&gt;Hotjar’s core values&lt;/a&gt; and how we’re performing against our seniority criteria. We’re required to give examples of things we’ve accomplished during the period, and also think about how we can improve and continue to grow. Our peers are also given an opportunity to share their thoughts and feedback. Finally, we discuss these results at a performance review meeting with our lead.&lt;/p&gt;

&lt;p&gt;Although this time can feel tense and high-pressure, ultimately, it helps us grow and develop our skills. To that end, it’s every Hotjarian’s responsibility to find ways of processing all the information given to us in a way that translates to concrete actions or steps we can take to achieve our goals. Of course, this is easier said than done.&lt;/p&gt;

&lt;p&gt;I want to share my personal process and experience, and what works for me. Each person is different, so my method may not necessarily be beneficial for everyone—but it’s something to consider if you, like many others, dread the review cycle and &lt;strong&gt;find it difficult to comprehend and respond to feedback received in the moment&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing my approach to reviews
&lt;/h2&gt;

&lt;p&gt;I’m a nervous and anxious person. The anticipation of my lead sharing feedback with me is inevitably very stressful. In the past, I’ve been guilty of joining the meeting, sitting there, and partially listening—while at the same time trying to read the feedback cards and managing my anxiety. Because I’m receiving the info &lt;strong&gt;at that moment&lt;/strong&gt;, I don’t have much to say or comment on because I don’t have the capacity to process it on the spot effectively.&lt;/p&gt;

&lt;p&gt;This is not a very useful way to spend my and my lead’s time. So we decided to try something different: in my last review, my lead shared the feedback with me a few days before we met, giving me the time to process this information in my own way. &lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;For the first time, I was the one doing the majority of the talking in my review. I was the one presenting the feedback and ideas for changes I could make to achieve my goals to my lead—not the other way around.&lt;/p&gt;

&lt;p&gt;I felt more proactive, and the time was more productive: I could ask about specific aspects that were unclear to me, or focus on things I wanted to improve but needed more guidance on how.&lt;/p&gt;

&lt;p&gt;I organized all the information on a Miro board, developing this template:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hn3vbh2C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yja4zu6ii5o7hq6m9r4q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hn3vbh2C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yja4zu6ii5o7hq6m9r4q.jpg" alt="Image description" width="880" height="863"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A breakdown of my Miro
&lt;/h2&gt;

&lt;p&gt;My board consists of five sections:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. To improve
&lt;/h3&gt;

&lt;p&gt;Here, I organized the big topics or themes—things that I repeatedly mentioned—on bigger green cards, and placed the specifics around them on smaller yellow cards.&lt;/p&gt;

&lt;p&gt;For example: a green card might say &lt;em&gt;&lt;strong&gt;impact the company&lt;/strong&gt;&lt;/em&gt;. In turn, its yellow cards might say &lt;em&gt;&lt;strong&gt;lead a Chapter Week initiative&lt;/strong&gt;&lt;/em&gt; or &lt;em&gt;&lt;strong&gt;share more learnings in common channels&lt;/strong&gt;&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;I used arrows to help me correlate between them and added a few notes where necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. To discuss with peers
&lt;/h3&gt;

&lt;p&gt;Sometimes, feedback can be unexpected or unclear. It’s crucial to clarify exactly what my peers are trying to say so I can actually do something about it. I added those cards so I could meet with my colleagues and have these discussions directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. To continue
&lt;/h3&gt;

&lt;p&gt;The good stuff!😊 Even though we tend to focus on what we need to improve, it’s good to reflect on the things we’re good at so we can keep doing them. It’s also something that makes us feel good, so we shouldn’t ignore our strengths!&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Goals
&lt;/h3&gt;

&lt;p&gt;This is a big one: if we don’t know what we want to achieve, it’s harder to come up with concrete action items. It’s important to work towards a goal, even if that goal changes in time (which is both natural and expected, especially with long-term goals).&lt;/p&gt;

&lt;p&gt;It helps to select the most important aspects from the review and focus on these. So I added two sections: one for the short-term goal (for example, &lt;em&gt;&lt;strong&gt;promotion to level X in 6 months&lt;/strong&gt;&lt;/em&gt;) and another for long-term, looking two or three years into the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Actions
&lt;/h3&gt;

&lt;p&gt;This is where I added concrete actions to take based on the green and yellow cards from the “To improve” section. &lt;/p&gt;

&lt;p&gt;I kept the green ones (the themes) and added blue cards for each action I wanted to take. For example, related to the &lt;em&gt;&lt;strong&gt;impact the company&lt;/strong&gt;&lt;/em&gt; example I mentioned earlier, I could add &lt;em&gt;&lt;strong&gt;lead the next Chapter Week initiative on [this date]&lt;/strong&gt;&lt;/em&gt;. As you can see, this is similar to the yellow cards, but it’s more concrete. It’s also filtered because not all the things mentioned in the yellow cards resulted in actions for this specific time.&lt;/p&gt;

&lt;p&gt;While populating these sections, I left comments on things I wasn’t sure of or wanted to discuss with my lead, and we focused on these during our meeting time. I found this approach incredibly helpful. Unlike previous meetings, which were often spent on things I already knew how to address, or weren’t that relevant for what I wanted to achieve, this meeting ended with &lt;strong&gt;my questions answered and a clear set of actions to take or ideas to refine&lt;/strong&gt;. And that’s exactly how it should be—after all, the purpose of the review is to help &lt;strong&gt;me&lt;/strong&gt; grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making my review work for me
&lt;/h2&gt;

&lt;p&gt;Since my last review, I’ve bookmarked the Miro board, and I’m marking progress on my action items. Every now and then, I revisit it to see what I’ve already achieved and what I can still work on before my next review. I also find this helps me organize my personal development time, and I can keep this information in mind when I write my next self-review. &lt;/p&gt;

&lt;p&gt;The feeling that I’m directly addressing the feedback I receive during the review instead of taking random actions has been a game-changer. I have a completely different attitude towards my review. While the nerves and anxiety haven’t &lt;em&gt;completely&lt;/em&gt; dissipated, I feel much more comfortable—and even confident. Maybe this approach could have the same impact on someone else.&lt;/p&gt;

</description>
      <category>growth</category>
      <category>productivity</category>
      <category>feedback</category>
    </item>
    <item>
      <title>Prioritizing brilliantly: better alternatives to grind and guilt</title>
      <dc:creator>Stephanie Mifsud</dc:creator>
      <pubDate>Thu, 25 Aug 2022 15:27:00 +0000</pubDate>
      <link>https://forem.com/hotjar/prioritizing-brilliantly-better-alternatives-to-grind-and-guilt-23ck</link>
      <guid>https://forem.com/hotjar/prioritizing-brilliantly-better-alternatives-to-grind-and-guilt-23ck</guid>
      <description>&lt;p&gt;As software engineers, we’re always in pursuit of high-quality solutions against a ticking clock. But spending too much time perfecting solutions before delivery increases cost and delays learnings. &lt;/p&gt;

&lt;p&gt;What’s more, the standard approach of grinding to the finish line is not sustainable. I quickly realised I had to learn how to pace myself for a marathon, not a sprint, while also enjoying the views. In other words, I needed to work smarter rather than harder.&lt;/p&gt;

&lt;p&gt;In this post, I would like to share a few engineering-adjacent skills I’ve found useful for a successful career as a Senior Engineer and Team Lead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned (and how)
&lt;/h2&gt;

&lt;p&gt;At Hotjar we use &lt;a href="https://cloud.google.com/resources/state-of-devops"&gt;industry benchmarks&lt;/a&gt; that set elite performers apart from the rest. These benchmarks favour an iterative approach to learning quickly, reducing waste, and keeping up the momentum. &lt;/p&gt;

&lt;p&gt;I’ve also picked up a few tips from my mentors, who inspire me to be intentional in my work and align my efforts in the same direction as my team or company. The book &lt;a href="https://www.amazon.com/Coherence-Secret-Science-Brilliant-Leadership/dp/0749470054"&gt;Coherence by Dr. Alan Watkins&lt;/a&gt; has also been a great inspiration during this ongoing journey.&lt;/p&gt;

&lt;p&gt;Both these sources have helped me develop a few valuable ideas of my own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Align your goals
&lt;/h3&gt;

&lt;p&gt;One of my major time wasters was flitting from one thing to another without first gauging the importance of each task. I needed a mental representation of the priority areas, to differentiate between what was a high-impact activity for my goals and what was merely a distraction. I started by combining all my goals for the quarter. &lt;/p&gt;

&lt;p&gt;It’s all too easy to be pulled into different directions by: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rework.withgoogle.com/guides/set-goals-with-okrs/steps/introduction/"&gt;OKRs&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Tech briefs and guidance &lt;/li&gt;
&lt;li&gt;Performance Review opportunities &lt;/li&gt;
&lt;li&gt;My own interests and goals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So now, I put all these goals on one list. Then, I pick a theme to focus on for the month. The theme helps me prioritise activities that increase the impact of my work—especially if some groundwork is required before focusing on a particular area. For example, if the theme is to tackle tech debt, it also helps to look at the north star direction.&lt;/p&gt;

&lt;p&gt;For an individual contributor, these goals may be squad-related, &lt;a href="https://www.adaptovate.com/agile/what-are-chapters-in-an-agile-operating-model/"&gt;chapter-related&lt;/a&gt;, north star-related, etc. Maintaining a laundry list of bottlenecks or implementations reaching a stress point also helps allocate the right amount of effort. Some of these areas might be aligned with the squad’s current focus area, and thus tech debt can be addressed as preparation for feature work. If not, laundry lists at the very least make good material for a proposal for upcoming focus areas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alternate between breadth and depth
&lt;/h3&gt;

&lt;p&gt;Goals and themes require frequent alignment and refinement to continue serving their purpose. I often ask questions when our decisions seem to be drifting away from our original squad goals. As we update our direction with learnings and results from data, it helps the team align and evolve their understanding of what’s next. Then we shift our focus to the tasks in our backlog and identify what’s missing for us to reach the goal we’ve identified. &lt;/p&gt;

&lt;p&gt;We’ve reached a point several times where we know in our hearts that we are not going to manage the original target we set. Or perhaps we’ve gained new knowledge that changes our approach. This is either an invitation to grind to the original deadline or rise to the challenge of &lt;a href="https://www.visual-paradigm.com/scrum/user-story-splitting-vertical-slice-vs-horizontal-slice/"&gt;slicing the deliverable differently&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Some questions I ask here are: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What resources do I have at my disposal to help me de-risk this work? &lt;/li&gt;
&lt;li&gt;Can I spike the riskiest part to check for feasibility?&lt;/li&gt;
&lt;li&gt;What is the minimum work I can do to get the whole system working from end to end?&lt;/li&gt;
&lt;li&gt;Will excluding part X from this work still make it a successful release?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Slicing work into smaller deliverables maintains momentum and ensures timely feedback, so a pivot will not lead to a lot of waste. (Sometimes we even replace the original task with one of higher value to our customers as we adapt to new learnings.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Use sync-meeting agendas for broad-picture thinking
&lt;/h4&gt;

&lt;p&gt;The switch between breadth and depth is costly—and sometimes painful—because the brain wants to return to its previous train of thought. I like to use sync meetings as a time to address this: syncs can help switch focus faster. If I know a planning meeting is coming up, I spend 30 minutes preparing my contributions. The end of a sync meeting is also an excellent time to revise what was decided and identify any gaps or misalignments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask for feedback
&lt;/h3&gt;

&lt;p&gt;After aligning on the right priorities and keeping a bird's-eye view on the end goal, the next time-suck I realized I needed to avoid was perfection. Contrary to popular belief, &lt;a href="https://medium.com/cmd-opt-shift/stop-using-perfectionism-as-an-excuse-11ae4c4d45e6"&gt;perfection is more a vice than a virtue&lt;/a&gt;! I remember a time when I used to polish and perfect my work before asking for feedback, mostly out of fear that my work would appear sloppy. However, getting someone to give me feedback on the initial direction, scope, and size proved extremely helpful in structuring my thoughts. &lt;/p&gt;

&lt;p&gt;When a draft is spending too much time gathering dust on my desk, I ask my lead or peers whether it’s valuable to continue pursuing it. &lt;/p&gt;

&lt;h4&gt;
  
  
  Set the right expectations
&lt;/h4&gt;

&lt;p&gt;Naming conventions matter to help set the right expectations from reviewers. Here are a few examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Artifact name&lt;/th&gt;
&lt;th&gt;Feedback expectations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A brain dump&lt;/td&gt;
&lt;td&gt;High-level feedback is required to validate the direction of ideas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DRAFT: Merge Request&lt;/td&gt;
&lt;td&gt;The reviewer will not nitpick with the incomplete parts but instead provide direction on boundaries and code structure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A proposal or RFC&lt;/td&gt;
&lt;td&gt;Needs to be well thought out, and address a clear business case, to receive feedback on specifics.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Iterate quickly
&lt;/h4&gt;

&lt;p&gt;Perfectionism has value when it comes to the polishing stage of an article or deliverable. However, the prior WIP stages are how we work together to achieve a higher impact. Hiding my work simply because it wasn’t perfect yet was not helping me. Iteration is more powerful than perfectionism because it refines the deliverable to the right audience, whether it is the company or the customer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Energy Management
&lt;/h3&gt;

&lt;p&gt;All the strategies I’ve discussed continue to help me reduce waste and distraction—but being focused still takes up a lot of my energy. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://hbr.org/2007/10/manage-your-energy-not-your-time"&gt;Energy management is more impactful than time management&lt;/a&gt;. Having hard stops in my day allows me energy for activities that make my life more meaningful—like spending time with friends, family, and pets. This increases my motivation and sense of self overall. And restoring myself before I crash helps me work more consistently. &lt;/p&gt;

&lt;p&gt;Crashing is expensive; it takes time to recover from. I’m still learning to notice the signs I’m slowing down and find healthy pick-me-ups. &lt;/p&gt;

&lt;p&gt;I’ve also redefined success to represent my whole person—I’m not simply a brain attached to a body. Our life is so digital that sometimes our bodies become an afterthought. We eat and move on autopilot. Being intentional about improving my physical and mental strength has made my energy more consistent and reliable, helping me do better at work and in my personal life.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>growth</category>
      <category>leadership</category>
      <category>motivation</category>
    </item>
    <item>
      <title>Hotjar's migration from AngularJS to React - Our approach</title>
      <dc:creator>Steve Pitchford</dc:creator>
      <pubDate>Thu, 30 Sep 2021 09:13:24 +0000</pubDate>
      <link>https://forem.com/hotjar/hotjar-s-migration-from-angularjs-to-react-our-approach-4g54</link>
      <guid>https://forem.com/hotjar/hotjar-s-migration-from-angularjs-to-react-our-approach-4g54</guid>
      <description>&lt;p&gt;Hotjar is a tool that helps people understand how their users are behaving on their site, what they need, and how they feel. You can find out more about Hotjar and the services we provide at &lt;a href="https://www.hotjar.com/" rel="noopener noreferrer"&gt;hotjar.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From a technical perspective, we provide a rich single-page application (SPA) for displaying data collected from our user’s sites and data provided by their users. Our application was initially written in AngularJS as far back as early 2014. As a bootstrapped startup, the first version of the application was created by a very small team of full-stack engineers. As the company matured, we switched to more specialised roles and now we have a dedicated team of 26+ Frontend Engineers and continuing to grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reasons to migrate away from AngularJS
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AngularJS is an older framework that didn’t keep up with modern development practices. Features like lazy loading aren’t very easy to implement and require modification to the application to get it working.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AngularJS is reaching its end of life and will no longer get support.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Due to the framework reaching the end of life it is becoming progressively harder to find developers with recent experience in this framework, due mainly to the two points above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A lot of lessons were learned from the implementation of AngularJS and these problems were addressed in other frameworks. We want to leverage these better design patterns to make our application scale and easier to maintain.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why React?
&lt;/h2&gt;

&lt;p&gt;We discussed several framework options including Angular, Vue, Ember, and React.&lt;/p&gt;

&lt;p&gt;Angular didn’t feel like the right fit for most developers in the team despite there being a more defined migration path for AngularJS -&amp;gt; Angular. The reason it didn’t feel like a good fit for us as a team was that we felt that the opinionated nature of Angular didn’t align with our goal of allowing teams autonomy in how they develop features and that it would be too restrictive.&lt;/p&gt;

&lt;p&gt;Vue was still somewhat new at the time, and no one in the team at the time had any experience using Vue.&lt;/p&gt;

&lt;p&gt;Ember is a powerful framework but, as with Vue, no one had any experience using it.&lt;/p&gt;

&lt;p&gt;We had developers who had used React in the past and were familiar with the design patterns used in React. Given React’s popularity and community, we wanted to leverage this for both JS libraries and for the pool of developers that we could hire from that already had lots of experience with React.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our React setup
&lt;/h2&gt;

&lt;p&gt;We opted not to go for a full Redux app setup since we wanted to keep the state local and avoid oversharing the state between different parts of the application if it wasn’t needed, this encourages teams to work independently from each other. We preferred to pull the state up the component tree when needed.&lt;/p&gt;

&lt;p&gt;We use TypeScript with React as we find it adds an extra layer of safety to our development. It takes longer to set up components but the payoff exceeds the extra work. We however do have problems with the React/AngularJS boundary since the AngularJS code is not in TypeScript. This means we lose our type safety when passing data to and from the AngularJS application.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://react-query.tanstack.com/" rel="noopener noreferrer"&gt;react-query&lt;/a&gt; to handle caching of API requests to avoid over-fetching of data from the backend which, in a way, acts as a global store. Since the query cache is immutable and all changes trigger updates within components we need to worry less about the state being modified in an unexpected way by some other part of the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complete rewrite vs Incremental migration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Complete rewrite
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PROS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A complete rewrite is great because you can ship a new shiny application to your end-users when you finish.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can develop the application with a clean architecture since you don’t have to carry around any baggage from the legacy application.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CONS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You have to either halt the development of new features to your customers, or you need to develop features twice so that the new application keeps feature parity with the older one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You are more prone to introducing regressions and bugs since you are writing tests from scratch and don’t have existing test suites you can leverage to ensure that flows continue to work as expected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Micro frontends may have solved some of these issues. However, using micro frontends within the AngularJS app isn’t trivial and would still have required rewrites of entire pages or sections of the application. This would still require halting the development of new features while the migration happened.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is hard to estimate the size of a complete rewrite since there are usually a lot of unknowns.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It usually comes with a huge initial cost of designing the architecture of the application and making application-wide decisions before we even start coding; then when the plan meets reality, it needs to be adjusted and you either end up with an inconsistent codebase or rewrite parts over and over again; this might be the case for the alternative approach as well, though.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Incremental migration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PROS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can develop features at the same time as code is migrated to a new framework. This is great because customers continue to get new value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can leverage existing test suites such as end-to-end tests to ensure that features still work as expected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is easier to size the work needed to migrate since migration work can be broken down into much smaller defined tasks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It gives you time and opportunity to adjust the approach or architecture over time, evaluate how your ideas work in practice, and change them along the way; it’s still possible with a complete rewrite but it may be more difficult there; with incremental migration, the initial cost is pretty low and you’re not even required of making decisions beforehand - you do it only when you really need it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;CONS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You carry around a lot of baggage and dependencies from the old codebase as parts of the application may still depend on code that lives in the old codebase; this may have a performance cost for the end-user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can’t easily implement new designs into your code since it needs to match the rest of the legacy application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It takes significantly longer to migrate this way since we have to introduce some workarounds to get data passed between the different frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We chose to take the incremental approach since at the start of the migration we didn’t have a very big frontend team and we wanted to be able to continue delivering features to our customers.&lt;/p&gt;

&lt;p&gt;Despite the drawbacks of incremental migration, we feel like this was a good choice for us as a company and that it has paid off in the long run.&lt;/p&gt;

&lt;h2&gt;
  
  
  React-2-angular
&lt;/h2&gt;

&lt;p&gt;To approach an incremental migration we needed a way to embed react components within the AngularJS application. We found the great library &lt;a href="https://github.com/coatue-oss/react2angular" rel="noopener noreferrer"&gt;react-2-angular&lt;/a&gt; that lets us create React components that can be embedded within an AngularJS page. This also allows us to pass in dependencies to the react components as props so we can leverage our existing AngularJS services to pass data and API function calls to our react components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating UI First and Design Systems
&lt;/h2&gt;

&lt;p&gt;It was decided to first attempt to start migrating the UI layer of the application to React while keeping state and some UI logic in AngularJS. This meant we didn’t have to migrate some of our really large AngularJS controllers that had grown over the years and never been nicely split into smaller components. It also had the benefit of allowing us to build pure(ish) components that largely didn’t handle any state, except for maybe state used by controlled inputs. This, in turn, enabled us to get the UI of the app to a point where it is more easily modified across the app while teams work on migrating the controller and service layers of the application.&lt;/p&gt;

&lt;p&gt;At a certain point in the migration process, the company decided to create a Design System implemented in React that would standardize all the common UI patterns used through the app. This made the migration of the UI layer even easier since large sections of the UI could be constructed using components from the Design System.&lt;/p&gt;

&lt;p&gt;Up until this point, teams had been building reusable components within the project and we didn’t want to throw these away, so these components we used to identify common patterns. We were also able to take these reused components and copy them into the Design System and give them clearly defined type definitions and make them consistent with the Design Language&lt;/p&gt;

</description>
      <category>react</category>
      <category>angular</category>
    </item>
    <item>
      <title>How Does React Work? Brief History - PART 1</title>
      <dc:creator>Adam Płócieniak</dc:creator>
      <pubDate>Thu, 12 Aug 2021 07:19:16 +0000</pubDate>
      <link>https://forem.com/hotjar/how-does-react-work-brief-history-part-1-1d12</link>
      <guid>https://forem.com/hotjar/how-does-react-work-brief-history-part-1-1d12</guid>
      <description>&lt;p&gt;&lt;em&gt;The article’s goal is to shed some light on the framework internals and historical insights standing behind the implementation decision made by the React Team at the time. I assume you are already aware of basic JavaScript / React and JSX concepts. So let’s kick off with some history first.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s&lt;/strong&gt; &lt;strong&gt;2013&lt;/strong&gt;. The React framework is born (version &lt;a href="https://github.com/facebook/react/blob/master/CHANGELOG.md#030-may-29-2013" rel="noopener noreferrer"&gt;0.3.0&lt;/a&gt; is rolled out to the public) and it’s immediately loved by the community. It stands out with its simplicity, one-way-data-binding, and - what’s obvious - declarative API powered by original, attractive to the community syntax extension, JSX.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22vfqxaoii2y7eh2l978.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22vfqxaoii2y7eh2l978.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;React’s JSX representation of an example app&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;The following months and years bring new releases; bugs are fixed, features and improvements are added. The community grows, the tooling ecosystem supporting its development flourishes. &lt;a href="https://reactnative.dev/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt; is embraced by mobile native developers, which brings even more popularity to the framework. But there is one thing around the framework that does not change at all. Its internal logic (so-called &lt;strong&gt;Reconciliation Algorithm&lt;/strong&gt;) is responsible for all of the “magic” - starting from when an application's state changes until direct DOM updates are performed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl95fqs7tlego1qjyhzp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl95fqs7tlego1qjyhzp.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;Input and output of the Reconciliation algorithm&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;Briefly, here’s how it works:&lt;/p&gt;

&lt;p&gt;(1) Every state change (e.g., clicking “Buy product” from the &lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u95tpve85arsv6ic2ulh.png" rel="noopener noreferrer"&gt;example application&lt;/a&gt;) forces building a so-called &lt;a href="https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom" rel="noopener noreferrer"&gt;Virtual DOM&lt;/a&gt;, representing the current state of the application. It's a structure of components.&lt;/p&gt;

&lt;p&gt;(2) Then, the newly created Virtual DOM tree is compared with a previously generated Virtual DOM tree representing the application’s state already displayed to a user. Discrepancies between those Virtual DOM structures are simple information, what we should change, e.g.:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;update attribute X for A element,&lt;/li&gt;
&lt;li&gt;remove element B,&lt;/li&gt;
&lt;li&gt;or append element C as a child of element A.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(3) The information is utilized by separate modules - renderers (for browsers, it’s react-dom) which applies the minimal set of changes necessary to update UI (the DOM nodes for browsers). &lt;/p&gt;

&lt;p&gt;And that’s how React works, briefly.&lt;/p&gt;

&lt;p&gt;But how is the Virtual DOM built? If we had to write the function responsible for that on our own, it might look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4pz63acgu3wik90ehko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4pz63acgu3wik90ehko.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;High-Level Pseudocode of React’s Reconciliation Algorithm&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;The &lt;code&gt;render&lt;/code&gt; function drills down through the entire &lt;strong&gt;React Elements&lt;/strong&gt; structure (Virtual DOM) and &lt;strong&gt;reconciles&lt;/strong&gt; (or works on) every element.&lt;/p&gt;

&lt;p&gt;Let’s stop for a moment here. What is a React Element? A React Element is a simple building block for our application. It’s what we return from our components, like &lt;code&gt;&amp;lt;Loader /&amp;gt;&lt;/code&gt; in our &lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u95tpve85arsv6ic2ulh.png" rel="noopener noreferrer"&gt;example application&lt;/a&gt;. It’s worth mentioning that React Elements are not DOM Elements. Whenever I refer to an “element” below in the article, I mean React Element. If we talk about DOM elements, I will explicitly use the “DOM” to avoid misunderstanding. &lt;/p&gt;

&lt;p&gt;Back to the &lt;strong&gt;reconciliation&lt;/strong&gt;. Reconciling here means doing some element-related work. For component elements, it includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invoking the &lt;code&gt;render()&lt;/code&gt; method (for class-based components) or calling functional component’s function reference with given props,&lt;/li&gt;
&lt;li&gt;managing internal state,&lt;/li&gt;
&lt;li&gt;invoking Lifecycle Methods (e.g., &lt;code&gt;getDerrivedState&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;marking changes that need to be applied later on,&lt;/li&gt;
&lt;li&gt;and many more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By calling &lt;code&gt;render(&amp;lt;App /&amp;gt;)&lt;/code&gt; (I recommend getting familiar with our &lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u95tpve85arsv6ic2ulh.png" rel="noopener noreferrer"&gt;example application&lt;/a&gt; specified above, we will use this structure later on), we re-create the tree structure from top to bottom, using &lt;code&gt;render&lt;/code&gt; function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starting from &lt;code&gt;render(App)&lt;/code&gt;, we work on the &lt;code&gt;App&lt;/code&gt; component,&lt;/li&gt;
&lt;li&gt;then we have some work to do with its child (&lt;code&gt;Content&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;then with its child’s children: &lt;code&gt;Loader&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;and so on, until we reach the last leaf of the structure of the elements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;It’s 2016&lt;/strong&gt;. Everyone is celebrating the framework's 3rd birthday (version &lt;a href="https://github.com/facebook/react/blob/master/CHANGELOG.md#1500-april-7-2016" rel="noopener noreferrer"&gt;15.0.0&lt;/a&gt; is released), but its parents (&lt;a href="https://reactjs.org/community/team.html" rel="noopener noreferrer"&gt;React Core Team&lt;/a&gt; with its lead, &lt;a href="https://reactjs.org/community/team.html#sebastian-markbage" rel="noopener noreferrer"&gt;Sebastian Markbåge&lt;/a&gt; - we will talk about him later in the article) are slightly worried about the future of the framework. But is there a reason to be concerned?&lt;/p&gt;

&lt;p&gt;It appears that the React has some “innate heart disease”, which limits its organic growth. Let’s have a brief look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4pz63acgu3wik90ehko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4pz63acgu3wik90ehko.png" alt="image"&gt;&lt;/a&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh50bbazr5smdhhhob7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqh50bbazr5smdhhhob7c.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;High-Level pseudocode of React’s core algorithm function vs. Fibonacci function&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;What’s common among &lt;code&gt;render()&lt;/code&gt; and &lt;code&gt;fib()&lt;/code&gt; functions?&lt;/p&gt;

&lt;p&gt;You’re right. It's a &lt;strong&gt;recursion&lt;/strong&gt;. The heart of the React framework relies strongly on recursion. But is it a problem at all?&lt;/p&gt;

&lt;p&gt;Web browsers are equipped with a single thread. We can do one operation at a time, so React operates in a limited environment. Although computing &lt;code&gt;fib(4)&lt;/code&gt; is not a challenge at all, computing &lt;code&gt;fib(4000)&lt;/code&gt; definitely is. Same for the Reconciliation Algorithm - building a Virtual DOM based on a thousand elements is a real challenge. Synchronous code execution blocks the main thread, so JavaScript’s event loop has to wait until the end of execution. During that time, none of the following activities can be performed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User input (e.g., handling user click event callback)&lt;/li&gt;
&lt;li&gt;Animations, layout calculations, repaints&lt;/li&gt;
&lt;li&gt;Handle incoming data (HTTP, WebSocket)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a brilliant talk about event loop &lt;a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ" rel="noopener noreferrer"&gt;here&lt;/a&gt;, so if you are not familiar with it or need a refresher, it’s definitely worth watching.&lt;/p&gt;

&lt;p&gt;Let’s talk now about the JS Stack; how does it look for both the &lt;code&gt;fib&lt;/code&gt; and &lt;code&gt;render&lt;/code&gt; functions?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnzfdehu9062vzyhc7rf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnzfdehu9062vzyhc7rf.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;Fibonacci function stack frames vs. Render function stack frames&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;The JS Stack grows as we move deeper in the structure, so the process simply can’t be paused because there is no straightforward way to do that in a recursion. &lt;strong&gt;We reconcile all of the elements in one shot or none at all&lt;/strong&gt;. What’s more, React's computation output is pushed onto the JS stack, so it's ditched immediately after the &lt;code&gt;render&lt;/code&gt; function returns. There is no way to reuse this work later on if it’s needed.&lt;/p&gt;

&lt;p&gt;Imagine a case of a heavy application with a massive number of components. We are in the middle of the Reconciliation Algorithm, and a user clicks a button. Such action is critical from a UX standpoint and should be handled immediately. But what happens?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clicking dispatches a DOM event. &lt;/li&gt;
&lt;li&gt;The event callback lands in a queue and waits (until the JS Stack is empty) to be processed.&lt;/li&gt;
&lt;li&gt;But the JS stack is “overwhelmed” by heavy React-related work, so the event callback waits…, waits, and waits for its turn until the Reconciliation Algorithm is done.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/QBd2kLB5qDmysEXre9/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/QBd2kLB5qDmysEXre9/giphy.gif"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;(&lt;a href="https://i.giphy.com/media/QBd2kLB5qDmysEXre9/giphy.gif" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;There is an excellent &lt;a href="https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle" rel="noopener noreferrer"&gt;Sierpinski triangle&lt;/a&gt; example &lt;a href="https://github.com/adasq/react-fiber-vs-stack-demo" rel="noopener noreferrer"&gt;application&lt;/a&gt; on Github. It is a more tangible showcase of the problem. Keep in mind that it’s 2016, so the application is built on top of React 15.x. Here is how the application looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw9vj90yygensayk763p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faw9vj90yygensayk763p.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;Sierpinski triangle example application view&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;Each dot is a component displaying a number. Among state updates, there are a lot of other computations, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;animations (layout computations, painting), &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/claudiopro/react-fiber-vs-stack-demo/blob/master/stack.html#L93-L99" rel="noopener noreferrer"&gt;deliberate delays&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;a whole bunch of artificial and pointless &lt;a href="https://github.com/claudiopro/react-fiber-vs-stack-demo/blob/master/stack.html#L134-L139" rel="noopener noreferrer"&gt;state changes&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these simulate a heavy application. And &lt;a href="https://claudiopro.github.io/react-fiber-vs-stack-demo/stack.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; is the application. Pretty sluggish, huh? Here’s how the top of the JS Stack looks like (I recommend watching this &lt;a href="https://youtu.be/Jb4X4hyaUCs" rel="noopener noreferrer"&gt;short video&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwijrareaznmkbphhtapp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwijrareaznmkbphhtapp.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;center&gt;&lt;em&gt;React 15.x JS Stack&lt;/em&gt;&lt;/center&gt;
&lt;img src="https://i.giphy.com/media/KSJ8UkuKHLmvK/giphy.gif"&gt;&lt;center&gt;&lt;em&gt;(&lt;a href="https://i.giphy.com/media/KSJ8UkuKHLmvK/giphy.gif" rel="noopener noreferrer"&gt;source&lt;/a&gt;)&lt;/em&gt;&lt;/center&gt;
&lt;br&gt;

&lt;p&gt;A synchronous and time-consuming function (in the Sierpinski’s triangle example, for my equipment, each “Task” takes ~300ms) reconciles the entire application from the top to the bottom of the elements tree, no matter what. &lt;/p&gt;

&lt;p&gt;The framework here is relentless. It overwhelms the main thread, which can’t perform any other types of work (animations, user’s input). It introduces a significant impact on the browser’s performance. It’s hard to build on the top of such architecture, isn’t it?&lt;/p&gt;

&lt;p&gt;And this is a huge limitation for the React Team. &lt;/p&gt;

&lt;p&gt;Of course, this is an example application built to show the problem. But we can quickly observe such pitfalls when the number of components grows in our real-world applications. Long and heavy lists are a perfect example here. It’s also why the helper libraries, like &lt;a href="https://github.com/bvaughn/react-virtualized" rel="noopener noreferrer"&gt;react-virtualized&lt;/a&gt; (or its lightweight version, &lt;a href="https://github.com/bvaughn/react-window" rel="noopener noreferrer"&gt;react-window&lt;/a&gt;), emerged and gained noticeable popularity by supporting the framework. Those libraries are &lt;a href="https://reactjs.org/docs/optimizing-performance.html#virtualize-long-lists" rel="noopener noreferrer"&gt;officially recommended&lt;/a&gt; on React docs pages.&lt;/p&gt;

&lt;p&gt;It’s also worth noticing that React is clever enough, and implements caching, exposes the keys API, introduces some trade-offs to reinforce performance which is a real deal, but still - it’s not enough to move forward (If you are interested in in-depth details of the algorithm, visit official docs &lt;a href="https://reactjs.org/docs/implementation-notes.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In the next part, I will explain what approach the React Team took to address those limitations.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>fiber</category>
    </item>
    <item>
      <title>Why should you write good commits?</title>
      <dc:creator>Gil Goncalves</dc:creator>
      <pubDate>Wed, 28 Jul 2021 08:19:35 +0000</pubDate>
      <link>https://forem.com/hotjar/why-should-you-write-good-commits-4ba5</link>
      <guid>https://forem.com/hotjar/why-should-you-write-good-commits-4ba5</guid>
      <description>&lt;p&gt;As software engineers who use git or other similar version control systems, we tend to write a lot of commits. Is investing in good commits a worthwhile investment?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should you write good commits?
&lt;/h2&gt;

&lt;p&gt;As engineers, it’s easy to focus on just the code because, after all, that’s what we spend most of our time reading and changing. I’m going to argue that Git isn’t simply a smart diff merger to help us put all code in the same place nor is it just a simple version control system, it can be so much more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clearing your head whilst coding
&lt;/h3&gt;

&lt;p&gt;One way I use git commits is to help myself understand what I was doing the day before when writing/changing a lot of code. It gives me a descriptive context that I can read in order to understand what I was doing the day/hours before quicker than just reading the code. This is also useful as a way to share with your colleagues some change you want some feedback on, what better place to write your intentions than in the commit message itself?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fteuhdg4df1yrr9lirm9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fteuhdg4df1yrr9lirm9z.png" alt="Example of a pre-toilet commit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Documenting the codebase
&lt;/h3&gt;

&lt;p&gt;If you use git to manage your codebase, then &lt;a href="https://mislav.net/2014/02/hidden-documentation/" rel="noopener noreferrer"&gt;every single line of code is being documented&lt;/a&gt; by the commit messages. Along with each change, there’s a git commit message, and if we’re able to document each change with some text describing it in a way that’s not tied to any external services, why not leverage that as a way to document your whole codebase as you write it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Easier debugging
&lt;/h3&gt;

&lt;p&gt;Let’s pretend that you’re working in a codebase where each commit clearly explains the reason why it was added. You are now looking at a piece of code and you want to delete it, but you don’t know if you can. Using &lt;code&gt;git blame&lt;/code&gt; to check the commit itself lets you understand when and why that change happened. You can then know whether you can remove that code (if it’s not needed because it was added as part of an experiment that has finished, for example) or whether you should keep it (it’s really important because the specific version of your DB requires that for example).&lt;/p&gt;

&lt;p&gt;Another git tool that can help us with debugging is &lt;code&gt;git bisect&lt;/code&gt;, which helps you find out which commit introduced a bug by helping you check commits using binary search. In this situation, it really helps having atomic commits that have one change only and that are well written so it’s easier for the person debugging to figure out which commit introduced the bug, but also why we made that specific change to answer questions such as: can we revert it?&lt;/p&gt;

&lt;p&gt;And speaking of &lt;code&gt;revert&lt;/code&gt;, that’s another good example of how good commits can help you. If each change is isolated in a commit, then it’s easy to &lt;code&gt;git revert&lt;/code&gt; just that specific commit (or some) to fix a problem in production, when speed is crucial.&lt;/p&gt;

&lt;h2&gt;
  
  
  OK, then what is a “good commit”?
&lt;/h2&gt;

&lt;p&gt;Let’s split the commit into two parts, the code change and the message.&lt;/p&gt;

&lt;p&gt;The code change should be “atomic”, meaning it should not break tests and it should be contained in a particular change.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Good example:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing a new API endpoint including tests and documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Bad examples:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing a new API endpoint, tests and documentation in three separate commits&lt;/li&gt;
&lt;li&gt;Implementing a change to the code and another commit to “fix tests”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With atomic changes, it’s easier to run &lt;code&gt;git bisect&lt;/code&gt; for finding out problems since you know that every commit keeps the build green, so you won’t have noise with whatever command you’re running to find a particular commit when bisecting and it’s easy to &lt;code&gt;git revert&lt;/code&gt; it for the exact same reason.&lt;/p&gt;

&lt;p&gt;As for the message, it should focus on explaining &lt;em&gt;why&lt;/em&gt; the change is being added to the codebase rather than what it’s doing since that’s already described in the change itself, this way when you check a commit for a piece of code you’re curious about, you can use it to understand why that code was written and its purpose (when it’s unclear from the code itself).&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;OK, I managed to convince you and you want to write commit messages that explain the reasoning behind an atomic code change to the code, what can you do next?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When writing a commit message, ask yourself whether the message explains well the reason for the change to be added to the codebase.&lt;/li&gt;
&lt;li&gt;When making a commit, consider if it can be &lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History" rel="noopener noreferrer"&gt;amended&lt;/a&gt; with another commit or if it’s an independent change that doesn’t break the tests.&lt;/li&gt;
&lt;li&gt;Make reviewing merge requests commit by commit a part of the code review process so that you’re only merging good commits into your codebase.&lt;/li&gt;
&lt;li&gt;Share this article with your team and friends (who use git) :)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;Because I’m standing on the shoulders of giants, here is a selection of talks/articles that helped me write this article and have helped me improve my own git commits during my career.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=mE8DZUfhdm4" rel="noopener noreferrer"&gt;Joel Chippindale | Simplify writing code with deliberate commits talk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tekin.co.uk/2021/01/how-atomic-commits-make-you-a-better-coder" rel="noopener noreferrer"&gt;How atomic commits make you a better coder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History" rel="noopener noreferrer"&gt;Pro git book | "Rewriting history"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=1NoNTqank_U" rel="noopener noreferrer"&gt;A good talk on git including a story of why git commit messages are useful&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The pro git book is free and a great resource to learn more about git: &lt;a href="https://git-scm.com/book/en/v2" rel="noopener noreferrer"&gt;https://git-scm.com/book/en/v2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=TdBELZG0UMY" rel="noopener noreferrer"&gt;Tom Stuart offers writing git commits as a solution to “Get off the tightrope”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>documentation</category>
    </item>
  </channel>
</rss>
