<?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: Gaute Meek Olsen</title>
    <description>The latest articles on DEV Community by Gaute Meek Olsen (@gautemeekolsen).</description>
    <link>https://dev.to/gautemeekolsen</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%2F187797%2F5c40e6f0-6b64-4359-9833-aec734c1e21b.jpg</url>
      <title>DEV Community: Gaute Meek Olsen</title>
      <link>https://dev.to/gautemeekolsen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gautemeekolsen"/>
    <language>en</language>
    <item>
      <title>Best practice for images</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Mon, 06 Feb 2023 19:39:14 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/best-practice-for-images-54o7</link>
      <guid>https://dev.to/gautemeekolsen/best-practice-for-images-54o7</guid>
      <description>&lt;p&gt;Adding an image to your website is quite simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/apple.jpg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's pack our bags and go home.&lt;/p&gt;

&lt;p&gt;But there are more things you should think about for a better user experience and performance improvement. Let's go through them one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative text
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;alt&lt;/code&gt; attribute should be used for a textual description of the image. This is used by screen readers, so it's really important for accessibility. It is also displayed if the image can't be loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/apple.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"A green apple"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your image is only decoration and doesn't provide any value to the content you should set an empty string, &lt;code&gt;alt=""&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Figure
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; is an element that represents some self-contained content, optionally with a caption. It's often used with an image where we want a caption for the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;figure&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/apple.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"A green apple"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;figcaption&amp;gt;&lt;/span&gt;Largest green apple discovered&lt;span class="nt"&gt;&amp;lt;/figcaption&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/figure&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Vector images
&lt;/h2&gt;

&lt;p&gt;If your image is a Scalable Vector Graphics (SVG), you should keep it in that format instead of serving something like JPEG or PNG. That way your image can scale better to different sizes, also when the user zooms in, and there is less data transferred over the network most of the time.&lt;/p&gt;

&lt;p&gt;Extra tip, you can use &lt;a href="https://jakearchibald.github.io/svgomg/"&gt;svgomg&lt;/a&gt; to clean up your &lt;code&gt;.svg&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compress your image
&lt;/h2&gt;

&lt;p&gt;This is the easiest measure you can do to improve your performance. You probably want your users to view the images in the best quality, but at what cost and is it necessary? If you serve an oversized image only to be viewed in a limited-sized box, you transfer unnecessary data over the network that take extra time before it can be displayed. I recommend you use a tool to resize and compress your image, e.g. &lt;a href="https://squoosh.app/"&gt;Squoosh&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid layout shift
&lt;/h2&gt;

&lt;p&gt;Layout shift is when you think the content is loaded, but suddenly there is a jump because of a new element or an element changing height. Images typically do this, since they take up 0px until the source is loaded and the browser knows the actual height.&lt;/p&gt;

&lt;p&gt;If you know the image size and the space it will take up on the page, you should set the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/apple.jpg"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"150"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"A green apple"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But often we do not know the exact space it will take up, because you have a dynamic layout that depends on the screen size. If you know the aspect ratio of the image, then that solves the problem. &lt;code&gt;aspect-ratio: width / height&lt;/code&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;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;aspect-ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="m"&gt;4&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 if you don't know that as well, you should set a sensible minimum height you know the image won't be smaller than.&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;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&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;h2&gt;
  
  
  Different images for different screen sizes
&lt;/h2&gt;

&lt;p&gt;Would you serve the same image to a mobile phone and a large external monitor? Probably not. The &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; element can help with that, it will pick the first &lt;code&gt;&amp;lt;source&amp;gt;&lt;/code&gt; that matches its criteria. &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; is used as a fallback and the place you put common attributes.&lt;/p&gt;

&lt;p&gt;There are two approaches, either with &lt;code&gt;media&lt;/code&gt; or &lt;code&gt;sizes&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  media
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;media&lt;/code&gt; attribute accepts a media query and if true the source is valid to be shown.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(min-width: 1000px)"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/large.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;media=&lt;/span&gt;&lt;span class="s"&gt;"(min-width: 500px)"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/medium.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/small.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/medium.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"monster evolution demo"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  sizes
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;sizes&lt;/code&gt; attribute is a comma-separated list with a pair of a media query and the preferred minimum width. The first valid media query is chosen. With &lt;code&gt;srcset&lt;/code&gt; you can let the browser know the size of the image before it's downloaded. It's also a comma-separated list with a pair of the source and width in pixels suffixed with &lt;code&gt;w&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt;
    &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"
      (min-width: 1000px) 300px,
      (min-width: 500px) 200px,
      100px,
    "&lt;/span&gt;
    &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/large.png 300w, /medium.png 200w, /small.png 100w"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/medium.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"monster evolution demo"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser decides which image is best from the &lt;code&gt;srcset&lt;/code&gt;, but we control it through &lt;code&gt;sizes&lt;/code&gt;. &lt;code&gt;(min-width: 500px) 200px&lt;/code&gt; means that if the screen is a minimum of 500 pixels, it should select an image that is 200px or wider. And if we look into our &lt;code&gt;srcset&lt;/code&gt; that would be &lt;code&gt;medium.png&lt;/code&gt; or &lt;code&gt;large.png&lt;/code&gt; (the browser will be efficient and pick the smallest).&lt;/p&gt;

&lt;p&gt;Note that when a larger image is loaded, the browser will keep using that even though the screen size decreases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modern image formats
&lt;/h2&gt;

&lt;p&gt;
There are some new image formats with better compression than JPEG and PNG, such as WebP, AVIF, and JPEG XL. You should check out the browser support on Can I use
  (&lt;a href="https://caniuse.com/webp" rel="noreferrer"&gt;WebP&lt;/a&gt;,
  &lt;a href="https://caniuse.com/avif" rel="noreferrer"&gt;AVIF&lt;/a&gt;, and
  &lt;a href="https://caniuse.com/jpegxl" rel="noreferrer"&gt;JPEG XL&lt;/a&gt;)
and choose accordingly. Again you can use &lt;a href="https://squoosh.app/" rel="noreferrer"&gt;Squoosh&lt;/a&gt; to convert your images and see how much you save.
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;picture&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/jxl"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/image.jxl"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/avif"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/image.avif"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/webp"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/image.webp"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/jpeg"&lt;/span&gt; &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"/image.jpg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/image.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/picture&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modern image formats can be combined with how we served different sizes for each format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loading strategy
&lt;/h2&gt;

&lt;p&gt;Should you load an image you might not need? Maybe, but you can use the &lt;code&gt;loading&lt;/code&gt; attribute to not load all images eagerly. The default value is &lt;code&gt;eager&lt;/code&gt;, so there is no need to specify that. But if you want the image to be hidden from the preload scanner and only load once it's about to enter the viewport, setting &lt;code&gt;loading="lazy"&lt;/code&gt; is good.&lt;/p&gt;

&lt;p&gt;The browser is smart about the fetch priority of images, but it can be improved if we gave it some hints. If we say nothing it can end up being &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, or both. But if you want the image to skip a few places in the loading queue you can set &lt;code&gt;fetchpriority&lt;/code&gt; to &lt;code&gt;high&lt;/code&gt;. If the image is not that important you can set it to &lt;code&gt;low&lt;/code&gt; so other requests can take priority.&lt;/p&gt;

&lt;p&gt;We can also use &lt;code&gt;decoding&lt;/code&gt; to change the prioritization of processing our content. You can leave it blank to let the browser know best. But if it's important to show the image early you can set it to &lt;code&gt;sync&lt;/code&gt;. If it's okay to process other content before or at the same time as the image you can set it to &lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;
Putting it all together. If the image is your largest contentful paint (&lt;a href="https://web.dev/lcp/" rel="noreferrer"&gt;LCP&lt;/a&gt;) or the image is "above the fold", meaning it's visible without scrolling, you might want this code.
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/apple.jpg"&lt;/span&gt; &lt;span class="na"&gt;fetchpriority=&lt;/span&gt;&lt;span class="s"&gt;"high"&lt;/span&gt; &lt;span class="na"&gt;decoding=&lt;/span&gt;&lt;span class="s"&gt;"sync"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"A green apple"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the image is "below the fold" or not that important, you might want this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/apple.jpg"&lt;/span&gt; &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="na"&gt;decoding=&lt;/span&gt;&lt;span class="s"&gt;"async"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"A green apple"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At last, what if your image is not found by the preload scanner because the image is client-side rendered by JavaScript or a &lt;code&gt;background-image&lt;/code&gt; in CSS? You can fix this by preloading the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"preload"&lt;/span&gt; &lt;span class="na"&gt;as=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/apple.jpg"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Caching
&lt;/h2&gt;

&lt;p&gt;
If a user is returning to a site, there is no need to re-download the same images. You can cache your images through a service worker. But probably the simplest way is to let the browser do it for you through &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" rel="noreferrer"&gt;Cache-Control&lt;/a&gt; in response headers. This can be configured by your hosting provider or your server. You set the &lt;code&gt;max-age&lt;/code&gt; in seconds for how long it is okay to reuse it and &lt;code&gt;immutable&lt;/code&gt; to ensure it's used. If your build step adds a hash to the filename you can set &lt;code&gt;max-age&lt;/code&gt; to the highest supported value, a year, otherwise use a sensible value.
&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cache-Control: max-age=31536000, immutable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>webdev</category>
      <category>html</category>
    </item>
    <item>
      <title>Web safe fonts interactive directory</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Sat, 07 Nov 2020 16:15:42 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/web-safe-fonts-interactive-directory-582d</link>
      <guid>https://dev.to/gautemeekolsen/web-safe-fonts-interactive-directory-582d</guid>
      <description>&lt;p&gt;Check out my imitation of &lt;a href="https://fonts.google.com/" rel="noopener noreferrer"&gt;Google Fonts&lt;/a&gt; website, where you can browse and select a font for your project. The only difference, my website is just for web safe fonts.&lt;/p&gt;

&lt;p&gt;You can find it at &lt;a href="https://web-safe-fonts.vercel.app/" rel="noopener noreferrer"&gt;web-safe-fonts.vercel.app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2eixky4g1n04s6dkac9y.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2eixky4g1n04s6dkac9y.PNG" alt="Screenshot of web safe fonts."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find the code at &lt;a href="https://github.com/gautemo/web-safe-fonts" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. It was created with Vite and Vue 3, which is a blast! I also tried out Vercel for the first time, which was just as easy as Netlify and Firebase hosting.&lt;/p&gt;

&lt;p&gt;If you find any web safe fonts that are missing, give me a heads up in the comments, create an issue, or send a pull request. ✌&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Quality Assess your website with Lighthouse</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Fri, 06 Nov 2020 20:29:04 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/quality-assess-your-website-with-lighthouse-48dn</link>
      <guid>https://dev.to/gautemeekolsen/quality-assess-your-website-with-lighthouse-48dn</guid>
      <description>&lt;p&gt;The best way to measure the quality of your website is by knowing how the users use it and think about it. But often we do not have the possibility to get that insight, you might not know who the users are or have the capacity to find out.&lt;/p&gt;

&lt;p&gt;Here we can utilize tools to help us. One tool that we can use, which the article will cover, is Lighthouse. If you already know Lighthouse and are not bothering reading, just a heads up. We will cover how to utilize it for a &lt;strong&gt;non-technologist&lt;/strong&gt;, &lt;strong&gt;during development&lt;/strong&gt;, and &lt;strong&gt;automate it in the CI pipeline&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Lighthouse?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/web/tools/lighthouse/" rel="noopener noreferrer"&gt;Lighthouse&lt;/a&gt; is an automated tool by Google for assessing and improving the quality of web pages. It has audits for multiple categories with specific details for each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Audits for metrics like first paint and time to interactive to determine lag.&lt;br&gt;
&lt;strong&gt;Best Practices&lt;/strong&gt;: Looks for everything from HTTPS usage to correct image aspect ratios.&lt;br&gt;
&lt;strong&gt;Search Engine Optimization (SEO)&lt;/strong&gt;: Checks for best practices to ensure your site is discoverable.&lt;br&gt;
&lt;strong&gt;Accessibility&lt;/strong&gt;: Checks for common issues that may prevent users from accessing your content.&lt;br&gt;
&lt;strong&gt;Progressive Web App (PWA)&lt;/strong&gt;: These checks validate the aspects of a &lt;a href="https://web.dev/pwa-checklist/" rel="noopener noreferrer"&gt;Progressive Web App&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhiu4oopcfrkv5898cotf.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhiu4oopcfrkv5898cotf.PNG" alt="Example of Lighthouse results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above is an example of how the results can look. After you have scanned your website it will provide you with a quality score from 0 to 100 for the categories Performance, Accessibility, Best Practices, and SEO. It also gives details for PWA.&lt;/p&gt;

&lt;p&gt;You can even dig into the report further and find the results for each audit, such as "Time to interactive" and "Background and foreground colors have sufficient contrast ratio" to mention a few. Each audit has an explanation of why the audit is important and how to fix it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that there exist other tools out there that might be better at assessing categories such as accessibility and performance. But Lighthouse is a great tool covering multiple areas, is free, and has good quality.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  For the Non-technologist
&lt;/h2&gt;

&lt;p&gt;If you own a website, are a project manager, or have any other reasons to validate the quality, you can do that really simple. Just visit &lt;a href="https://web.dev/measure/" rel="noopener noreferrer"&gt;web.dev/measure&lt;/a&gt;, enter your URL, and press RUN AUDIT. There you get the full report on the quality and maybe you can periodically check the report and tell your team what to focus on improving. &lt;/p&gt;
&lt;h2&gt;
  
  
  For the Developer
&lt;/h2&gt;

&lt;p&gt;You can also run Lighthouse in Chrome DevTools. So make sure you have installed the Chrome browser, open a website, and hit F12. Navigate to the tab Lighthouse in the top navigation bar. There you can select which categories to audit and which device to simulate on. Then pressing the Generate report button will generate a report for you. This is an easy way to quality assess your website, even during development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F544d9n2lv01r8l8u1lxa.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F544d9n2lv01r8l8u1lxa.PNG" alt="Lighthouse in Chrome DevTools"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  For the Machine
&lt;/h2&gt;

&lt;p&gt;You can even run Lighthouse from the command line. Which means we can Automate our Lighthouse runs via shell scripts. It can be installed through NPM with &lt;code&gt;npm install -g @lhci/cli&lt;/code&gt; and run with &lt;code&gt;lhci autorun&lt;/code&gt;. I will show you how to implement this on every pull request on GitHub, giving the team full control over the quality of code being added to the repository. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you don't use GitHub, you can still follow along as doing the same for other platforms are most likely almost the same.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First, let's create our workflow file in our repository. Create &lt;code&gt;.github/workflows/lighthouse.yml&lt;/code&gt;. The file name can be what you want, but the &lt;code&gt;.github&lt;/code&gt; and &lt;code&gt;workflows&lt;/code&gt; folders should have those names. Your .yml file should look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lighthouse-ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install &amp;amp; build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run Lighthouse CI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sudo npm i -g @lhci/cli&lt;/span&gt;
          &lt;span class="s"&gt;lhci autorun&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says that on pull requests it will run our lighthouse-ci job with those steps. The steps will install and build our application, then run Lighthouse on the build. If you don't have a build step, you can omit the "npm install &amp;amp; build step". This will run Lighthouse with default settings. It will look for a folder named &lt;code&gt;dist&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;out&lt;/code&gt;, or &lt;code&gt;public&lt;/code&gt; for HTML files to scan.&lt;/p&gt;

&lt;p&gt;We can also add a configuration file to control the Lighthouse run. In the root folder create &lt;code&gt;lighthouserc.js&lt;/code&gt;. For a detailed specification of all the settings, check out the &lt;a href="https://github.com/GoogleChrome/lighthouse-ci/blob/master/docs/configuration.md" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. But here is an example of my final configuration.&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;staticDistDir&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&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isSinglePageApplication&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;about&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;404&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="na"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lighthouse:no-pwa&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;assertions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories:performance&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minScore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories:accessibility&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minScore&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories:seo&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minScore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;categories:best-practices&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minScore&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unsized-images&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off&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="na"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;temporary-public-storage&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;collect&lt;/code&gt; option is for which pages to audit. If you have multiple .html files in the folder, you might only need the &lt;code&gt;staticDistDir&lt;/code&gt; set. But if you have a Single Page Application (SPA) with multiple routes, you can add the &lt;code&gt;staticDistDir&lt;/code&gt;, &lt;code&gt;isSinglePageApplication: true&lt;/code&gt;, and &lt;code&gt;url&lt;/code&gt; with an array of routes to assess.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;assert&lt;/code&gt; allows you to control what it asserts. For &lt;code&gt;preset&lt;/code&gt; you can set &lt;code&gt;lighthouse:all&lt;/code&gt;, &lt;code&gt;lighthouse:recommended&lt;/code&gt;, or &lt;code&gt;lighthouse:no-pwa&lt;/code&gt;. Where all is very aggressive and can't have any failures. Recommended evaluates the assertions on what they recommend, which is a more realistic assessment. No PWA option is the same as recommended, except it doesn't assess the PWA category. You can also be more specific in &lt;code&gt;assertions&lt;/code&gt;. Since recommended only gives error when the score is below 50, I'm using &lt;code&gt;"categories:performance": ["error", { "minScore": 0.9 }]&lt;/code&gt; to give an error if it's below 90 to be more strict. You can also turn off specific rules, i.e. &lt;code&gt;"unsized-images": "off"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now if you want to keep the report, you can use &lt;code&gt;upload&lt;/code&gt; with &lt;code&gt;target&lt;/code&gt; to upload the report to a server. If you don't want to save them yourself, you can use the &lt;code&gt;temporary-public-storage&lt;/code&gt; option provided by Lighthouse. This will upload the report on a temporary page you can get access to for free. &lt;/p&gt;

&lt;p&gt;In the end, if you don't want to click into the workflow console in the pull request, you can use the Lighthouse CI GitHub app to set statuses. Here is an example of how Lighthouse has set a status for each URL in the pull request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffa9wq983luu3ztdw0vlk.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffa9wq983luu3ztdw0vlk.PNG" alt="Pull request status"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, install the GitHub app &lt;a href="https://github.com/apps/lighthouse-ci" rel="noopener noreferrer"&gt;Lighthouse CI&lt;/a&gt; and give access to the repositories you want. Then copy the token and store it under Settings &amp;gt; Secrets in your GitHub repository with the name &lt;code&gt;LHCI_GITHUB_APP_TOKEN&lt;/code&gt;. Now you only need to update the workflow &lt;code&gt;lighthouse.yml&lt;/code&gt; file with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# below - uses: actions/checkout@v2&lt;/span&gt;
&lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.head.sha }}&lt;/span&gt;
&lt;span class="c1"&gt;# below lhci autorun&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;LHCI_GITHUB_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LHCI_GITHUB_APP_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete file will be something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lighthouse-ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event.pull_request.head.sha }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install &amp;amp; build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;npm install&lt;/span&gt;
          &lt;span class="s"&gt;npm run build&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run Lighthouse CI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;sudo npm i -g @lhci/cli&lt;/span&gt;
          &lt;span class="s"&gt;lhci autorun&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;LHCI_GITHUB_APP_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.LHCI_GITHUB_APP_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out my &lt;a href="https://github.com/gautemo/lighthouse-example" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; for a demonstration with a &lt;a href="https://github.com/gautemo/lighthouse-example/pull/9" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; containing Lighthouse checks.&lt;/p&gt;

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

&lt;p&gt;Lighthouse provides reports that help us create better websites. It's easy to use without any knowledge by passing in any URL in &lt;a href="https://web.dev/measure/" rel="noopener noreferrer"&gt;web.dev/measure&lt;/a&gt;, during development with Chrome DevTools, and even automate it into your pipeline to continually keep the quality on top.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>a11y</category>
      <category>ci</category>
    </item>
    <item>
      <title>Emojis Picker - Vite/Electron app</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Sun, 27 Sep 2020 09:11:10 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/emojis-picker-vite-electron-app-2ckp</link>
      <guid>https://dev.to/gautemeekolsen/emojis-picker-vite-electron-app-2ckp</guid>
      <description>&lt;p&gt;I was tired of googling emojis to copy/paste them. I wanted something similar to the keyboards on our mobile phone where all emojis are easily found. So I created Emojis Picker.&lt;/p&gt;

&lt;p&gt;You can find it on &lt;a href="https://www.microsoft.com/en-us/p/emojis-picker/9nfq6j6h002j" rel="noopener noreferrer"&gt;Microsoft Store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftdh08z7vzs0by7o3im1w.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftdh08z7vzs0by7o3im1w.PNG" alt="Screenshot of Emojis Picker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try it out and let me know what you think 😄&lt;/p&gt;

&lt;p&gt;The project is created using &lt;a href="https://github.com/vitejs/vite" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, Vue 3, and &lt;a href="https://www.electronjs.org/" rel="noopener noreferrer"&gt;Electron&lt;/a&gt; for a cross-platform desktop app.&lt;/p&gt;

&lt;p&gt;You can check out the code on &lt;a href="https://github.com/gautemo/emoji-picker" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you want a similar setup.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>vue</category>
    </item>
    <item>
      <title>Global state in React with Vue!</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Sat, 26 Sep 2020 18:56:42 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/global-state-in-react-with-vue-4n08</link>
      <guid>https://dev.to/gautemeekolsen/global-state-in-react-with-vue-4n08</guid>
      <description>&lt;p&gt;There exist a million (or many) global state solutions in React. It seems like the community is struggling to find the best solution. So here I'm going to come with yet another one. &lt;/p&gt;

&lt;p&gt;Recently Vue 3 was released. I know, Vue is another framework, but Vue solves the reactivity in a way that it's not tied to the framework. Which means we can use the reactivity everywhere, including React.&lt;/p&gt;

&lt;p&gt;First, let's create a store file.&lt;br&gt;
&lt;em&gt;store.js&lt;/em&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;reactive&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;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;count&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;increase&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="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;increase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an overview of what the Vue composition API can do except for &lt;code&gt;reactive&lt;/code&gt; you can get an &lt;a href="https://composition-api.vuejs.org/api.html#reactivity-apis"&gt;overview here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we can import the reactive &lt;code&gt;store&lt;/code&gt; object and the &lt;code&gt;increase&lt;/code&gt; method from the store.js file anywhere we like to. The problem is that React functions don't know when to re-run the function to render the updated values. We will create a custom hook to deal with this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;useStore.js&lt;/em&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useReducer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;watch&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;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;stores&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;ignored&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceUpdate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useReducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="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;stopWatch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stores&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;forceUpdate&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;stopWatch&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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can either use &lt;code&gt;useState&lt;/code&gt; or &lt;code&gt;useReducer&lt;/code&gt; to make the component &lt;a href="https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate"&gt;update itself&lt;/a&gt;. We are watching the params &lt;code&gt;stores&lt;/code&gt; with the Vue Composition API and calls &lt;code&gt;forceUpdate&lt;/code&gt; on every change. Also, we stop watching on component unmount by returning &lt;code&gt;stopWatch&lt;/code&gt; in &lt;code&gt;useEffect&lt;/code&gt;. Any amount of stores could be passed into our &lt;code&gt;useStore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Bump.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;increase&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;./store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Bump&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;increase&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;+1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;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 could also do &lt;code&gt;store.count++&lt;/code&gt; directly here if we wanted.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Counter.js&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useStore&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;./useStore&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&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;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Complete example on &lt;a href="https://stackblitz.com/edit/react-global-state-with-vue"&gt;StackBlitz&lt;/a&gt;&lt;br&gt;
&lt;iframe src="https://stackblitz.com/edit/react-global-state-with-vue?" width="100%" height="500"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Afterthoughts
&lt;/h2&gt;

&lt;p&gt;I actually think this is a nice and simple way of handling a global state. No need for extra components, reduce, dispatch, and/or complete re-assigning of the whole state object. This way we can create exactly as many global stores as we want in a clean way.&lt;/p&gt;

&lt;p&gt;Importing the whole Vue might create a bigger bundle size. But you can import only Vue's reactivity module &lt;a href="https://github.com/vuejs/vue-next/tree/master/packages/reactivity"&gt;@vue/reactivity&lt;/a&gt; and &lt;a href="https://github.com/vue-reactivity/watch"&gt;@vue-reactivity/watch&lt;/a&gt; or rely on tree shaking for a small bundle.&lt;/p&gt;

&lt;p&gt;Now not every developer would want a different way of dealing with component state and global state, so the React way and Vue way might be confusing in the same project. But it's at least an interesting and fun idea.&lt;/p&gt;

</description>
      <category>react</category>
      <category>vue</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Vue 3 is out!</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Fri, 18 Sep 2020 19:13:57 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/vue-3-is-out-3f1f</link>
      <guid>https://dev.to/gautemeekolsen/vue-3-is-out-3f1f</guid>
      <description>&lt;p&gt;Today Vue 3 was released! &lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1306992969728380930-438" src="https://platform.twitter.com/embed/Tweet.html?id=1306992969728380930"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1306992969728380930-438');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1306992969728380930&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I'm really looking forward to using Vue 3 for all my projects. It's really a well-done framework and totally my favorite.&lt;/p&gt;

&lt;p&gt;Some of the main benefits are &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Composition API&lt;/li&gt;
&lt;li&gt;Performance Improvements&lt;/li&gt;
&lt;li&gt;Improved TypeScript integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the keynote announcement here:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Vp5ANvd88x0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;See my previous article, &lt;a href="https://dev.to/gautemeekolsen/vue-3-is-coming-3icj"&gt;Vue 3 is Coming!&lt;/a&gt;, for all the benefits and changes to Vue 3, here on dev.to. &lt;/p&gt;

</description>
      <category>news</category>
      <category>vue</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What's new in JavaScript - ES2020</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Wed, 08 Jul 2020 14:00:20 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/what-s-new-in-javascript-es2020-22m4</link>
      <guid>https://dev.to/gautemeekolsen/what-s-new-in-javascript-es2020-22m4</guid>
      <description>&lt;p&gt;In June 2020, a handful of new features arrived in the JavaScript language.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR - To Long, Didn't Read
&lt;/h2&gt;

&lt;p&gt;If you don't want to read the entire article, I have gathered the most noteworthy in this image.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jqqO7oSi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d35knzx2fcy46vb7e6mr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jqqO7oSi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/d35knzx2fcy46vb7e6mr.png" alt="ES2020 features" width="880" height="674"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Ecma International is responsible for standardizing JavaScript. Therefore they make the ECMAScript specification. So when someone refers to ECMAScript you can think of this as a synonym to JavaScript. Since 2015 they create yearly editions. Therefore we refer to the edition with the year, ie ECMAScript 2015 is shortened to ES2015. But there are a lot who use the count of the number of editions when referring to an edition, therefore ES6 is the same as ES2015. Future features not yet released are referred to as ESNext.&lt;/p&gt;

&lt;p&gt;In June ECMAScript 2020/ES2020/ES11 was released and is already implemented in modern browsers. Let's look at what advantages this gives us.&lt;/p&gt;
&lt;h2&gt;
  
  
  Nullish coalescing
&lt;/h2&gt;

&lt;p&gt;If you want to assign a value, but want a default value in case it is &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;, you can use &lt;code&gt;??&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anonymous&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;Here the name will be "anonymous" if the object person doesn't have fullName set. If the person has a value for fullName, then that will be written to the variable name. &lt;/p&gt;

&lt;p&gt;You might think that this is something you always have been able to do with &lt;code&gt;||&lt;/code&gt;. But this is only almost the same, if the value before &lt;code&gt;||&lt;/code&gt; is falsy, the evaluation won't short-circuit and the next value will be used. But remember that an empty string &lt;code&gt;''&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;NaN&lt;/code&gt;, and &lt;code&gt;false&lt;/code&gt; are falsy and will use the default value, something that might not be wanted if we want to assign those values. &lt;code&gt;??&lt;/code&gt; uses instead nullish, which only checks for &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;preferredSound&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;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preferredSound&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="c1"&gt;// value is 0&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;soundWrong&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preferredSound&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="c1"&gt;// value is 50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;50 is only used if preferredSound is not set or null, it should be possible to set the sound-level to zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional chaining
&lt;/h2&gt;

&lt;p&gt;If you want to use properties that are nested in several levels in an object, you previously had to check if they are not &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt; for the code not to crash. Now we can use &lt;code&gt;?.&lt;/code&gt; before accessing those properties so that the code after is only used if the value is not &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Imagine that we have a house with an owner, that again owns a pet. Here we need to make sure that house, owner, or pet has a value or check them in advance to avoid getting the error "Cannot read property 'type' of null". Here you can see how we needed to deal with this before and after ES2020.&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;house&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Jim&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pet&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="c1"&gt;// Old JavaScript&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;house&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;house&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;house&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pet&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;house&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pet&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;dog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;owner has a dog&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;// ES2020&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;house&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;pet&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;dog&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;owner has a dog&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;h2&gt;
  
  
  Promise.allSettled
&lt;/h2&gt;

&lt;p&gt;If we have more asynchronous requests that run in parallel, you could gather them with &lt;code&gt;Promise.all&lt;/code&gt;. But this will throw an exception if one of the requests fails. What if we want to let every request finish no matter if others fail or not. With &lt;code&gt;Promise.allSettled&lt;/code&gt; it will return when all requests are settled, either resolved or rejected.&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;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reject&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;result1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result2&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allSettled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we can still use the result1 value even though other promises were rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  matchAll
&lt;/h2&gt;

&lt;p&gt;If you want to use regex to find all instances of a regular expression match, you can use &lt;code&gt;match&lt;/code&gt; to get all the substrings. But what if you want both the substring and the index? Then you can use &lt;code&gt;matchAll&lt;/code&gt; and iterate the matches.&lt;/p&gt;

&lt;p&gt;Let's find all the numbers in a string.&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;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Here are some numbers: 5 12 88&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;+/g&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;match&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;matches&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Output:&lt;/span&gt;
&lt;span class="c1"&gt;// ["5", index: 22, input: "Here are some numbers: 5 12 88", groups: undefined]&lt;/span&gt;
&lt;span class="c1"&gt;// ["12", index: 24, input: "Here are some numbers: 5 12 88", groups: undefined]&lt;/span&gt;
&lt;span class="c1"&gt;// ["88", index: 27, input: "Here are some numbers: 5 12 88", groups: undefined]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  BigInt
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BigInt&lt;/code&gt; is a new primitive datatype in JavaScript, the same as &lt;code&gt;Boolean&lt;/code&gt;, &lt;code&gt;Number&lt;/code&gt;, &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Symbol&lt;/code&gt;, and &lt;code&gt;undefined&lt;/code&gt;. &lt;code&gt;BigInt&lt;/code&gt; can handle numbers above the safe integer limit of &lt;code&gt;Number&lt;/code&gt;. That means if we want to deal with numbers larger than 9_007_199_254_740_991, it is wise to use &lt;code&gt;BigInt&lt;/code&gt;. &lt;code&gt;BigInt&lt;/code&gt; is represented with an n at the end of the number.&lt;/p&gt;

&lt;p&gt;Let's add 2 to the number 9_007_199_254_740_991, the correct number should end with the digit 3.&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="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;_007_199_254_740_991&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 9007199254740992&lt;/span&gt;
&lt;span class="nx"&gt;BigInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;_007_199_254_740_991&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;BigInt&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="c1"&gt;// 9007199254740993n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dynamic import
&lt;/h2&gt;

&lt;p&gt;Before we only could import modules statically at the top of the file. Now with dynamic imports, we have the option to do this anywhere in the code on-demand and only when we need it. &lt;code&gt;import()&lt;/code&gt; will return a promise with the module.&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;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;module&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;h2&gt;
  
  
  Module namespace exports
&lt;/h2&gt;

&lt;p&gt;With import and export of JavaScript modules, we have in most situations been able to rename the name of the module. 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="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;values&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;module&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;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;v&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;module&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we have not been able to re-export something from another module with a name change directly. Now with ES2020 we can do it 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="k"&gt;export&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;someUtils&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;utils&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;h2&gt;
  
  
  globalThis
&lt;/h2&gt;

&lt;p&gt;If you write code that runs on multiple environments, for example, both the browser and a Node server, then they have different names for the global object. Browsers use &lt;code&gt;window&lt;/code&gt;, Node uses &lt;code&gt;global&lt;/code&gt;, and web workers use &lt;code&gt;self&lt;/code&gt;. Now, &lt;code&gt;globalThis&lt;/code&gt; will give you the correct global object no matter which environments the code runs in.&lt;/p&gt;

&lt;p&gt;Here is an example where we want to check if we can prompt an alert to the user. If the code runs in a browser the &lt;code&gt;globalThis&lt;/code&gt; will refer to window and alert will be available.&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hi&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;



</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>16 week streak of blogging - how to do it</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Thu, 25 Jun 2020 07:10:59 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/16-week-streak-of-blogging-how-to-do-it-1i3</link>
      <guid>https://dev.to/gautemeekolsen/16-week-streak-of-blogging-how-to-do-it-1i3</guid>
      <description>&lt;p&gt;It's time to stop and celebrate my own victory, I got the 16 week streak badge after blogging for every week on dev.to. I'm happy for myself right now 🥳&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xQBv8oZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yafw9y0blj10newyty5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xQBv8oZP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/yafw9y0blj10newyty5e.png" alt="Badge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How you can do the same
&lt;/h2&gt;

&lt;p&gt;I didn't know I was going to aim for the 16 week streak and write one article every week 4 months from now, but after getting some badges along the way just from wanting to write articles, I decided to go for the longest streak. So the first point is that &lt;strong&gt;you enjoy writing blogs&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;I recommend writing articles because you will get a better understanding of the topic yourself, you can get feedback from the community, you promote yourself as a skilled person, you have documented something so you can easily look back and remember how it was, and you will contribute to the community.&lt;/p&gt;

&lt;p&gt;Going for a streak writing every week, you might feel like it's going to be hard to keep up with the consistency. This helped me; &lt;strong&gt;writing about learnings from your own projects&lt;/strong&gt;, both work and side projects. &lt;strong&gt;Mix it up with some short &lt;em&gt;Today I Learned&lt;/em&gt; blogs&lt;/strong&gt; that contain short useful information that is quick to write. After a while I created a &lt;strong&gt;to-do list with ideas for articles&lt;/strong&gt;. Which helped me for weeks I wasn't sure what I wanted to write about. But &lt;strong&gt;don't plan everything, you will get ideas along the way&lt;/strong&gt;. If you have a topic/project with a lot of different focus areas, you can &lt;strong&gt;split the article into a blog series&lt;/strong&gt;. This will also help you write concise articles that are focused and easy to read. At the end I was going traveling without a computer, so &lt;strong&gt;writing some articles in advance&lt;/strong&gt; and publishing from the mobile helped me.&lt;/p&gt;

&lt;p&gt;Thank you for reading. And remember that the dev.to community is a kind one, so publishing content feels very safe and welcome 🤗&lt;/p&gt;

</description>
      <category>career</category>
      <category>writing</category>
      <category>productivity</category>
      <category>learning</category>
    </item>
    <item>
      <title>CI/CD to AWS with GitHub Actions</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Sun, 21 Jun 2020 23:18:06 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/ci-cd-to-aws-with-github-actions-2m14</link>
      <guid>https://dev.to/gautemeekolsen/ci-cd-to-aws-with-github-actions-2m14</guid>
      <description>&lt;p&gt;This article is part of the &lt;strong&gt;Twitter bot with Kotlin in AWS series&lt;/strong&gt; showing how &lt;a href="https://dev.to/gautemeekolsen/twitter-bot-vue-3-updates-j5b"&gt;I created a Twitter bot for Vue 3 updates&lt;/a&gt;. But this article works as an independent article on how to automatically deploy to AWS from your GitHub repository.&lt;/p&gt;

&lt;p&gt;Now let's wrap it up by setting up continuous build and deploy of our Kotlin Lambda function to AWS. This will make sure the Lambda function is always up to date with the code in the master branch, no need to manually deploy your changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS IAM User
&lt;/h2&gt;

&lt;p&gt;First, we need an &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/getting-started_create-admin-group.html"&gt;IAM user&lt;/a&gt;. If you already have one, you can use the existing Access key ID and Secret access secret key. Or you can just click the "Create access key" button and generate new tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;We need to add two secrets to the GitHub repository. Go to your repository, click the "Settings" tab, and then "Secrets" in the side menu. Add the secrets AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from the IAM user.&lt;/p&gt;

&lt;p&gt;Luckily the GitHub Actions run on machines with a lot of &lt;a href="https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md"&gt;software pre-installed&lt;/a&gt;, which includes the AWS CLI and Gradle.&lt;/p&gt;

&lt;p&gt;Now head to "Actions" in the tab menu. Click "Set up this workflow" from any template. Add this yml code, that will setup Gradle, configure the AWS CLI, build the project, and update the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI/CD&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;master&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;CI-CD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gradle setup&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gradle wrapper&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build &amp;amp; Deploy&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
        &lt;span class="s"&gt;aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
        &lt;span class="s"&gt;aws configure set default.region eu-west-1&lt;/span&gt;
        &lt;span class="s"&gt;./gradlew build&lt;/span&gt;
        &lt;span class="s"&gt;aws lambda update-function-code --function-name twitter-bot-vue-3 --zip-file fileb://build/libs/twitter-bot-vue-3.jar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;That completes our series on how to create your own Twitter bot running in AWS. Check out the Twitter account &lt;a href="https://twitter.com/vue_3"&gt;@vue_3&lt;/a&gt; for the completed product. Also you can find all the code on &lt;a href="https://github.com/gautemo/twitter-bot-vue-3"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>github</category>
      <category>kotlin</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Secrets in AWS and reading from Kotlin</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Sun, 21 Jun 2020 15:32:05 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/secrets-in-aws-and-reading-from-kotlin-238a</link>
      <guid>https://dev.to/gautemeekolsen/secrets-in-aws-and-reading-from-kotlin-238a</guid>
      <description>&lt;p&gt;This article is part of the &lt;strong&gt;Twitter bot with Kotlin in AWS series&lt;/strong&gt; showing how &lt;a href="https://dev.to/gautemeekolsen/twitter-bot-vue-3-updates-j5b"&gt;I created a Twitter bot for Vue 3 updates&lt;/a&gt;. But this article works as an independent article on how to manage your secret values and tokens in AWS and read them from Kotlin code.&lt;/p&gt;

&lt;p&gt;We are using the AWS Secrets Manager to keep our keys and tokens safe. Make sure your role running the code has access to the Secrets Manager.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding secrets with AWS CLI
&lt;/h2&gt;

&lt;p&gt;Run the create-secret command to create secret containing a JSON object with key/value's for all the secrets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws secretsmanager create-secret --name twitter-bot-vue-3/auth --description "Twitter app keys and tokens" --secret-string "{\"consumer-key\":\"API key\",\"consumer-secret\":\"API secret key\",\"access-token\":\"Access token\",\"access-token-secret\":\"Access token secret\"}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading from Kotlin
&lt;/h2&gt;

&lt;p&gt;First, we need to add a JSON serializer and AWS Secrets Manager to &lt;code&gt;build.gradle.kts&lt;/code&gt;.&lt;br&gt;
&lt;em&gt;plugins&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kotlin("plugin.serialization") version "1.3.70"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;dependencies&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;implementation("software.amazon.awssdk:secretsmanager")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create a Secrets.kt file where we create a class that will hold the secrets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlinx.serialization.json.Json&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlinx.serialization.json.JsonConfiguration&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlinx.serialization.json.content&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;software.amazon.awssdk.regions.Region&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;software.amazon.awssdk.services.secretsmanager.SecretsManagerClient&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;secretName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"twitter-bot-vue-3/auth"&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Secrets&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;accessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;

    &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SecretsManagerClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EU_WEST_1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;secrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecretValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GetSecretValueRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;secretId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secretName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JsonConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;jsonObject&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secretString&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;jsonObject&lt;/span&gt;
        &lt;span class="n"&gt;consumerKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"consumer-key"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="n"&gt;consumerSecret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"consumer-secret"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"access-token"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
        &lt;span class="n"&gt;accessTokenSecret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonObject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"access-token-secret"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&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 can now use the Secrets class in our &lt;code&gt;Tweet.kt&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sendTweet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;secrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Secrets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConfigurationBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthConsumerKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthConsumerSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthAccessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;twitter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TwitterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;
    &lt;span class="n"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&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;



</description>
      <category>aws</category>
      <category>kotlin</category>
    </item>
    <item>
      <title>Posting a tweet from Kotlin</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Tue, 16 Jun 2020 08:52:30 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/posting-a-tweet-from-kotlin-10b1</link>
      <guid>https://dev.to/gautemeekolsen/posting-a-tweet-from-kotlin-10b1</guid>
      <description>&lt;p&gt;This article is part of the &lt;strong&gt;Twitter bot with Kotlin in AWS series&lt;/strong&gt; showing how &lt;a href="https://dev.to/gautemeekolsen/twitter-bot-vue-3-updates-j5b"&gt;I created a Twitter bot for Vue 3 updates&lt;/a&gt;. But this article works as an independent article on how to use the Twitter API from Kotlin code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Twitter Developer Console
&lt;/h2&gt;

&lt;p&gt;First, we need to head over to the &lt;a href="https://developer.twitter.com/apps"&gt;Twitter developer console&lt;/a&gt;. If you haven't applied for access you need to do that before creating an app. Now create an app and provide a name, description, and website URL(could be the GitHub repository URL). Leave the rest blank.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ivj0ET9---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6l0d1dpe782ijljqs9k7.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ivj0ET9---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6l0d1dpe782ijljqs9k7.PNG" alt="Alt Text" width="880" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the Keys and tokens tab and generate an Access token and access token secret. You will need the API key, API secret key, Access token, and Access token secret for later. These will be used to perform a tweet from the Twitter account that owns the Twitter developer app. If you want to tweet from another account's behalf, you can get tokens from the &lt;a href="https://developer.twitter.com/en/docs/basics/authentication/oauth-1-0a/obtaining-user-access-tokens"&gt;3-legged OAuth flow&lt;/a&gt;, but I won't cover that here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kotlin Project
&lt;/h2&gt;

&lt;p&gt;You can send a tweet from an HTTP request, but the difficulties lie in the authorization of the requests. Therefore we are using a library &lt;a href="https://github.com/Twitter4J/Twitter4J"&gt;Twitter4J&lt;/a&gt; to do the heavy lifting for us. Other libraries can be found &lt;a href="https://developer.twitter.com/docs/developer-utilities/twitter-libraries"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;build.gradle.kts&lt;/code&gt; file, add this line to your dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;implementation("org.twitter4j", "twitter4j-core", "4.0.7")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add these functions to &lt;code&gt;Tweet.kt&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;twitter4j.TwitterFactory&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;twitter4j.conf.ConfigurationBuilder&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;API key&amp;gt;"&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;API secret key&amp;gt;"&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;Access token&amp;gt;"&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;accessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;Access token secret&amp;gt;"&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sendTweet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;cb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConfigurationBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthConsumerKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthConsumerSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOAuthAccessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;accessTokenSecret&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;twitter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TwitterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;
    &lt;span class="n"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&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;



</description>
      <category>kotlin</category>
    </item>
    <item>
      <title>DynamoDB for our Kotlin Lambda function</title>
      <dc:creator>Gaute Meek Olsen</dc:creator>
      <pubDate>Wed, 10 Jun 2020 11:09:16 +0000</pubDate>
      <link>https://dev.to/gautemeekolsen/dynamodb-for-our-kotlin-lambda-function-1mjb</link>
      <guid>https://dev.to/gautemeekolsen/dynamodb-for-our-kotlin-lambda-function-1mjb</guid>
      <description>&lt;p&gt;This article is part of the &lt;strong&gt;Twitter bot with Kotlin in AWS series&lt;/strong&gt; showing how &lt;a href="https://dev.to/gautemeekolsen/twitter-bot-vue-3-updates-j5b"&gt;I created a Twitter bot for Vue 3 updates&lt;/a&gt;. But this article works as an independent article on how to create a DynamoDB table and interact with it from Kotlin code.&lt;/p&gt;

&lt;p&gt;If you haven't set up a Lambda function in Kotlin before, you can look &lt;a href="https://dev.to/gautemeekolsen/creating-an-aws-lambda-kotlin-function-3j80"&gt;here&lt;/a&gt;. Also remember to give your role running the code permission to access DynamoDB.&lt;/p&gt;

&lt;p&gt;Now it's time to set up our DynamoDB table, add some items, and write code to read and update them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table setup
&lt;/h2&gt;

&lt;p&gt;First, let's create our table through the AWS CLI. We are specifying our required partition key with the --attribute-definitions and --key-schema, saying that the attribute "Project" is a string and HASH for the partition key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws dynamodb create-table --table-name twitter-bot-vue-3 --attribute-definitions AttributeName=Project,AttributeType=S -
-key-schema AttributeName=Project,KeyType=HASH --billing-mode PAY_PER_REQUEST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For my use case, I want an item for each changelog file I'm looking up. They will be defined beforehand, so you can add them through the AWS console, AWS CLI or code if you want. This is how to add an item with the CLI. (for Linux based systems, you can change to single quotes ' on the start and end and skip the escaping of quotes).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws dynamodb put-item --table-name twitter-bot-vue-3 --item "{\"Project\": {\"S\": \"Vue 3\"}, \"Changelog\": {\"S\": \"
https://github.com/vuejs/vue-next/blob/master/CHANGELOG.md\"}, \"LastRecordedChange\": {\"S\": \"\"}}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dependencies
&lt;/h2&gt;

&lt;p&gt;Now go to the &lt;code&gt;build.gradle.kts&lt;/code&gt; file and add these two dependencies (add them to the existing dependencies in your file). You can find the latest version &lt;a href="https://sdk.amazonaws.com/java/api/latest/"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    implementation(platform("software.amazon.awssdk:bom:2.13.18"))
    implementation("software.amazon.awssdk:dynamodb")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Read and update items
&lt;/h2&gt;

&lt;p&gt;I have the file &lt;code&gt;ProjectsDB.kt&lt;/code&gt; which has two methods that interact with the DynamoDB table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;software.amazon.awssdk.services.dynamodb.DynamoDbClient&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;software.amazon.awssdk.services.dynamodb.model.*&lt;/span&gt;

&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;changelog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;lastRecordedChangelog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tableName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"twitter-bot-vue-3"&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;readProjects&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DynamoDbClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&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="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&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="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Changelog"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"LastRecordedChange"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;s&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;fun&lt;/span&gt; &lt;span class="nf"&gt;updateProjectLastRecordedChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DynamoDbClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutableMapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;createAttributeValueString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;updates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutableMapOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LastRecordedChange"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AttributeValueUpdate&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createAttributeValueString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastRecordedChangelog&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AttributeAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributeUpdates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;updates&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;fun&lt;/span&gt; &lt;span class="nf"&gt;createAttributeValueString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;AttributeValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AttributeValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Updating the Lambda function
&lt;/h2&gt;

&lt;p&gt;First, we need a function that checks the latest update to the changelog files in the Vue repositories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.URL&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;checkChangelog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;rawGithubUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://github.com/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"https://raw.githubusercontent.com/"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"blob/"&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="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawGithubUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;first&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 let's update the Lambda function with some logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;runLambda&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;projects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readProjects&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="n"&gt;project&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;latestChange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;checkChangelog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changelog&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="n"&gt;latestChange&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastRecordedChangelog&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastRecordedChangelog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;latestChange&lt;/span&gt;
            &lt;span class="c1"&gt;// tweet(project)&lt;/span&gt;
            &lt;span class="nf"&gt;updateProjectLastRecordedChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&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;That's it. If you want a temporary tweet function that prints the tweet message you can use this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;regex&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="nf"&gt;toRegex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lastRecordedChangelog&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'['&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;']'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${project.name}, ${if(version.isNullOrEmpty()) "&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="s"&gt;" else version} is out! (${project.changelog}) #VueJS"&lt;/span&gt;
    &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&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;



</description>
      <category>aws</category>
      <category>database</category>
      <category>kotlin</category>
    </item>
  </channel>
</rss>
