<?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>DEV Community: Sam Thorogood</title>
    <description>The latest articles on DEV Community by Sam Thorogood (@samthor).</description>
    <link>https://dev.to/samthor</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%2Fuser%2Fprofile_image%2F56676%2F495bf9f2-044a-42a1-9cb6-e43dba199fe6.jpeg</url>
      <title>DEV Community: Sam Thorogood</title>
      <link>https://dev.to/samthor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samthor"/>
    <language>en</language>
    <item>
      <title>Web Font Loading &amp; The Status Quo</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Tue, 16 Feb 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/samthor/web-font-loading-the-status-quo-3mpb</link>
      <guid>https://dev.to/samthor/web-font-loading-the-status-quo-3mpb</guid>
      <description>&lt;p&gt;Let's start with the obvious: there's lots of great posts out there on font loading (which all tend to be 27 pages long for some reason) and using the &lt;code&gt;font-display&lt;/code&gt; CSS property, and… you get the idea. These all &lt;em&gt;accept&lt;/em&gt; the status quo—that fonts cannot load synchronously like your CSS—and just describe ways to mask that.&lt;/p&gt;

&lt;p&gt;But, it's my website, and I know exactly what fonts the user is going to need. So why can't I ask the browser to put a small font onto the critical path before a page displays at all? As an engineer, I find the lack of choice frustrating. 😠&lt;/p&gt;

&lt;p&gt;I don't have a perfect soution, but this post lays out my gripes, a fallback solution via base64 encoding your fonts, and platform suggestion. To start, here's the fundamental issue, shown via animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JLBmyBZd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/emojityper-2020-08-10.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JLBmyBZd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/emojityper-2020-08-10.webp" width="750" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While there's variants on this problem, there's two things happening here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Emojityper" displays with the system font first&lt;/li&gt;
&lt;li&gt;The loaded font is &lt;em&gt;bigger&lt;/em&gt; than the system font—we see &lt;a href="https://web.dev/cls/"&gt;layout shift&lt;/a&gt;, which I'm paid by my employer to tell you is bad (it &lt;em&gt;is&lt;/em&gt; bad, but I'm also paid to tell you)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The status quo solution is to use the &lt;code&gt;font-display&lt;/code&gt; CSS property (and some friends). And to be fair, traditional CSS can solve both of these problems. However, these issues are typically solved by &lt;em&gt;not displaying the offending text&lt;/em&gt; until its font arrives—even though the rest of your page is rendered.&lt;/p&gt;

&lt;p&gt;The most frustrating issue here is that this "flash" takes all of a few frames—maybe 50-60ms. This is the choice I'd like: to delay rendering by a small amount of time. My opinion on this UX is that users are going to be more delighted by a page ready-to-go rather than one effected by a flash that confuses a user's eyes for mere milliseconds. 👀&lt;/p&gt;

&lt;h3&gt;
  
  
  Case Study
&lt;/h3&gt;

&lt;p&gt;On &lt;a href="https://developer.chrome.com"&gt;developer.chrome.com&lt;/a&gt;, we actually inline all of our stylesheets and images (largely SVGs) into each page's HTML in order to reduce the number of requests and make the page load faster. We're really happy with this solution, because for most users, their network is going to deliver that whole &lt;em&gt;single&lt;/em&gt; payload incredibly quickly.&lt;/p&gt;

&lt;p&gt;Despite this sheer duplication of assets across every HTML page, our fonts still go to the network, and new users will still see a flash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading in general
&lt;/h2&gt;

&lt;p&gt;For background on loading, see my &lt;a href="https://whistlr.info/2020/understanding-load/"&gt;recent interactive post&lt;/a&gt;. The TL;DR from that post is that the &lt;em&gt;only&lt;/em&gt; thing that can block a page from rendering is loading external CSS. And for fonts, your browser will asynchronously load a font when glyphs from it are needed—e.g., for the heading font of this blog, that's immediately, but only once the stylesheet has first arrived.&lt;/p&gt;

&lt;p&gt;Here, I'm actually using two tricks to get you the font earlier (although neither prevents the flash and layout shift):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I use &lt;code&gt;&amp;lt;link rel="preload" ... /&amp;gt;&lt;/code&gt; to request the font early, although this only helps if you have an external CSS file (if it's inlined in &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;, the font URL is &lt;em&gt;right there&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;I also send the font via &lt;a href="https://en.wikipedia.org/wiki/HTTP/2_Server_Push"&gt;HTTP2 Server Push&lt;/a&gt; &lt;em&gt;before&lt;/em&gt; any HTML goes to the user, although it seems like browser vendors are removing support for this due to misuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regardless of what you think this post, preloading your font is a good idea. Modern HTTP is very good at sending you lots of files at once, so the earlier your user's font can get on that train, the better. 🚂🚋🚋&lt;/p&gt;

&lt;p&gt;Font files should also be &lt;a href="https://web.dev/love-your-cache/#fingerprinted-urls"&gt;fingerprinted&lt;/a&gt; and cached forever for future loads. I digress, but this loading issue—like so many—is only about the user's 1&lt;sup&gt;st&lt;/sup&gt; load. With the advent of service workers, we as web developers have almost complete control over the user's 2&lt;sup&gt;nd&lt;/sup&gt; load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions, today
&lt;/h2&gt;

&lt;p&gt;This is a tricky one. We can actually include a font inline in your blocking CSS file—by base64 encoding it, which has ~33% space overhead. There's no extra network requests here and decoding is done in a blocking way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@font-face {
  font-family: 'Carter One';
  src: url('data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAG74ABI...') format('woff2');
  font-weight: normal;
  font-style: normal;
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many folks argue that &lt;a href="https://csswizardry.com/2017/02/base64-encoding-and-performance-part-2/"&gt;base64 is a bad idea&lt;/a&gt;. Although, in that case study, the size of the image isn't listed—about 220k—and the author fundamentally disagrees with my assertion that fonts &lt;em&gt;can&lt;/em&gt; be critical resources.&lt;/p&gt;

&lt;p&gt;There is cost here, both in space and decoding time. If you're going to base64 a font to avoid the flash, how can you minimize the cost?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I find that most Latin custom fonts are about ~20k, and I wouldn't base64 anything substantially larger than that—keep it to a single font at most. (I'd use the &lt;a href="https://whistlr.info/2020/system-font/"&gt;system font&lt;/a&gt; for body text, and leave a custom font for your headings or hero text.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put the font declaration in a unique CSS file that's cached forever. Unlike the rest of your CSS, which you might change, the font is not going to change over time.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- These will be downloaded in parallel --&amp;gt;
&amp;lt;link rel="stylesheet" href="./base64-encoded-font-eeb16h.css" /&amp;gt;
&amp;lt;link rel="stylesheet" href="./styles-cakl1f.css" /&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Only ship woff2—&lt;a href="https://caniuse.com/woff2"&gt;95%+ of users&lt;/a&gt; have support&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This is advanced, but if you can control what your user gets on their 2&lt;sup&gt;nd&lt;/sup&gt; load (e.g., via a Service Worker), then you &lt;em&gt;could&lt;/em&gt; serve the user a real, cached woff2 as well and then use only it for repeat loads.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anti-patterns
&lt;/h2&gt;

&lt;p&gt;There are other ways to ensure users don't see any part of your page before the fonts load. But they're going to involve JavaScript and that's just a rabbit hole that increases your site's complexity &lt;em&gt;real fast&lt;/em&gt;. 📈&lt;/p&gt;

&lt;p&gt;You could mark every part of your page as hidden via a CSS class, and then only remove it once you see a font arrive. You could do this via the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API"&gt;Font Loading API&lt;/a&gt; or by literally measuring the rendering size of a test &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; until it changes. These are not good solutions.&lt;/p&gt;

&lt;p&gt;(This is something I happily do on &lt;a href="https://santatracker.google.com"&gt;Santa Tracker&lt;/a&gt;, but we literally have a loading screen, &lt;em&gt;lean in&lt;/em&gt; to a slow load, and the entire site requires JS. It's not suitable for &lt;em&gt;sites&lt;/em&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  A standards plea
&lt;/h2&gt;

&lt;p&gt;Last year, a proposal was made to add &lt;a href="https://wicg.github.io/priority-hints/"&gt;Priority Hints&lt;/a&gt;.&lt;br&gt;
Right now, this proposal is &lt;em&gt;just&lt;/em&gt; for hints about the importance of network traffic.&lt;/p&gt;

&lt;p&gt;But perhaps it could include a hint choice of &lt;code&gt;critical&lt;/code&gt; which informs a browser that this preload &lt;em&gt;may&lt;/em&gt; block page rendering—if it arrives quickly, of course.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- Preload this font and block until used, with limited budget --&amp;gt;
&amp;lt;link rel="preload"
    importance="critical"
    href="/carter-one.woff2?v11"
    as="font"
    type="font/woff2"
    crossorigin /&amp;gt;

&amp;lt;!-- This could work for as="style", as="fetch" or others --&amp;gt;
&amp;lt;link rel="preload"
    importance="critical"
    href="/important-data.json"
    as="fetch"
    crossorigin /&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This would allow for standards-based developer &lt;em&gt;choice&lt;/em&gt;, and because it's a purely additive attribute, would have a sensible fallback for unsupported browsers (i.e., not to block the page at all). There's also a wide range of resources &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attributes"&gt;you can preload&lt;/a&gt;, so it could be a versatile tool. ⚒️&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;I find a lack of control over font loading frustrating, and using base64 for small fonts can help you if this problem frustrates you too. And if you find yourself trying to preload similarly-sized images 🖼️ to make your page work, that's actually one of the biggest signs this approach might help you—to me, that font is just as important as that site logo or navigation button. 🍔&lt;/p&gt;

&lt;p&gt;To be clear, this can be a &lt;a href="https://en.wiktionary.org/wiki/footgun"&gt;footgun&lt;/a&gt;—don't block page loading for minutes because 100k of fonts haven't arrived—use base64 sparingly to avoid a flash or &lt;a href="https://web.dev/cls/"&gt;layout shift&lt;/a&gt;. I don't think it makes sense for every site. I'm not even sure I'm going to implement this strategy on this blog.&lt;/p&gt;

&lt;p&gt;Yet, to revisit the &lt;a href="https://developer.chrome.com"&gt;developer.chrome.com&lt;/a&gt; case study from earlier, where we happily inline images and our stylesheets. I don't think we should inline the fonts directly on the page—they're ~20k files which &lt;em&gt;never change&lt;/em&gt;—but moving them to a synchronous, fingerprinted (and cached forever), stylesheet including just the base64 font may be on the cards.&lt;/p&gt;

&lt;p&gt;➡️ Let me know what you think on &lt;a href="https://twitter.com/intent/tweet?text=Hi%20@samthor%20I%20think%20base64%20is%20the%20worst%20and%20fonts%20should%20not%20be%20critical%20to%20a%20page%20load%20%F0%9F%94%A5"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>font</category>
      <category>webfonts</category>
    </item>
    <item>
      <title>Observing rendered DOM nodes</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Wed, 20 Jan 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/samthor/observing-rendered-dom-nodes-5eim</link>
      <guid>https://dev.to/samthor/observing-rendered-dom-nodes-5eim</guid>
      <description>&lt;p&gt;When building webpages, we create and use HTML elements, but this is often a decidedly one-way interface. And while you can continually &lt;em&gt;request&lt;/em&gt; information about how a node is being rendered through methods like &lt;code&gt;Element.getBoundingClientRect()&lt;/code&gt; or &lt;code&gt;window.getComputedStyle()&lt;/code&gt;, it's difficult to be &lt;em&gt;notified&lt;/em&gt; when an element's render changes.&lt;/p&gt;

&lt;p&gt;This post will explain how to be notified when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an element is added or removed from the DOM&lt;/li&gt;
&lt;li&gt;the bounding box of an element changes (i.e., resizes)&lt;/li&gt;
&lt;li&gt;an element moves around the page for &lt;em&gt;any&lt;/em&gt; reason&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I personally enjoy having these approaches in my toolbox 🛠️ and I hope you find them useful too!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. DOM addition and removal notifications
&lt;/h2&gt;

&lt;p&gt;You might, in your toolbox, want to be informed when a particular DOM node is added or removed from the page. As a digression: for Web Components, this is &lt;a href="https://whistlr.info/2020/understanding-load/#an-alternative"&gt;really easy&lt;/a&gt;—Web Components provide &lt;code&gt;connectedCallback&lt;/code&gt; and &lt;code&gt;disconnectedCallback&lt;/code&gt; methods, which are &lt;em&gt;literally called when the WC is added and removed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Instead, this section is going to talk about doing this for arbitrary 'classic' DOM nodes, such as your friendly neighborhood &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;. There's actually no perfect solution, but read on 👇&lt;/p&gt;

&lt;h3&gt;
  
  
  Using ResizeObserver to track appearance
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ResizeObserver&lt;/code&gt; interface does what it &lt;a href="https://en.wikipedia.org/wiki/Does_exactly_what_it_says_on_the_tin"&gt;says on the tin&lt;/a&gt;: it informs you whether a target element has changed in size. However, a little-known &lt;em&gt;benefit&lt;/em&gt; of this is that it'll also tell you when that element is added or removed from the DOM (see &lt;a href="https://www.w3.org/TR/resize-observer/#intro"&gt;the spec&lt;/a&gt;). This works because an element that's off the page has zero size—but this introduces an interesting caveat.&lt;/p&gt;

&lt;p&gt;If your node has a style of say, &lt;code&gt;display: none&lt;/code&gt; while it's on the page, it already has zero size: so as nothing actually changes when it's added and removed from the DOM. The &lt;code&gt;ResizeObserver&lt;/code&gt; won't be triggered.&lt;/p&gt;

&lt;p&gt;Instead, we can easily track the &lt;em&gt;appearance&lt;/em&gt; of a single DOM node. This looks like:&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="cm"&gt;/**
 * @param {Element} element to track appearance for
 * @param {(appearing: boolean) =&amp;gt; void} callback when appearance changes
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;observeElementAppearing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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;ro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ResizeObserver&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="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBoundingClientRect&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;appearing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appearing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;ro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&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;This is very simple and doesn't require knowledge of the target element's context, such as its peers or parent. For some of my projects, I'm actually &lt;em&gt;happy&lt;/em&gt; with the caveat: an element I'm interested in is gone because it's either off the page &lt;em&gt;or&lt;/em&gt; has zero size. I don't care which one, I'll set up or tear down some code based on that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using IntersectionObserver
&lt;/h3&gt;

&lt;p&gt;If you're worried about browser support, It's worth noting that &lt;code&gt;ResizeObserver&lt;/code&gt; was only added to Safari in March 2020, in a 13.x release. Another helper, &lt;code&gt;IntersectionObserver&lt;/code&gt;, was introduced a year earlier in 12.x, and has slightly wider support among other browsers, too. It's ostensibly for tracking the visibility of elements as they appear in your scroll viewport (to lazy-load images and so on), but it can also be used with arbitrary parent elements.&lt;/p&gt;

&lt;p&gt;In this case, we can actually ask the browser whether an element has any of its size within &lt;code&gt;document.body&lt;/code&gt;, and be informed when that changes:&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="cm"&gt;/**
 * @param {Element} element to track appearance for
 * @param {(appearing: boolean) =&amp;gt; void} callback when appearance changes
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;observeElementAppearing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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;io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;IntersectionObserver&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="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBoundingClientRect&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;appearing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appearing&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="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&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;This code looks almost the same as above, and works the same way—we're not strictly told about removals, but rather, the &lt;em&gt;apperance&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using MutationObserver
&lt;/h3&gt;

&lt;p&gt;There's also a helper called &lt;code&gt;MutationObserver&lt;/code&gt; to help us track changes to DOM nodes. You can &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver"&gt;read about it on MDN&lt;/a&gt;. It &lt;em&gt;replaces&lt;/em&gt; something called &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events"&gt;Mutation events&lt;/a&gt;, a long-since deprecated API with low browser support.&lt;/p&gt;

&lt;p&gt;The main downside for &lt;code&gt;MutationObserver&lt;/code&gt; is that you get a firehouse of all events, as the only way to reliably be informed about page-level changes is to observe the entire &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element. (And if you're interested in Shadow DOM changes, you'll also have to observe individual &lt;code&gt;#shadow-root&lt;/code&gt; nodes.) 🕴️&lt;/p&gt;

&lt;p&gt;You can set up a global &lt;code&gt;MutationObserver&lt;/code&gt; 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;// We don't implement the callback yet.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entries&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="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;mo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;subtree&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="na"&gt;childList&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The callback you receive &lt;em&gt;will&lt;/em&gt; tell you about any nodes added and removed from &lt;code&gt;document.body&lt;/code&gt;. However, it's important to note that only the "parent" of any addition or removal will trigger the callback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0PfL3_QT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/node-mutationobserver.webp" class="article-body-image-wrapper"&gt;&lt;img alt="Animation showing a parent node being removed" src="https://res.cloudinary.com/practicaldev/image/fetch/s--0PfL3_QT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/node-mutationobserver.webp" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What this means in practice is that you'll have to check the &lt;em&gt;descendants&lt;/em&gt; of any node being changed in case you're interested in their status. If you want to get &lt;em&gt;all&lt;/em&gt; individual added and removed nodes, you could traverse them:&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="cm"&gt;/**
 * @param {NodeList} nodes
 * @param {Node[]} out
 * @return {Node[]}
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;traverseAllNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;out&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="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&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;traverseAllNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&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="nx"&gt;out&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;out&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;mo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entries&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;for&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;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&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;allAddedNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;traverseAllNodes&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;addedNodes&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;allRemovedNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;traverseAllNodes&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;removedNodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// do something with added/removed nodes&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;mo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is correct, but could be slow. If you're only interested in a small number of nodes changing, you could instead ignore &lt;code&gt;entries&lt;/code&gt; completely and just check whether a target node &lt;code&gt;.isConnected&lt;/code&gt; whenever the callback is fired.&lt;/p&gt;

&lt;p&gt;⚠️ To be very clear, you cannot directly observe single nodes status in the DOM with &lt;code&gt;MutationObserver&lt;/code&gt;, or even via the &lt;code&gt;childList&lt;/code&gt; of a target node's parent. As the animation above shows, an element might disappear from the page because of something that happened to any of its ancestors.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Bounding box changes
&lt;/h2&gt;

&lt;p&gt;This is really the smallest section of this post, and in many ways, is a superset of my suggested approach above. You can literally just use &lt;code&gt;ResizeObserver&lt;/code&gt;, as—turns out—informing you of an element's resize is its primary goal. This looks like:&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="cm"&gt;/**
 * @param {Element} element to track size
 * @param {(bounds: DOMRect) =&amp;gt; void} callback when size changes
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;observeSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&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;ro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ResizeObserver&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="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;ro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&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;…you could also dispense with the helper method and just use &lt;code&gt;ResizeObserver&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;Something I've often found useful is that it's valid to observe &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; (or &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, which works the same way). This can tell you whether the whole whole page has changed size. 📄&lt;/p&gt;

&lt;p&gt;Unfortunately, &lt;code&gt;ResizeObserver&lt;/code&gt; won't tell you if an element &lt;em&gt;moves&lt;/em&gt;—you can reposition the same DOM node around the page, and if its bounds don't change, this callback won't fire. (Read on!)&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Move observations
&lt;/h2&gt;

&lt;p&gt;As well as resize, you might want to know if an element moves on the page. This can be niché: it's &lt;em&gt;your&lt;/em&gt; webpage, so you likely have a good idea if change you make (like a CSS class or maniplating DOM) will cause a move.&lt;/p&gt;

&lt;p&gt;For me, like the above notifications, this approach is useful to have around in my toolbox when I'm building something tricky. The example I'll use below is that of a tooltip which exists unrelated in the DOM via &lt;code&gt;position: absolute&lt;/code&gt;—I need to keep it positioned adjacent to my button of choice while not sharing any common parts of the element heirarchy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using IntersectionObserver
&lt;/h3&gt;

&lt;p&gt;It's possible to overload the &lt;code&gt;IntersectionObserver&lt;/code&gt; helper to detect moves. I introduced this above, but it's worth restating: if you were to &lt;a href="https://developers.google.com/web/updates/2019/02/intersectionobserver-v2"&gt;read about&lt;/a&gt; this API, you'd believe it's for tracking element visibility—for lazy-loading, or seeing whether users can see your ads and so on. And the most common use case is to determine what proportion of an element is currently visible on the page, expressed as a ratio of its total size.&lt;/p&gt;

&lt;p&gt;But it has a couple of interesting options we can play with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;we can choose a parent element to observe within—by default, &lt;code&gt;IntersectionObserver&lt;/code&gt; uses the scroll viewport, &lt;em&gt;not&lt;/em&gt; a specific element (we used this above to observe within &lt;code&gt;document.documentElement&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;we can set a &lt;code&gt;rootMargin&lt;/code&gt; to expand or constrain the physical space being observed&lt;/li&gt;
&lt;li&gt;we can set a &lt;code&gt;threshold&lt;/code&gt; for callback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By observing &lt;code&gt;document.body&lt;/code&gt; and getting creative with its &lt;code&gt;rootMargin&lt;/code&gt;, we can construct a bounding box which fits around any specific element. If it moves, and our threshold is set to 1.0, we'll be notified—the element starts intersecting 100% with the target range, but as soon as it moves away from the bounding box we'll be triggered—as its visible ratio will go lower than 1.0.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WKIuqcGL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/node-io-hack.webp" class="article-body-image-wrapper"&gt;&lt;img alt="Animation showing creating a bounding box around a specific element before that element moves out of that bounding box" src="https://res.cloudinary.com/practicaldev/image/fetch/s--WKIuqcGL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/node-io-hack.webp" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a couple of nuances here. We also have to keep track of the size of the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; element, because the right and bottom margins in &lt;code&gt;rootMargin&lt;/code&gt; cannot use &lt;code&gt;calc()&lt;/code&gt; (i.e., we cannot use say, the total width or height &lt;em&gt;minus&lt;/em&gt; the offset)—so if it resizes, we have to recreate the &lt;code&gt;IntersectionObserver&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, with that in mind, the code roughly ends up like this (this has some issues, don't just copy and paste it):&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Observe the whole document&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vizObservers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&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;documentResizeObserver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ResizeObserver&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;vizObservers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;fn&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;fn&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;documentResizeObserver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @param {Element} element to observe
 * @param {(rect: DOMRect) =&amp;gt; void} callback on move or resize
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;vizObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&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;io&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refresh&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="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Inform the user that the bounding rect has changed.&lt;/span&gt;
    &lt;span class="c1"&gt;// If it's zero, we can't build an IntersectionObserver.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getBoundingClientRect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Construct the margin in the form "top right bottom left".&lt;/span&gt;
    &lt;span class="c1"&gt;// This needs to be -ve and always rounded _down_.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invertToPx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;px`&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;rootMargin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetWidth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetHeight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invertToPx&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&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="c1"&gt;// Watch for intersection change. Ignore the first update&lt;/span&gt;
    &lt;span class="c1"&gt;// as it should always be 1.0.&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;isFirstUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;entries&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;isFirstUpdate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;isFirstUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="nx"&gt;refresh&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;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;vizObservers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Observe size, since size changes refresh.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ResizeObserver&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;refresh&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;ro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&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;This is a pretty long snippet, but I've tried to add some comments. The core of this is building &lt;code&gt;rootMargin&lt;/code&gt;: we need to find the insets from the sides of the root element, make them negative, and ensure they're rounded down—&lt;code&gt;IntersectionObserver&lt;/code&gt; works on pixel boundaries, but DOM nodes can &lt;em&gt;technically&lt;/em&gt; have floating-point size. 📏&lt;/p&gt;

&lt;p&gt;⚠️ Due to this rounding, it's also possible that we get an initial callback of &lt;code&gt;intersectionRatio&lt;/code&gt; slightly less than one—e.g., &lt;code&gt;0.9991451&lt;/code&gt; or a very high floating-point value. The snippet above doesn't deal with this, but you actually need to recreate the &lt;code&gt;IntersectionObserver&lt;/code&gt; at this point too. Due to the way it works, we're only told once we transition &lt;em&gt;past&lt;/em&gt; any specific threshold—and in this case, we've already transitioned past the &lt;code&gt;1.0&lt;/code&gt; threshold—we won't be called back again—so we need to create it again.&lt;/p&gt;

&lt;p&gt;If you'd like to play more with this, I've built a demo ➡️ &lt;a href="https://codepen.io/samthor/pen/da8b24343b9dcd059cb359392c0df866"&gt;over on Codepen&lt;/a&gt;. I've also pulled out an improved &lt;code&gt;vizObserver&lt;/code&gt; function as a small library &lt;a href="https://gist.github.com/samthor/37a8555218e8a69ebf9f1eaf20f751db"&gt;you can find on GitHub&lt;/a&gt;. It's also worth noting that the way we track moves, by necessity, &lt;em&gt;also&lt;/em&gt; ends up informing you about element resize and appearance (#1 and #2).&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;These raw primitives &lt;code&gt;IntersectionObserver&lt;/code&gt; and &lt;code&gt;ResizeObserver&lt;/code&gt; are very powerful and help us keep track of new and interesting things in ways that weren't possible before. They're largely supported by evergreens, although as of writing, &lt;code&gt;ResizeObserver&lt;/code&gt; has slightly less support—it wasn't available &lt;a href="https://caniuse.com/resizeobserver"&gt;until a 13.x release of Safari&lt;/a&gt;. That's about 15% of Safari users you can't support, although personally, I'll be embracing &lt;code&gt;ResizeObserver&lt;/code&gt; in my web projects in 2021 anyway.&lt;/p&gt;

&lt;p&gt;For me, I'll be using these primitives in a few ways, but I hope you find them useful in others, too. My use-case is mostly my last example: I want to align tooltips to arbitrary elements—which I don't want to directly pierce into, as I've written a good abstraction—even though they have no DOM in common. By keeping track of an element's position and size, I can ensure that the tooltip correctly "follows" the target.&lt;/p&gt;

&lt;p&gt;Thanks for reading! Let me know &lt;a href="https://twitter.com/samthor"&gt;on Twitter&lt;/a&gt; what you think. 🐦&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>Check your JS with TS</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Mon, 11 Jan 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/samthor/check-your-js-with-ts-3nfm</link>
      <guid>https://dev.to/samthor/check-your-js-with-ts-3nfm</guid>
      <description>&lt;h1&gt;
  
  
  Check your JS with TS
&lt;/h1&gt;

&lt;p&gt;TypeScript is great and its static analysis and typechecking can help you be more productive, but swapping to TS wholesale isn't possible for many projects. You may also want to keep your project as pure JS, if, like me, you like not compiling when testing in your browser (just serve your files as ESM and let the browser do it).&lt;/p&gt;

&lt;p&gt;So, you want check your JS—and this post uses ES Modules, or ESM—with TS tools. Great! This post has three levels of complexity, so read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic: Inline types in VSCode
&lt;/h2&gt;

&lt;p&gt;As you hover over symbols in VSCode, you'll see inferred type information: for constants and so on you'll see &lt;code&gt;string&lt;/code&gt; and &lt;code&gt;number&lt;/code&gt;. This is the type that TS can safely guess. (If you see &lt;code&gt;any&lt;/code&gt;, this means that TS can't work out what type you're using.)&lt;/p&gt;

&lt;p&gt;You can fill in the gaps here with JSDoc comments which add types. There's a number of ways to specify them:&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="cm"&gt;/** @type {number[]} */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="c1"&gt;// otherwise TS thinks this is 'any[]'&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * @param {Element} bar
 * @param {?Element} barOrNull
 * @return {Promise&amp;lt;void&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fooMethod&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="nx"&gt;barOrNull&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// do something with bar/barOrNull&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {(arg: number) =&amp;gt; void} */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&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="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// this is a _cast_, not a declaration: you need to wrap in parens ()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nowIsNumberType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/** @type {number} */&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;someExternalAny&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within any &lt;code&gt;@type {...}&lt;/code&gt;, you can use a combination of &lt;a href="https://www.typescriptlang.org/docs/handbook/basic-types.html"&gt;TypeScript's type system&lt;/a&gt; as well as much of &lt;a href="https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System"&gt;JSDoc&lt;/a&gt;. The possibilities for types are out of scope of this post.&lt;/p&gt;

&lt;p&gt;So—this is fine, but all it gets you is helpful information when you hover over a type or you're trying to autocomplete. Let's get more useful feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intermediate: Write TSConfig
&lt;/h2&gt;

&lt;p&gt;If you create a custom "tsconfig.json" file in the root of your project, you can enable warnings and errors for your project. The file should look something like this:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&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="nl"&gt;"checkJs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noEmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you'd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;warn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you're&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;modern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;features&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;change&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;these&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;both&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;e.g.&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es2017"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esnext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esnext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;configure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;like:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;these&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;my&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;preferred&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;defaults!&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skipLibCheck"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"forceConsistentCasingInFileNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"strict"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;implies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you'll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you're&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ready:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;huge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reason&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;complaining&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"noImplicitAny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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="nl"&gt;"include"&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you'd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;here&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"src/**/*.js"&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="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;You can also use a &lt;a href="https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#tsconfig-bases"&gt;base config&lt;/a&gt; but you'll still need to specify &lt;code&gt;include&lt;/code&gt; as well as the top two &lt;code&gt;compilerOptions&lt;/code&gt; to ensure we just check JS files.&lt;/p&gt;

&lt;p&gt;⚠️ Keen observers might also notice that I've included JS-style comments inside the JSON &lt;em&gt;as well&lt;/em&gt; as my favourite syntax feature, trailing commas. TypeScript seems to be completely fine with this extended syntax.&lt;/p&gt;

&lt;h3&gt;
  
  
  For free: VSCode
&lt;/h3&gt;

&lt;p&gt;Once you create your "tsconfig.json" and make sure it matches your source files, you'll notice something amazing: VSCode will now just start warning you about problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Z2G9iJR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/vscode-warning.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Z2G9iJR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/vscode-warning.png" alt="VSCode showing a warning about an unused variable" width="880" height="364"&gt;&lt;/a&gt;VSCode can now show JS warnings, not even just about types&lt;/p&gt;

&lt;p&gt;To be clear: I didn't install any TypeScript tooling to make this happen, it was just implicitly part of VSCode. Nice! 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Command-line: TSC
&lt;/h3&gt;

&lt;p&gt;You can also now run TypeScript via the command-line to get warnings and errors for your whole project, even if it's not compiling your code. Install the NPM package and run its command-line compiler (which will just check, since we set &lt;code&gt;noEmit&lt;/code&gt; above):&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="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript
&lt;span class="nv"&gt;$ &lt;/span&gt;tsc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your project has errors—and trust me, for any project where you've not typechecked before, you'll have them—this will print all of them and exit with a non-zero status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced: Write/use types
&lt;/h2&gt;

&lt;p&gt;It's all well and good to use types like &lt;code&gt;number&lt;/code&gt; and &lt;code&gt;string[]&lt;/code&gt;, but what if you'd like to define your own types—such as a complex interface type with many properties? There's actually many ways to do this in TypeScript, and some background is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can &lt;a href="https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html"&gt;use the triple-slash syntax&lt;/a&gt; to bring in or reference other types&lt;/li&gt;
&lt;li&gt;You're able to &lt;code&gt;import&lt;/code&gt; type files, although this only makes sense to TS: not your browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the first approach is useful for say, external types—you might depend on something in NPM's &lt;code&gt;@types&lt;/code&gt; repo or a built-in library—the second is my preferred option for your ESM projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Import your types
&lt;/h3&gt;

&lt;p&gt;If you create a file like "types.d.ts", you can actually import it as "types.js" (and VSCode can suggest this in an autocomplete). TypeScript actually &lt;em&gt;prevents&lt;/em&gt; you from importing the ".d.ts" directly-you &lt;em&gt;must&lt;/em&gt; pretend that it is a JS file. But the JS file doesn't actually exist—how can this interop with other tools and loading in your browser?&lt;/p&gt;

&lt;p&gt;Turns out, we can just create two files: one "types.d.ts" for types, and one "types.js" that's actually just empty. These two files might look like:&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;//&lt;/span&gt;
&lt;span class="c1"&gt;// @file types.js&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// This is an empty file so that browsers and tooling doesn't complain.&lt;/span&gt;

&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// @file types.d.ts&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="cm"&gt;/**
 * This isn't a real class, it just defines an expected object type.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ArgForSomething&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bar&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="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * We can define functions, too.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;exportedFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ArgForSomething&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to use the code, in a regular JS file:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;types&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;./types.js&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 {types.ArgForSomething} arg
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&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;span class="cm"&gt;/**
 * If you export a function from your types, you can also just reference it
 * wholesale: this might be useful if you're publishing to NPM.
 *
 * @type {types.exportedFunction}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;exportedFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&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;Voila—type information!&lt;/p&gt;

&lt;p&gt;Importantly, when you're bundling or compiling, tools will hide the dummy, empty file. And during development, the file technically exists, but is ignored since it's empty anyway and is only referenced inside your comments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other approaches
&lt;/h3&gt;

&lt;p&gt;I mention the classic approach for completeness, but this post is really about treating ".d.ts" files as modules. Skip this section unless you're really interested.&lt;/p&gt;

&lt;p&gt;So, you can reference other files in your own project using the triple-slash syntax. However, it doesn't mesh well with modules: you can't see anything that had &lt;code&gt;export&lt;/code&gt; on it in that referenced file, and everything &lt;em&gt;else&lt;/em&gt; will be brought into the global namespace. (There's exceptions here too, and it's just more complex than treating it as an ES Module.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Export types for others
&lt;/h3&gt;

&lt;p&gt;If you're not publishing to NPM, you can stop reading. But if you are building something that can be consumed further, then read on.&lt;/p&gt;

&lt;p&gt;By default, TypeScript looks for the "index.d.ts" file in your project's root directory to provide types for the users of your package. In the example above, I've intentionally &lt;em&gt;not&lt;/em&gt; used that name as I think creating an empty peer "index.js" in the top-level of your project is probably going to lead to confusion. I like specifically calling it "types".&lt;/p&gt;

&lt;p&gt;You can specify a path to your types in "package.json". (Turns out, TypeScript recommends you do this &lt;em&gt;anyway&lt;/em&gt;, even if the file is the default name.) This looks like:&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="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;"your-awesome-package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"path/to/types.d.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exports"&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="nl"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./main-module.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"require"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./main-require.cjs"&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;This types file should match your top-level export file. It can &lt;code&gt;import&lt;/code&gt; further ".d.ts" files (and these &lt;em&gt;don't&lt;/em&gt; need a dummy peer JS file) and even re-export them.&lt;/p&gt;

&lt;p&gt;⚠️ As of writing, TypeScript &lt;a href="https://github.com/microsoft/TypeScript/issues/33079"&gt;doesn't support&lt;/a&gt; &lt;a href="https://nodejs.org/api/packages.html#packages_conditional_exports0"&gt;subpath exports&lt;/a&gt;. There's some workarounds in that thread.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Your JavaScript can benefit from TS' static typechecking and errorchecking abilities. It can also reveal a whole bunch of errors or risky behavior you didn't know you had—but hey, that's progress for you. The tools you regularly use—including VSCode, but command-line "tsc" too—are &lt;em&gt;so&lt;/em&gt; close to being incredibly useful, even for pure JS, and by giving them the right config, you can get a lot more data.&lt;/p&gt;

&lt;p&gt;And of course, while static analysis is great, it's also not a replacement for good tests. Go forth and check your code!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>compile</category>
    </item>
    <item>
      <title>The System Font</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Thu, 06 Aug 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/samthor/the-system-font-50kj</link>
      <guid>https://dev.to/samthor/the-system-font-50kj</guid>
      <description>&lt;p&gt;&lt;em&gt;tl;dr: as of August 2020, use the following snippet to get the system font on your webpages, on most platforms, with sane fallbacks.&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Segoe&lt;/span&gt; &lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;-apple-system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;sans-serif&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;If you'd like to know more about how I got to this line, read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;There's been a recent, major development in web fonts. The "system" font family is now widely supported—it came on the scene, technically in about 2015, and was supported by most browsers in about 2017. And a few years later, in 2020, it's incredibly common to see this used.&lt;/p&gt;

&lt;p&gt;What is the system font? If you use it, your webpage will display like the platform it's being read on. I'm not a designer, but I find it visually appealing and it helps simple webpages fit in well with their environment. If you're reading this article on &lt;a href="https://whistlr.info"&gt;https://whistlr.info&lt;/a&gt;, you are in fact reading it in the system font! 🤯&lt;/p&gt;

&lt;p&gt;There's some &lt;a href="https://nathanfriend.io/2018/04/27/fantastic-fast-fonts-with-system-ui.html"&gt;great&lt;/a&gt; &lt;a href="https://medium.design/system-shock-6b1dc6d6596f"&gt;articles&lt;/a&gt; out there that have comparison screenshots. This &lt;em&gt;does not&lt;/em&gt; impact font loading—the point is these fonts are already on your user's machine, and are just a good replacement for classic "Safe Web Fonts", e.g., &lt;code&gt;Arial&lt;/code&gt; and &lt;code&gt;Times New Roman&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So: Chrome, Chromium-based browsers and Safari have shipped the system font as &lt;code&gt;system-ui&lt;/code&gt;. You use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* don't just do this: read on! */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, like all things on the web, it's never as simple as that.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Practice
&lt;/h2&gt;

&lt;p&gt;Firefox is still yet &lt;a href="https://caniuse.com/#feat=font-family-system-ui"&gt;to come to the party&lt;/a&gt;, instead only supporting the system font on Mac, and under a specific name. Additionally, it's been observed that—surprise surprise—on Windows, the &lt;code&gt;system-ui&lt;/code&gt; font can in fact &lt;a href="https://infinnie.github.io/blog/2017/systemui.html"&gt;end up resolving to literally anything&lt;/a&gt;. I suspect the reason no-one noticed this 'issue' is that on every other platform, this system font is fixed, and designers tend not to use Windows. 🔥&lt;/p&gt;

&lt;p&gt;In practice, this means most sites will end up with an ungodly mess of CSS like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* from GitHub, August 2020 */&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-apple-system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;BlinkMacSystemFont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Segoe&lt;/span&gt; &lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Helvetica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Apple&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="n"&gt;Emoji&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Segoe&lt;/span&gt; &lt;span class="n"&gt;UI&lt;/span&gt; &lt;span class="n"&gt;Emoji&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;…which serves mostly to &lt;em&gt;avoid&lt;/em&gt; using &lt;code&gt;system-ui&lt;/code&gt;, and instead has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-apple-system&lt;/code&gt; for Safari and Firefox; plus &lt;code&gt;BlinkMacSystemFont&lt;/code&gt;, the same for Chrome&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Segoe UI&lt;/code&gt; for Windows (originally included with Windows Vista, so it has great coverage)&lt;/li&gt;
&lt;li&gt;some "Safe Web Fonts" for very old browsers: &lt;code&gt;Helvetica&lt;/code&gt; and &lt;code&gt;Arial&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sans-serif&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and finally, support for emoji (this isn't really needed—I'll get to this later, but it could have a whole blogpost on its own).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub's code excludes a pretty default for Linux. Various distributions of Linux have their &lt;em&gt;own&lt;/em&gt; fonts: Ubuntu has &lt;a href="https://fonts.google.com/specimen/Ubuntu"&gt;Ubuntu&lt;/a&gt;, and Red Hat has… unsurprisingly, &lt;a href="https://fonts.google.com/specimen/Red+Hat+Display"&gt;Red Hat&lt;/a&gt;. I've included GitHub's code as it's pretty good, but… it could be simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just Tell Me What To Do
&lt;/h2&gt;

&lt;p&gt;Sorry, I know this is why people read articles. My general advice for system font loading in August 2020 is to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Segoe&lt;/span&gt; &lt;span class="n"&gt;UI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;system-ui&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;-apple-system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;sans-serif&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;This works basically by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;specifying &lt;code&gt;Segoe UI&lt;/code&gt; for everyone on Windows: this works for modern browsers &lt;em&gt;as well as&lt;/em&gt; IE11 and legacy Edge&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;system-ui&lt;/code&gt; for most modern browsers on macOS, Android, iOS, Linux, Chrome OS, …&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;-apple-system&lt;/code&gt; for Firefox on macOS, and a &lt;em&gt;tiny&lt;/em&gt; fraction of older Safari versions&lt;/li&gt;
&lt;li&gt;finally, falling back to &lt;code&gt;sans-serif&lt;/code&gt; for all other cases, including ancient browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hooray! 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  Some Notes
&lt;/h2&gt;

&lt;p&gt;So there's always some notes. These are pretty obscure, and I don't think they should stop you using the snippet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sans-Serif Fallback
&lt;/h3&gt;

&lt;p&gt;The fallback will apply to ancient browsers. It'll probably map to a font like &lt;code&gt;Arial&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fallback will also be used by Firefox on Linux and Android. Some guides actually suggest using a longer list of fonts—e.g., &lt;code&gt;Ubuntu&lt;/code&gt;, &lt;code&gt;Oxygen&lt;/code&gt;—to catch all possible Linux distributions. But, in my limited testing, the default &lt;code&gt;sans-serif&lt;/code&gt; provided by Ubuntu and other distributions are quite appealing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Emoji
&lt;/h3&gt;

&lt;p&gt;On macOS and Linux—both Firefox and Crome—the system font has some problems with old emoji. It doesn't matter if you use &lt;code&gt;-apple-system&lt;/code&gt; or &lt;code&gt;system-ui&lt;/code&gt;: the same problems occur. If this is a black and white emoji [✈️️], then congratulations, you're expericing the issue!&lt;/p&gt;

&lt;p&gt;The airplane emoji above includes what's known as &lt;a href="https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)"&gt;a VS16 character&lt;/a&gt;, indicating that it should be rendered in color. The airplane actually predates modern emoji, so without the VS16, it renders in black-and-white. However, the system font—in these cases—isn't respecting the VS16.&lt;/p&gt;

&lt;p&gt;Confusingly, emoji like the telephone—which also require a VS16 character—work fine. Here's the phone with a VS16 (☎️), and without (☎). This implies to me that this is a bug in either the effected browsers or the platforms, or both. (Safari has no such issues on Mac.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In my experience with &lt;a href="https://emojityper.com"&gt;Emojityper&lt;/a&gt; and friends, to guarantee emoji support for &lt;em&gt;all&lt;/em&gt; emoji, you need to specify actual fonts and can't rely on the system font.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sites like GitHub, which embrace the system font, have a secret: they replace all their emoji with images anyway. This is probably just to support even the newest emoji on all platforms—but it has the effect of 'fixing' this underlying issue with &lt;code&gt;system-ui&lt;/code&gt; too.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an aside, if you include &lt;code&gt;Segoe UI Emoji&lt;/code&gt; in the font list above, Windows will display text in emoji form even without the VS16 character. This is &lt;em&gt;probably&lt;/em&gt; against Unicode's specification.&lt;/p&gt;

&lt;h3&gt;
  
  
  Segoe UI
&lt;/h3&gt;

&lt;p&gt;Finally, a note about &lt;code&gt;Segoe UI&lt;/code&gt;. Technically, as I list it first, it could be possible for it to win out—even on Mac or mobile where it doesn't fit—if a user has installed the font locally. However, it's against Microsoft's font policy to redistribute &lt;code&gt;Segoe UI&lt;/code&gt;, so in practical terms it should &lt;a href="https://docs.microsoft.com/en-us/typography/fonts/font-faq#web"&gt;never be available&lt;/a&gt; anywhere but on Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. Some thoughts, including on design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use the short blurb of CSS included at the top of the article to use the system font.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Personally, I find the best place for the system font is in simple pages—like this blog!—where there's no huge visual style or theme and you'd like to match the platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't be afraid to use another font for contrast, even if you're including that font via CSS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And while it's out of scope of this post, &lt;a href="https://davidwalsh.name/font-smoothing"&gt;read up&lt;/a&gt; on CSS properties like &lt;code&gt;-webkit-font-smoothing&lt;/code&gt; and &lt;code&gt;text-rendering&lt;/code&gt; to tweak your fonts until they're just right.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>html</category>
      <category>font</category>
    </item>
    <item>
      <title>Control Loading Spinner in HTML</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Tue, 21 Apr 2020 06:48:33 +0000</pubDate>
      <link>https://dev.to/samthor/control-loading-spinner-in-html-2eof</link>
      <guid>https://dev.to/samthor/control-loading-spinner-in-html-2eof</guid>
      <description>&lt;h1&gt;
  
  
  Control Loading Spinner in HTML
&lt;/h1&gt;

&lt;p&gt;If your web experience involves real HTML pages and actual &lt;code&gt;&amp;lt;a href="..."&amp;gt;&lt;/code&gt; links, navigation will happen as a normal browser action: your users will see some kind of loading indicator or spinner that's built-in to the platform.&lt;/p&gt;

&lt;p&gt;However, if you've adopted the &lt;a href="https://en.wikipedia.org/wiki/Single-page_application"&gt;single-page application&lt;/a&gt; idiom, this won't happen for free. You'll typically be loading new data with a &lt;code&gt;fetch()&lt;/code&gt; and then hydrating your page with the content. This is pretty common for app-like experiences, and lots of even pure content sites do it too—&lt;a href="https://web.dev"&gt;web.dev&lt;/a&gt; to name one.&lt;/p&gt;

&lt;p&gt;It's time to show you how to fake this experience. I don't endorse this for production, but it's a cool demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Demo
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;
    &lt;br&gt;
      &lt;br&gt;
    &lt;br&gt;an early version of this demo for users &lt;a href="https://whistlr.info/2020/control-loading-spinner"&gt;not on Whistlr&lt;/a&gt;
    &lt;br&gt;
  &lt;/p&gt;

&lt;p&gt;&lt;small&gt;⚠️ This is broken on iOS, but I'm looking into a workaround.&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;Neat, huh? Let's dissect how it works.&lt;/p&gt;
&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Firstly: I'll admit it. I am actually loading a new page that does have a real URL. But instead of your natural instinct to reply to HTTP requests as fast as possible, we instead delay its completion until a point in the future that we control.&lt;/p&gt;

&lt;p&gt;When the request finally resolves, reply with a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204"&gt;HTTP 204&lt;/a&gt;. Importantly, and to quote MDN:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The HTTP &lt;code&gt;204 No Content&lt;/code&gt; success status response code indicates that the request has succeeded, but that the client doesn't need to go away from its current page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With these two parts holding hands together—like a knife 🔪 and a wrench 🔧—we can create the illusion of loading, while not actually doing anything at all.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using a Service Worker
&lt;/h3&gt;

&lt;p&gt;The easiest–but not the only—way to integrate this with your existing site is to rely on a Service Worker. We can add a &lt;code&gt;fetch&lt;/code&gt; handler that accepts a request to e.g., "/204_delay?key=...", and lets you finish the request later. Something like:&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="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fetch&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;e&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="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/204_delay&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&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;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&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;await&lt;/span&gt; &lt;span class="k"&gt;new&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;r&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;activeLoads&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;=&lt;/span&gt; &lt;span class="nx"&gt;r&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;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&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;We'll also need to define &lt;code&gt;activeLoads&lt;/code&gt; and a way to invoke them—perhaps via a message:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeLoads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&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;e&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;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeLoads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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="k"&gt;delete&lt;/span&gt; &lt;span class="nx"&gt;activeLoads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;r&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;Now, on our foreground page, if we've got a valid service worker, your async loading code can be wrapped like:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;loadPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;controller&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&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;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// cause a real but fake navigation&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/204_delay?key=&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="s2"&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;await&lt;/span&gt; &lt;span class="nx"&gt;loadDataAndUpdatePage&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;controller&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// tell the SW to finish the navigation&lt;/span&gt;
    &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&lt;/span&gt;&lt;span class="dl"&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="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;And, as I mentioned: your browser will actually load a new page, with a real URL. But as it's a 204, nothing will change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a Custom Server
&lt;/h3&gt;

&lt;p&gt;The demo in this blog post talks to a real webserver which acts in exactly the same way as the Service Worker, above. One request "hangs" until another request causes the server to complete the first.&lt;/p&gt;

&lt;p&gt;It's much more dangerous: if there's a network error or your client is offline, the request to "/204_delay" will &lt;em&gt;actually&lt;/em&gt; fail, showing an error message and closing your site. And by the time a navigation has occured—which includes if a user clicks on a real link, we as JS developers can't actually stop that intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;loadPage()&lt;/code&gt; example above shouldn't be run in parallel, as your browser can only handle one navigation at once.&lt;/p&gt;

&lt;p&gt;Some browsers—and of the big three, that's currently just Safari—will treat navigation as a signal to stop active network requests. Navigation does actually cause &lt;a href="https://dev.to/chromiumdev/sure-you-want-to-leavebrowser-beforeunload-event-4eg5"&gt;&lt;code&gt;beforeunload&lt;/code&gt; to be fired&lt;/a&gt;, and that means &lt;code&gt;fetch()&lt;/code&gt; will fail. You can work around this by fetching data you need &lt;em&gt;inside&lt;/em&gt; your Service Worker, as &lt;code&gt;postMessage&lt;/code&gt; is still allowed. But this isn't a very natural way to fetch content.&lt;/p&gt;

&lt;p&gt;Safari will also stop DOM changes from being rendered, even in the same frame as navigation starts. In practice, this means that for the demo above, I create the "Loading..." text and then only cause our faux-navigation to start inside a &lt;code&gt;requestAnimationFrame&lt;/code&gt; handler.&lt;/p&gt;

&lt;p&gt;Finally, in the example above, our Service Worker might end up leaking memory if say, navigation is cancelled by the user closing their page. This is solvable but is tricky because it seems like &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/clientId"&gt;&lt;code&gt;clientId&lt;/code&gt;&lt;/a&gt; isn't available everywhere. Chrome only provides &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/resultingClientId"&gt;&lt;code&gt;resultingClientId&lt;/code&gt;&lt;/a&gt;, which will be the client if the navigation completes—except we can never &lt;em&gt;navigate&lt;/em&gt; to a 204. In practice, you should probably just implement a timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parting Thoughts
&lt;/h2&gt;

&lt;p&gt;You could also pretend to load by rapidly animating your favicon. But that wouldn't be nearly as satisfying as triggering a native-like experience, especially as the favicon is not the only source of loading UX.&lt;/p&gt;

&lt;p&gt;And as I mentioned all the way at the top, &lt;strong&gt;I don't condone this approach&lt;/strong&gt;. &lt;a href="https://twitter.com/samthor"&gt;Don't @-me&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Async Generators for User Input</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Wed, 15 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/samthor/async-generators-for-user-input-3jbg</link>
      <guid>https://dev.to/samthor/async-generators-for-user-input-3jbg</guid>
      <description>&lt;h1&gt;
  
  
  Async Generators for User Input
&lt;/h1&gt;

&lt;p&gt;It's possible to build JS, on the web, with a native-like event loop.This is thanks to async generators and the &lt;code&gt;await&lt;/code&gt; keyword—are &lt;em&gt;you&lt;/em&gt; sick of a twisty maze of &lt;code&gt;addEventListener&lt;/code&gt;, timeouts and global state?Read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Generators allow us to suspend normal program flow as the interpreter &lt;em&gt;jumps&lt;/em&gt; to/from your &lt;code&gt;yield&lt;/code&gt; statements:&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="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&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="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;between&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;for&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;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;generator&lt;/span&gt;&lt;span class="p"&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="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&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;This program will print out "1", "between", and "2".Used at face value, this primitive allows you to create something which vaguely looks like an array.&lt;/p&gt;

&lt;p&gt;This isn't all, though: from Chrome 63 &lt;a href="https://caniuse.com/#feat=mdn-javascript_functions_method_definitions_async_generator_methods"&gt;and friends&lt;/a&gt;, you can perform asynchronous work between each &lt;code&gt;yield&lt;/code&gt; statement (to be fair, you could already &lt;code&gt;yield&lt;/code&gt; a &lt;code&gt;Promise&lt;/code&gt;).The syntax isn't too different, either: just add &lt;code&gt;async&lt;/code&gt; to your function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Event Loop
&lt;/h2&gt;

&lt;p&gt;Most documentation about &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop"&gt;JS' event loop&lt;/a&gt; correctly identifies it as event-driven.This is the normal JS model—if you &lt;code&gt;addEventListener&lt;/code&gt;, your handler is called, and is expected to complete synchronously.&lt;/p&gt;

&lt;p&gt;Instead, let's aim for something more akin to a native event loop, which you could use like:&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&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;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&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;event&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// If the user clicked on something, wait for their result.&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getUserInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rate your experience:&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="k"&gt;else&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keydown&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="c1"&gt;// Submit the form&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Enter&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="c1"&gt;// TODO: ...&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is basically implementing a state machine—&lt;a href="https://en.wikipedia.org/wiki/Deterministic_finite_automaton"&gt;a DFA&lt;/a&gt;, where the states are controlled by user input.This is especially userful for complex user interactions—like forms, or games.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
    &lt;br&gt;
  a &lt;a href="https://twitter.com/samthor/status/1249631219563675651"&gt;train game&lt;/a&gt; with two input states: choosing an edge, and drawing a track&lt;/p&gt;

&lt;p&gt;There's a few decisions you might have to make about the input, though.&lt;/p&gt;

&lt;p&gt;As you're now consuming time—potentially &lt;em&gt;asynchronously&lt;/em&gt; via &lt;code&gt;await&lt;/code&gt;—to process each event, it's unlikely that your code will be able to handle every event as it arrives.For example, if you're processing &lt;code&gt;click&lt;/code&gt; events but you do a network round-trip, a user might generate more clicks before the first event is done.This might be intentional, &lt;em&gt;but you'll need to decide&lt;/em&gt; what's important to queue up for processing later.&lt;/p&gt;

&lt;p&gt;What does the &lt;code&gt;loop&lt;/code&gt; object look like, then?Well, you can build a generator and a helper to push events into it:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;buildEventManager&lt;/span&gt;&lt;span class="p"&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;resolve&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="c1"&gt;// (there's no arrow function syntax for this)&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;generator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// there's nothing in the queue, wait until push()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&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;r&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;resolve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shift&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="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="c1"&gt;// allow the generator to resume&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;generator&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;This is a bit of code, but it basically just has two parts and a connection between them.First, a &lt;code&gt;push()&lt;/code&gt; method, which lets you control what events are handled, and pushes them into the queue.Secondly, a generator—which we run, and return as &lt;code&gt;loop&lt;/code&gt;—which waits for events to appear, and uses &lt;code&gt;yield&lt;/code&gt; to provide the next available one.&lt;/p&gt;

&lt;p&gt;To use it purely to keep a queue of all the pointer events that occur, try 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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buildEventManager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pointermove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await&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;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&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="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouse now at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenY&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;This simple example just queues &lt;em&gt;everything&lt;/em&gt;, rather than trying to e.g., only provide the most recent movement event.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Just User Events
&lt;/h3&gt;

&lt;p&gt;One of the benefits of a generic event loop is that we can process any sort of event we imagine, not just user-generated ones.For example, we could push some custom events and process them in your event loop inline with everything else:&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setInterval&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tick&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="mi"&gt;1000&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;ro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ResizeObserver&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;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resizeElement&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;ro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;someElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… of course, you're not just limited to custom events (&lt;code&gt;push&lt;/code&gt; accepts &lt;em&gt;any&lt;/em&gt; object), but this might match the rest of the inputs you're processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Machines, Oh My
&lt;/h3&gt;

&lt;p&gt;I mentioned that this native-like event loop helps us create state machines.If you just have a single event loop, that's not really true, because you might still have to manage global state yourself.Instead, you can actually use the loop many times.&lt;/p&gt;

&lt;p&gt;Unfortunately, using &lt;code&gt;for await (...)&lt;/code&gt; actually doesn't work here, as you're seemingly not able to use it more than once (I may need to read the ECMAScript spec to find out why).Instead, we can use a generator's &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next"&gt;&lt;code&gt;.next()&lt;/code&gt; method&lt;/a&gt;:&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="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for&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="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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="k"&gt;continue&lt;/span&gt;&lt;span class="p"&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="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start line at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&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="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;innerEvent&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&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;innerEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draw line to&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;innerEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&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="p"&gt;}());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we wait for one click, then another.It's a simple example, but shows how you might build up some interesting state—you've started at the default state, then moved to the "waiting for a second point" state, then you're able to draw a line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Digression
&lt;/h2&gt;

&lt;p&gt;As a digression, while it's &lt;em&gt;not an error&lt;/em&gt; to write something like:&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="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/foo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="c1"&gt;// update the DOM&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;… it is basically an antipattern because the &lt;code&gt;async&lt;/code&gt; keyword masks some possible issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You could end up handling the event several times in parallel, e.g., submitting a form many times before it's complete&lt;/li&gt;
&lt;li&gt;Errors aren't caught anywhere (they would appear as an "unhandled promise rejection")&lt;/li&gt;
&lt;li&gt;Importantly, it &lt;em&gt;appears&lt;/em&gt; like the event handler is synchronous and blocking, even though it's not&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can work around the 1&lt;sup&gt;st&lt;/sup&gt; issue with something like &lt;a href="https://dev.to/chromiumdev/cancellable-async-functions-in-javascript-5gp7"&gt;cancellable async functions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What I've covered here is an incredibly basic example of processing events in a queue.You'd want to inspire from these ideas and write something appropriate for your use-case: I know I will for my web-based games.&lt;/p&gt;

&lt;p&gt;One key difference from actual native event loops is that on the web, we can't (and probably don't want to) turn off every bit of built-in behavior your browser provides for you.I'm not suggesting that you handle every keystroke in an &lt;code&gt;&amp;lt;input type="text"&amp;gt;&lt;/code&gt;, or overload every click event.But this lets you control perhaps a tricky component which transitions through a bunch of states, especially if it has complex user interactions—keyboard, mouse, and so on.&lt;/p&gt;

&lt;p&gt;Finally, there's a &lt;a href="https://www.google.com/search?q=async+generators+state+machines"&gt;lot of reading out there&lt;/a&gt; about state machines via async generators—that's great, and I'm glad to see many folks taking advantage of this idea.I've not seen a huge amount of writing about processing user events, though, and that's where I want you to consider the possibilities.&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>Node.js Streams &amp; Object Mode</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Fri, 10 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/samthor/node-js-streams-object-mode-21af</link>
      <guid>https://dev.to/samthor/node-js-streams-object-mode-21af</guid>
      <description>&lt;h1&gt;
  
  
  Node.js Streams &amp;amp; Object Mode
&lt;/h1&gt;

&lt;p&gt;Streams in Node.js serve two purposes.The first, more commonly documented use-case is that of reading and processing bytes a 'chunk' at a time: bytes which most commonly come to/from your local disk, or are being transferred over a network.Secondly, you have &lt;code&gt;{objectMode: true}&lt;/code&gt;, which I'll explain later.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;tl;dr: Streams for bytes are rarely useful, and &lt;code&gt;objectMode&lt;/code&gt; can be made better &lt;a href="https://www.npmjs.com/package/async-transforms" rel="noopener noreferrer"&gt;through parallelism&lt;/a&gt; (which most libraries don't take advantage of).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunks of Data
&lt;/h2&gt;

&lt;p&gt;The classic use of stream works with files or file-like objects directly.For example, you might read, compress and write out a file, which looks a bit like:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs&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="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createGzip&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;zlib&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;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image.tar&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;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createGzip&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;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image.tar.gz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// now connect! \o/&lt;/span&gt;
&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without streams, you'd have to read the whole file at once rather than processing it in, well, a &lt;em&gt;stream&lt;/em&gt; of smaller chunks.So, you basically want this for one of two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a file is enormous so loading it all into memory at once is wasteful&lt;/li&gt;
&lt;li&gt;or; your task involves a network in any way (so you can send or use bytes immediately)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interacting with the network is a natural fit for streams, because the network is slow.You don't want to block until a whole file is a in memory, and only &lt;em&gt;then&lt;/em&gt; send it over a relatively slow connection to your users.&lt;/p&gt;

&lt;p&gt;However, when you're just processing files as part of a script, streams are rarely useful.Tasks like compression make great examples, as their algorithms just don't need the whole file at once.But anything outside this definition doesn't work this way—think compiling source files, resizing images—in places where the full context is required, streams don't make sense.&lt;/p&gt;

&lt;p&gt;To look at a popular build tool, Gulp, only one of its &lt;a href="https://www.npmjs.com/search?q=gulp&amp;amp;ranking=popularity&amp;amp;page=0&amp;amp;perPage=20" rel="noopener noreferrer"&gt;top ten popular plugins&lt;/a&gt; supports streaming mode (&lt;code&gt;gulp-replace&lt;/code&gt;).As an aside, Gulp is an interesting example, because a lack of support in any given plugin will always throw an error—it's actively &lt;em&gt;hostile&lt;/em&gt; to streams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speed Comparison
&lt;/h3&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%2Fstorage.googleapis.com%2Fhwhistlr.appspot.com%2Fassets%2Fread-chart.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%2Fstorage.googleapis.com%2Fhwhistlr.appspot.com%2Fassets%2Fread-chart.png" alt="Chart comparng approaches to reading file data"&gt;&lt;/a&gt;&lt;code&gt;fs.readFileSync&lt;/code&gt; is much faster than streaming data&lt;/p&gt;

&lt;p&gt;If your goal is to just read a whole file into memory as quickly as possible, &lt;code&gt;fs.readFileSync&lt;/code&gt; is the champion, with &lt;code&gt;fs.readFile&lt;/code&gt; (the callback-based version) slightly behind.Building the most simple of streams—just getting every chunk and putting it into an array—is about 2-3x slower.&lt;/p&gt;

&lt;p&gt;Interestingly enough, is that using &lt;code&gt;fs.promises.readFile&lt;/code&gt; is about 2-3x slower again.If you're worried about performance, maybe avoid it for now (Node v13).&lt;/p&gt;

&lt;h2&gt;
  
  
  Object Mode
&lt;/h2&gt;

&lt;p&gt;Where streams come into their own, however, is when they're used in &lt;code&gt;{objectMode: true}&lt;/code&gt;.Instead of transferring 'chunks' of a file, you can use them to move literally any object.&lt;/p&gt;

&lt;p&gt;Let's start with an example—again using Gulp.Gulp is described as a "streaming build system", but 'streaming' here refers specifically to the &lt;em&gt;Vinyl file objects&lt;/em&gt; it generates—which are passed through plugins with &lt;code&gt;objectMode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Gulp's tasks look a bit 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="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images&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="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="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;src&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;images/*.{png,gif,jpg}&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;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;imagemin&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gulp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist/images/&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call, &lt;code&gt;gulp.src&lt;/code&gt;, starts a stream that reads files matching a certain glob.It then pipes the stream to a &lt;em&gt;transform&lt;/em&gt; which modifies each file (in this case, minifies them) before finally to a writer that puts them on your disk again.&lt;/p&gt;

&lt;p&gt;This is a powerful primitive, but it has some serious caveats.Some of them seem like poor design choices in Node which would be impossible to change after so many years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;Streams, and the way they pipe together, aren't useful for constructing some permanent structure or scaffold.&lt;/p&gt;

&lt;p&gt;In the following example, we generate helper streams that emit some numbers.Once the first &lt;code&gt;Readable&lt;/code&gt; is complete it will call the &lt;code&gt;.end()&lt;/code&gt; method of the thing you've piped through to.Have a read:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;stream&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;stream&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;transforms&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;async-transforms&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;doSomething&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transforms&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;each&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;each&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="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&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;// the setTimeout shows that the above Readable&lt;/span&gt;
  &lt;span class="c1"&gt;// is closing the stream after a frame&lt;/span&gt;
  &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Readable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// won't run, won't crash&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program will only output "2, 3, 4" (the values passed in the first stream).To be fair, we &lt;em&gt;can&lt;/em&gt; fix this behavior by passing &lt;code&gt;{end: false}&lt;/code&gt; as an option to the &lt;code&gt;.pipe&lt;/code&gt; call—but this is something the &lt;em&gt;user&lt;/em&gt; of a stream decides on, not the author.&lt;/p&gt;

&lt;p&gt;To put it another way, if you're writing a library that exposes a stream target, &lt;em&gt;any&lt;/em&gt; part of a program that uses your stream can cause you to stop receiving input.This is a challenging place for library authors to be in.&lt;/p&gt;

&lt;p&gt;Streams also historically don't do anything sensible with errors.The &lt;code&gt;.pipe()&lt;/code&gt; command doesn't forward errors—check out &lt;a href="https://www.google.com/search?q=nodejs+stream+pipe+error" rel="noopener noreferrer"&gt;a bunch of other posts&lt;/a&gt; about why this is bad.&lt;/p&gt;

&lt;p&gt;The modern solution to errors, and which also makes streams a bit nicer to write code for, is the &lt;a href="https://nodejs.org/api/stream.html#stream_stream_pipeline_source_transforms_destination_callback" rel="noopener noreferrer"&gt;&lt;code&gt;stream.pipeline&lt;/code&gt;&lt;/a&gt; static method.This was added in Node v10, seemingly as a concession that the previous model hasn't worked that well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paralellism in Object Mode
&lt;/h2&gt;

&lt;p&gt;One of the great reasons to use &lt;code&gt;objectMode&lt;/code&gt; streams is part of a build process or similar pipeline.The reason I've used Gulp as an example is that it pioneered this approach.&lt;/p&gt;

&lt;p&gt;However, not every implementer of a transform or other parts of a stream gets parallelism right.This is the relevant line from &lt;a href="https://nodejs.org/api/stream.html#stream_transform_transform_chunk_encoding_callback" rel="noopener noreferrer"&gt;Node.js' docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;transform._transform()&lt;/code&gt; is never called in parallel; streams implement a queue mechanism, and to receive the next chunk, callback must be called, either synchronously or asynchronously.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you were to implement a transform as the docs suggest:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nf"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doComplexTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;callback&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;result&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... then only a single chunk will be processed at once.This probably makes sense for chunked &lt;em&gt;binary data&lt;/em&gt;, but rarely for objects.Let me explain.&lt;/p&gt;

&lt;p&gt;If you're writing a transform that compiles SASS or resizes a number of images on-disk, and each object is a filename or file contents, then it's unlikely that the order of the output matters, or that each task is related in any way.&lt;/p&gt;

&lt;p&gt;So, naïvely, you can run all your tasks in parallel by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const t = new stream.Transform({
  transform(object, encoding, callback) {
    callback(); // "done" already, send me more please
    doComplexTask(object, (result, err) =&amp;gt; {
      err ? this.emit('error', err) : this.push(result);
    });
  }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tradeoffs of this simple design are basically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tasks will complete in any order&lt;/li&gt;
&lt;li&gt;you'll run all the tasks as fast as they arrive—maybe they shouldn't all run at once, even for CPU or memory-related reasons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(The second point actually doesn't matter too much in Node.js, as it's single-threaded: at most, you could burn through one of your CPUs, unless your "complex task" is actually spawning another thread.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Async Transforms Library
&lt;/h3&gt;

&lt;p&gt;Yes, this post is mostly advertising for a library I've written.The &lt;a href="https://www.npmjs.com/package/async-transforms" rel="noopener noreferrer"&gt;async-transforms&lt;/a&gt; package has a number of stream helpers which implicitly work in parallel, and allow you control the number of tasks to use.&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;transforms&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;async-transforms&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;compileTransform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;transforms&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expensiveCompileTask&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;code&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="na"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's also got a number of other helpers, including farming work out to a &lt;code&gt;Worker&lt;/code&gt; inside Node.js (so CPU-bound tasks can run on their own thread).I'll let you take a read of its documentation, but it works around both tradeoffs of the naïve design, above.&lt;/p&gt;

&lt;p&gt;_As a fun aside, one of the most egregiously poor bits of JavaScript advice I've seen on the internet is that &lt;code&gt;forEach&lt;/code&gt; and &lt;code&gt;map&lt;/code&gt; on your stock-standard &lt;code&gt;Array&lt;/code&gt; type work in parallel.Nothing could be further from the truth—these are clearly defined to work step-by-step._Proper use of streams, however, can get you close to that vision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Streams are confusing.You probably don't need them for reading or writing bytes, unless you have a niche use-case (including interacting with the network).&lt;/p&gt;

&lt;p&gt;Streams in &lt;code&gt;objectMode&lt;/code&gt; have more utility, but know that they're not a silver bullet to all types of program flow, and that most folks implementing transforms build them in a completely serial way.&lt;/p&gt;

</description>
      <category>node</category>
    </item>
    <item>
      <title>Logging with App Engine and Stackdriver</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Wed, 08 Apr 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/samthor/logging-with-app-engine-and-stackdriver-dik</link>
      <guid>https://dev.to/samthor/logging-with-app-engine-and-stackdriver-dik</guid>
      <description>&lt;h1&gt;
  
  
  Logging with App Engine and Stackdriver
&lt;/h1&gt;

&lt;p&gt;In days of old, App Engine's logging statement looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;appengine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Infof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"You made a log! Here's a thing: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This appeared in Stackdriver's logging page as a line correctly attributed to the corresponding HTTP request.And if you call e.g., &lt;code&gt;Warningf&lt;/code&gt;, or &lt;code&gt;Errorf&lt;/code&gt;, the severity of the request itself is increased to match (to the maximum level of severity you log).&lt;/p&gt;

&lt;p&gt;Easy, right? Well, not anymore.&lt;/p&gt;

&lt;p&gt;⚠️ While this post uses Go, it applies to all App Engine runtimes.This is especially true as the reason behind App Engine's massive changes is that by removing functionality, more languages are supported 'for free'.&lt;/p&gt;

&lt;h2&gt;
  
  
  New App Engine
&lt;/h2&gt;

&lt;p&gt;Since &lt;a href="https://cloud.google.com/appengine/docs/standard/go/go-differences"&gt;the go112 runtime&lt;/a&gt;, App Engine, as we knew it for 10+ years, has been effectively deprecated.It's now a place to run a HTTP server with a bit of magic around the edges.&lt;/p&gt;

&lt;p&gt;Because of this, you now have a couple of points to consider when logging. For background, remember that App Engine still generates a default HTTP log for every request.&lt;/p&gt;

&lt;p&gt;If you simply follow the documentation on how to log for go112 and above, you'll encounter two fundamental issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the logs you generate won't be associated with the current HTTP request&lt;/li&gt;
&lt;li&gt;and, each log event you generate will appear inside Stackdriver &lt;strong&gt;on its own line&lt;/strong&gt; , not grouped together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, you can see these contextless log messages &lt;em&gt;adjacent&lt;/em&gt; to incoming HTTP requests, which could be useful for long-lived tasks.But it's now difficult to 'at-a-glance' see logs generated due to a HTTP request in context.&lt;/p&gt;

&lt;h3&gt;
  
  
  How To Log
&lt;/h3&gt;

&lt;p&gt;For background, to log from user code, you can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log to stdout (via the &lt;a href="https://golang.org/pkg/log/"&gt;built-in log package&lt;/a&gt;) or by literally printing to stdout or stderr&lt;/li&gt;
&lt;li&gt;or, log to a named log via &lt;a href="https://cloud.google.com/logging/docs/setup/go"&gt;Cloud Logging&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Confusingly, the named log you choose &lt;em&gt;can also be named&lt;/em&gt; stdout or stderr.&lt;/li&gt;
&lt;li&gt;The name doesn't really matter except that Stackdriver's Log Viewer will show stdout, stderr and your App Engine logs by default.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, if you print JSON to stdout, it will be treated &lt;em&gt;as&lt;/em&gt; a structured log (as if you called Cloud Logging with "stdout" as the log name).&lt;a href="https://github.com/GoogleCloudPlatform/golang-samples/issues/802"&gt;This is badly documented&lt;/a&gt;, but there are &lt;a href="https://github.com/andyfusniak/stackdriver-gae-logrus-plugin"&gt;third-party loggers&lt;/a&gt; that can help.&lt;/p&gt;

&lt;h3&gt;
  
  
  Associate Your Logs
&lt;/h3&gt;

&lt;p&gt;App Engine's &lt;a href="https://cloud.google.com/appengine/docs/standard/go/writing-application-logs#related-app-logs"&gt;documentation&lt;/a&gt; is vague on how you associate log events with the current HTTP request.Let's go through the steps.To associate your logs with the top-level, default App Engine log for the HTTP request, you'll need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse the &lt;code&gt;X-Cloud-Trace-Context&lt;/code&gt; header, which include the Trace ID, Span ID and an optional flag&lt;/li&gt;
&lt;li&gt;Insert the Trace ID into a string like "projects//traces/"&lt;/li&gt;
&lt;li&gt;Use this string as the &lt;code&gt;Trace&lt;/code&gt; field in a structured log&lt;/li&gt;
&lt;li&gt;Ensure you're passing a valid &lt;code&gt;mrbp.MonitoredResource&lt;/code&gt; that describes the log as &lt;code&gt;Type: "gae_app"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will ensure that your log is nested with the HTTP request, based on &lt;code&gt;Trace&lt;/code&gt; and &lt;code&gt;Type&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wsam6SSE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/nested-log.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wsam6SSE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/nested-log.png" alt="Stackdriver logging" width="880" height="340"&gt;&lt;/a&gt;shows the nested log statement&lt;/p&gt;

&lt;p&gt;However, this log will &lt;em&gt;still exist&lt;/em&gt; at the top-level—it's just being nested by Stackdriver's UI.A simple workaround here is to use a log name that's not shown by default (only stderr, stdout and the App Engine default logs are shown), so it won't clutter your view.&lt;/p&gt;

&lt;p&gt;⚠️ You can set the &lt;code&gt;HTTPRequest&lt;/code&gt; field of the log entry.But this will appear as if a &lt;em&gt;whole other&lt;/em&gt; HTTP request has occured (as it'll use the structured log format and display "GET", the path, etc) for every individual line you log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;The code looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s"&gt;"fmt"&lt;/span&gt;
  &lt;span class="s"&gt;"os"&lt;/span&gt;
  &lt;span class="s"&gt;"strings"&lt;/span&gt;
  &lt;span class="s"&gt;"cloud.google.com/go/logging"&lt;/span&gt;
  &lt;span class="n"&gt;mrpb&lt;/span&gt; &lt;span class="s"&gt;"google.golang.org/genproto/googleapis/api/monitoredres"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;projectName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-project-name"&lt;/span&gt;
  &lt;span class="c"&gt;// you can also use os.Getenv("GOOGLE_CLOUD_PROJECT") for this in prod&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;lg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;loggingClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"projects/%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CommonResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mrpb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MonitoredResource&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"gae_app"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="n"&gt;lg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;loggingClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"your-app-appengine-client-log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;httpHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;traceId&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Cloud-Trace-Context"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="n"&gt;lg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"projects/%s/traces/%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traceId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Yes, your log message finally goes here"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;lg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flush&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;Of course, you probably want to write a helper.&lt;em&gt;Simple&lt;/em&gt;. 🤨&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;You can't modify the severity of the default App Engine HTTP log.While this &lt;em&gt;is&lt;/em&gt; &lt;a href="https://cloud.google.com/appengine/docs/standard/go/writing-application-logs#related-app-logs"&gt;mentioned in the docs&lt;/a&gt;, it's actually an error—there's no way to do this.&lt;/p&gt;

&lt;p&gt;You also can't really test this locally, as App Engine no longer runs via the &lt;code&gt;dev_appserver&lt;/code&gt;, so no magic headers are provided to you.Local code just won't see the &lt;code&gt;X-Cloud-Trace-Context&lt;/code&gt; header.A quick way to test if you're in production or not is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;  &lt;span class="n"&gt;projectName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GOOGLE_CLOUD_PROJECT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;isProd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;projectName&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;App Engine is no longer designed to help you associate simple log output with its default HTTP logging and provide helpful 'at-a-glance' information.So, let's not work against it: another option is to &lt;strong&gt;write our own logs&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parallel To App Engine Logs
&lt;/h3&gt;

&lt;p&gt;As we know, App Engine generates default HTTP logs.They can't be disabled, which means if you insert &lt;em&gt;additional&lt;/em&gt; log statements, you might be fooled into thinking that your application has twice the number of requests.&lt;/p&gt;

&lt;p&gt;However, if you create logs under a different log name, and aggressively use a different search inside Stackdriver (as you can't set a default), it's possible to see just your own log lines.&lt;/p&gt;

&lt;p&gt;You'll need to create two different types of logs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The parent log (this maps to the App Engine log we're trying to replicate)&lt;/li&gt;
&lt;li&gt;Any individual log statement (generated from a classic &lt;code&gt;Logf&lt;/code&gt;-style function)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Confusingly, you should create the parent entry last, because it contains information you only know at request completion—e.g., the response size and the request latency.You don't &lt;em&gt;have&lt;/em&gt; to specify this data, but Stackdriver will show "undefined" for several fields without it (Stackdriver has a UI for custom fields, but it aggressively tries to include an undocumented number of HTTP-related fields regardless).&lt;/p&gt;

&lt;p&gt;As I mentioned before, Stackdriver will associate requests with the same Trace ID.Since we're not logging a real request, you can just make one up.I suggest deriving something from the &lt;em&gt;real&lt;/em&gt; ID.&lt;/p&gt;

&lt;p&gt;Here's how you might log individual log lines (part 2, above):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;res := logging.CommonResource(&amp;amp;mrpb.MonitoredResource{
  Type: "gae_app",
})
fakeTraceID := "_"+r.Header.Get("X-Cloud-Trace-Context") // derived by adding a char
clientLogger, err := loggingClient.Logger("events", res)
err := logger.LogSync(r.Context(), logging.Entry{
  Payload: "I'm a log message!",
  Severity: logging.Info, // you decide the level
  Trace: fakeTraceID,
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you can log information about the whole request (part 1, again, typically after your request is complete):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;parentLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;loggingClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sane_requests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;HTTPRequest&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// use incoming *http.Request from http handler&lt;/span&gt;
    &lt;span class="n"&gt;RemoteIP&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Appengine-User-Ip"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c"&gt;// not in App Engine's *http.Request&lt;/span&gt;

    &lt;span class="c"&gt;// nb. These can only be found by wrapping your handler.&lt;/span&gt;
    &lt;span class="n"&gt;ResponseSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Latency&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// the top-level payload is totally freeform (JSON or text)&lt;/span&gt;
  &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// you decide what this is&lt;/span&gt;
  &lt;span class="n"&gt;Trace&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fakeTraceID&lt;/span&gt; &lt;span class="c"&gt;// from previous example&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... phew.There's definitely room for a library to help you here, and then as a reminder, you'll have to ask Stackdriver to show you the "sane_requests" log.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orthogonal to App Engine logs
&lt;/h3&gt;

&lt;p&gt;App Engine is going to continue generating its own logs.Many of these logs are likely completely boring: requests for static assets, redirections, etc.&lt;/p&gt;

&lt;p&gt;Rather than trying to replace the built-in behavior, another suggestion is to just create logs for the most interesting of your handlers.You can follow the above guidance to insert HTTP requests but remember that the request is mutable and &lt;em&gt;something you can fake&lt;/em&gt;—or even not provide &lt;strong&gt;at all&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While I mentioned the list was undocumented (it is), I've observed that Stackdriver will show the following fields in its default view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;httpRequest.requestMethod&lt;/code&gt;: usually 'GET' or 'POST', but could be 'FOO'&lt;/li&gt;
&lt;li&gt;&lt;code&gt;httpRequest.status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;httpRequest.responseSize&lt;/code&gt;: parsed as bytes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;httpRequest.latency&lt;/code&gt;: parsed as a time&lt;/li&gt;
&lt;li&gt;&lt;code&gt;httpRequest.userAgent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;httpRequest.remoteIp&lt;/code&gt;: only displayed when the log event is expanded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these fields exist, then Stackdriver will try to display &lt;em&gt;all&lt;/em&gt; of them.So the choice is up to you: add single text logging events for the progress of your loggable event, and then provide the top-level logger, which can contain a &lt;em&gt;real&lt;/em&gt;, &lt;em&gt;fake&lt;/em&gt; or &lt;em&gt;no&lt;/em&gt; HTTP request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;When writing this blogpost, I found that my App Engine's Service Account (in the form &lt;a href="//mailto:appid@appspot.gserviceaccount.com"&gt;appid@appspot.gserviceaccount.com&lt;/a&gt;) didn't have permissions to write logs.I think this is because my app is actually quite old–it predates the Cloud Console.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uUIpVO5t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/logwriter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uUIpVO5t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://storage.googleapis.com/hwhistlr.appspot.com/assets/logwriter.png" alt="The Google Cloud IAM page" width="880" height="1024"&gt;&lt;/a&gt;adding a service account to the right groups&lt;/p&gt;

&lt;p&gt;If you see security or other errors, you might need to add the service account to your project (it doesn't always show on the IAM page) and give it "Logs Writer" access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parting Thoughts
&lt;/h2&gt;

&lt;p&gt;None of these solutions are ideal.There is an &lt;a href="https://github.com/googleapis/google-cloud-go/issues/720"&gt;official bug&lt;/a&gt; from 2017 which I referenced to write this post.Since this behavior remains the same in early 2020, I don't think there's any plans to simplify logging again.&lt;/p&gt;

</description>
      <category>appengine</category>
      <category>gcp</category>
    </item>
    <item>
      <title>Progress Indicator With Fetch</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Thu, 20 Jun 2019 13:50:14 +0000</pubDate>
      <link>https://dev.to/samthor/progress-indicator-with-fetch-1loo</link>
      <guid>https://dev.to/samthor/progress-indicator-with-fetch-1loo</guid>
      <description>&lt;p&gt;A quick tip: &lt;a href="https://dev.to/samthor/pwas-that-download-like-apps-fd6"&gt;in a previous demo&lt;/a&gt;, I showed how we can download a large file to seed the content for a Service Worker. If you look fast enough, you'll see a progress indicator. (Although for a small file, blink and you'll miss it!) 👀&lt;/p&gt;

&lt;p&gt;The code is pretty simple. Let's start with a simple async &lt;code&gt;fetch&lt;/code&gt;:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;downloadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;arrayBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&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;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// do something with bytes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;arrayBuffer&lt;/code&gt; call waits until the entire target has downloaded before returning the bytes. Instead, we can consume 'chunks' of the file (since we'll get parts of the file over time) at a time, to get a sense of percentage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check The Header
&lt;/h2&gt;

&lt;p&gt;Firstly, we read the "Content-Length" header of our response: this is something the server sends us before the data, so we can actually work out how far along we've gone:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&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;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Length&lt;/span&gt;&lt;span class="dl"&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// something was wrong with response, just give up&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&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;If there's no valid header, then either there's something wrong with the response, or the server hasn't told us how long it is. You can just fall back to whatever you were doing before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunks
&lt;/h2&gt;

&lt;p&gt;Your browser is receiving chunks of bytes from the remote server as the data arrives. Since we know how long the total response will be, we can prepare a buffer for it:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;length&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;at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// to index into the array&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And grab the reader, which lets us get chunks:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can store where we're &lt;em&gt;up to&lt;/em&gt; (in &lt;code&gt;at&lt;/code&gt;), and insert every new chunk into the output:&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="k"&gt;for&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="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&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;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the loop above, we can log the progress as a percentage, something like:&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="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then as above, just return the array: we're done.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fin
&lt;/h1&gt;

&lt;p&gt;20 👋&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>Graceful Shutdown Is A Lie</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Wed, 19 Jun 2019 13:12:15 +0000</pubDate>
      <link>https://dev.to/samthor/graceful-shutdown-is-a-lie-n79</link>
      <guid>https://dev.to/samthor/graceful-shutdown-is-a-lie-n79</guid>
      <description>&lt;p&gt;If you're writing backend code, you should always assume that your service is going to crash. 💥&lt;/p&gt;

&lt;p&gt;This was something I learned when I was designing reliable code that was performing (effectively) a transaction using &lt;a href="https://ai.google/research/pubs/pub36971"&gt;Megastore&lt;/a&gt;. This service needed to be live, always: our failure cases were that it crashed, or we were upgrading the binary.&lt;/p&gt;

&lt;p&gt;This is fairly understood for webservers, where e.g. a Node server can be shut down only by Ctrl-C, or a Go server can only "fail":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fooHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&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;But it's less clear what a crash means &lt;em&gt;while&lt;/em&gt; performing some operation, like database or file operations. When can your server crash awkwardly?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;when you're writing a file to disk over a previous file: for a time, &lt;em&gt;neither&lt;/em&gt; file will exist&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;... solve with writing a temporary file and replacing the previous one&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;updating an aggregate count (e.g., when you insert a row, add one to the 'total'): if the second operation fails&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;... solve with database transactions&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;marking an email as being sent: what if the database fails?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;... sending a second email is probably fine ¯\_(ツ)_/¯&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In all these cases, it pays to code defensively. This isn't a long post, but just something to think about: at what point could the power 🔌 be unplugged from your provider? What's the risk?&lt;/p&gt;

&lt;h1&gt;
  
  
  Thanks!
&lt;/h1&gt;

&lt;p&gt;19 👋&lt;/p&gt;

</description>
      <category>theory</category>
    </item>
    <item>
      <title>Divert Vertical Scroll To The Side ↔️</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Tue, 18 Jun 2019 10:59:58 +0000</pubDate>
      <link>https://dev.to/samthor/divert-vertical-scroll-to-the-side-3id0</link>
      <guid>https://dev.to/samthor/divert-vertical-scroll-to-the-side-3id0</guid>
      <description>&lt;p&gt;Let's try a combined blog post today: one tip for helping your users get around when you have horizontal scroll areas (for effect, or whatever), and one tip for &lt;em&gt;navigating&lt;/em&gt; the web.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vertical To Horizontal
&lt;/h2&gt;

&lt;p&gt;If you have a great, amazing UI feature which just happens to scroll horizontally: think a big long list of products which go on forever, users can use say, a trackpad or a scroll bar to get there.&lt;/p&gt;

&lt;p&gt;But if your user has a regular mouse with only a vertical scroll wheel, how can they navigate that area? 🤷&lt;/p&gt;

&lt;p&gt;We can actually redirect catch the &lt;code&gt;wheel&lt;/code&gt; event, and just modify the &lt;code&gt;scrollLeft&lt;/code&gt; property (which controls &lt;em&gt;horizontal&lt;/em&gt; navigation) based on &lt;code&gt;deltaY&lt;/code&gt; (the up and down motion of the wheel). We also need &lt;code&gt;deltaX&lt;/code&gt;, as otherwise we prevent real horizontal scrolling. Take a look:&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;// handle up/down scrollwheel on the scroller, as most folks don't have horizontal scroll&lt;/span&gt;
&lt;span class="nx"&gt;scrollableElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wheel&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;ev&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;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// stop scrolling in another direction&lt;/span&gt;
  &lt;span class="nx"&gt;scrollableElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollLeft&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deltaY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deltaX&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;iframe height="600" src="https://codepen.io/samthor/embed/jjroEa?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This is something we used on Santa Tracker in the &lt;a href="https://santatracker.google.com/elfbuilder.html"&gt;🧝 Elf Builder&lt;/a&gt; game.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tip (aka Today I Learned)
&lt;/h2&gt;

&lt;p&gt;If you scroll vertically on a page (in nearly any browser) but hold down &lt;em&gt;shift&lt;/em&gt;, you'll actually scroll horizontally.&lt;/p&gt;

&lt;h1&gt;
  
  
  Thanks!
&lt;/h1&gt;

&lt;p&gt;That's all for today!&lt;/p&gt;

&lt;p&gt;18 👋&lt;/p&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>What To Expect When You're Expecting To Drop IE11 🗑️</title>
      <dc:creator>Sam Thorogood</dc:creator>
      <pubDate>Mon, 17 Jun 2019 10:04:53 +0000</pubDate>
      <link>https://dev.to/samthor/what-to-expect-when-you-re-expecting-to-drop-ie11-ifg</link>
      <guid>https://dev.to/samthor/what-to-expect-when-you-re-expecting-to-drop-ie11-ifg</guid>
      <description>&lt;p&gt;So you've decided to drop support for IE11 and move onto evergreen browsers only (IE11 is only about ~2% globally). That's great! 🌲&lt;/p&gt;

&lt;p&gt;With that in mind, here's a giant list of the features you should use, today (today being mid-2019), safely, &lt;em&gt;without&lt;/em&gt; polyfills or feature detection. 📃&lt;/p&gt;

&lt;p&gt;Before we start, of course, there'll always be old browsers. And, to be fair, browsers in emerging markets are more complex: like UC, KaiOS (based on an older Firefox), and Opera Mini. In these cases, I suggest serving &lt;a href="https://dev.to/chromiumdev/the-chrome-dev-summit-site-case-study-15ng"&gt;no JS whatsoever&lt;/a&gt; (if possible), or encouraging users to upgrade. 🤷&lt;/p&gt;

&lt;p&gt;Let's go! ⬇️&lt;/p&gt;

&lt;h3&gt;
  
  
  The DOM
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Choose image URL &lt;a href="https://www.youtube.com/watch?v=SyVKRnusyqM"&gt;based on resolution 📽️&lt;/a&gt; (via &lt;code&gt;&amp;lt;img srcset&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Frames &lt;a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/9121088/"&gt;can load from&lt;/a&gt; a &lt;code&gt;Blob&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disable form elements with &lt;a href="https://dev.to/chromiumdev/disable-a-html-form-while-in-flight-using-fieldset-61b"&gt;&lt;code&gt;&amp;lt;fieldset disabled&amp;gt;&lt;/code&gt;&lt;/a&gt;, useful for in-progress forms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTML input types &lt;code&gt;color&lt;/code&gt; &lt;a href="https://robertnyman.com/html5/forms/input-types.html"&gt;and various date/time options&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTML templates and the &lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; element (this is also in JS, but you can specify them in your pages)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;&amp;lt;meter&amp;gt;&lt;/code&gt; element (&lt;a href="https://peter.sh/examples/?/html/meter-progress.html"&gt;goes along with &lt;code&gt;&amp;lt;progress&amp;gt;&lt;/code&gt;&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JavaScript Language
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ES Modules, through &lt;code&gt;&amp;lt;script type="module"&amp;gt;&lt;/code&gt; and &lt;code&gt;import&lt;/code&gt;/&lt;code&gt;export&lt;/code&gt;&lt;/strong&gt; 🎉&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Template literals (with backticks)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Classes like &lt;code&gt;class Foo { constructor() { ... } }&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Functions! Arrow functions, rest parameters, &lt;code&gt;async&lt;/code&gt; functions that allow &lt;code&gt;await&lt;/code&gt;, generators which can &lt;code&gt;yield&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JavaScript Library
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Promise&lt;/code&gt; &lt;a href="https://developers.google.com/web/fundamentals/primers/async-functions"&gt;and &lt;code&gt;fetch&lt;/code&gt;&lt;/a&gt; (no need for &lt;code&gt;XMLHttpRequest&lt;/code&gt; anymore 🚫)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;... XHR's &lt;code&gt;responseType&lt;/code&gt; can also &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType"&gt;now be set safely to "json"&lt;/a&gt;, but why would you bother? 🤷&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Methods on &lt;code&gt;Array&lt;/code&gt;: &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt;; and on &lt;code&gt;String&lt;/code&gt;: &lt;code&gt;includes&lt;/code&gt;, &lt;code&gt;padStart&lt;/code&gt; and &lt;code&gt;padEnd&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;Proxy&lt;/code&gt; object, allowing for interesting &lt;a href="https://exploringjs.com/es6/ch_proxies.html"&gt;approaches&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Methods on &lt;code&gt;Object&lt;/code&gt;: &lt;code&gt;entries&lt;/code&gt; and &lt;code&gt;values&lt;/code&gt;, for iteration (like &lt;code&gt;Object.keys&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;URL&lt;/code&gt; object (useful to check for query params and work with URLs)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;currentScript&lt;/code&gt; property (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript"&gt;"what file am I"&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can safely dispatch a &lt;code&gt;new CustomEvent('....')&lt;/code&gt; rather than dealing &lt;a href="https://www.google.com.au/search?q=ie11+"&gt;with weird intializers&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Symbol&lt;/code&gt; and friends&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JavaScript + The DOM
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/chromiumdev/beyond-appendchild-better-convenience-methods-for-html-55n4"&gt;Better convenience methods for HTML&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The third argument to &lt;code&gt;addEventListener&lt;/code&gt;, allowing you to set &lt;code&gt;{once: true}&lt;/code&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Parameters"&gt;other options&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt;, allowing you to tell &lt;a href="https://developers.google.com/web/updates/2016/04/intersectionobserver"&gt;whether DOM nodes are visible&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;navigator.sendBeacon&lt;/code&gt; method, to send POST messages &lt;a href="https://dev.to/chromiumdev/sure-you-want-to-leavebrowser-beforeunload-event-4eg5"&gt;even if a page closes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find the &lt;a href="https://dev.to/samthor/matching-elements-with-selectors-in-js-4991"&gt;closest matching element&lt;/a&gt; with &lt;code&gt;closest&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The 2nd argument to &lt;code&gt;classList.toggle&lt;/code&gt;, allowing you to set or remove a class via parameter (also, the &lt;code&gt;.relList&lt;/code&gt; property on links)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Canvas blend modes (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation"&gt;this is the &lt;code&gt;.globalCompositeOperation&lt;/code&gt; property&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Determine whether a CSS feature is supported via &lt;code&gt;CSS.supports&lt;/code&gt; (but this only helps &lt;em&gt;future&lt;/em&gt; features)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Whole New APIs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://jakearchibald.github.io/isserviceworkerready/"&gt;&lt;strong&gt;Service Workers&lt;/strong&gt;&lt;/a&gt; 🥳&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=iPtMiqutNT4"&gt;Web Assembly&lt;/a&gt; 👩‍💻&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gamepad API 🎮&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Web Audio API 📣&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://mdn.github.io/dom-examples/pointer-lock/"&gt;Pointer Lock API&lt;/a&gt;: useful for HTML games and rich experiences 🐁🔒&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Constraint Validation API (improved form validation) 📏&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://webrtc.org/"&gt;WebRTC&lt;/a&gt; 📽️&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;getUserMedia&lt;/code&gt; to get access to video, audio streams 🙏&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CSS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grid 🎉&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSS Variables, such as &lt;code&gt;--foo: blue;&lt;/code&gt;, used with &lt;code&gt;color: var(--foo)&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sticky Position&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CSS filters, allowing for visual effects like invert, drop shadow and hue changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Image &lt;code&gt;object-fit&lt;/code&gt;&lt;/strong&gt; (Edge only supports it on &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;), allowing you to make an image contain or cover its contents rather than &lt;em&gt;stretch&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Improved media queries &lt;a href="https://googlechrome.github.io/samples/media-hover-pointer/"&gt;for pointer or mouse access&lt;/a&gt; &lt;small&gt;Fun fact: this was one of the first demos I wrote working on Chrome.&lt;/small&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/cursor"&gt;CSS cursors&lt;/a&gt; 'grab', 'zoom-in', 'zoom-out'&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;::placeholder&lt;/code&gt; pseudo-element, for styling the placeholder text inside an &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using &lt;code&gt;initial&lt;/code&gt; or &lt;code&gt;unset&lt;/code&gt; &lt;a href="https://www.quirksmode.org/css/cascading/values.html"&gt;as CSS values&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;vmax&lt;/code&gt; unit, which is a percent of whichever's larger: &lt;a href="https://css-tricks.com/fun-viewport-units/"&gt;width or height&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Going along with the JS method, the CSS &lt;code&gt;@supports&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@supports"&gt;at-rule&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read-only and read-write pseudo-class selectors (&lt;code&gt;:read-write&lt;/code&gt; seems &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:read-write"&gt;the more useful&lt;/a&gt; of the two)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://webkit.org/blog/85/introducing-text-stroke/"&gt;Stroke and fill on text&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;... although supported on all evergreens, you'll need to include the &lt;code&gt;-webkit-&lt;/code&gt; prefixes: yes, &lt;em&gt;even for Edge and Firefox&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;

&lt;p&gt;Risky bugs in IE11 are no longer an issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can now safely put &lt;code&gt;calc(...)&lt;/code&gt; &lt;a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/105834/"&gt;inside a CSS animation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CSS &lt;code&gt;display: flex&lt;/code&gt; had a &lt;a href="https://github.com/philipwalton/flexbugs"&gt;variety of issues&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Phew!
&lt;/h1&gt;

&lt;p&gt;You got this far! Congratulations! 🎉&lt;/p&gt;

&lt;p&gt;If there's any I've missed, or good resources for any of these features, let me know below.&lt;/p&gt;

&lt;p&gt;17 👋&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>ie11</category>
    </item>
  </channel>
</rss>
