<?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: Vinicius Kiatkoski Neves</title>
    <description>The latest articles on DEV Community by Vinicius Kiatkoski Neves (@viniciuskneves).</description>
    <link>https://dev.to/viniciuskneves</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%2F117837%2F6f59dc6a-8c08-47e7-aaa4-d941d5bb07fe.jpeg</url>
      <title>DEV Community: Vinicius Kiatkoski Neves</title>
      <link>https://dev.to/viniciuskneves</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viniciuskneves"/>
    <language>en</language>
    <item>
      <title>Use AWS through GitHub Actions without secret keys</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Thu, 17 Mar 2022 12:47:38 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/use-aws-through-github-actions-without-secret-keys-32eo</link>
      <guid>https://dev.to/viniciuskneves/use-aws-through-github-actions-without-secret-keys-32eo</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://unsplash.com/photos/p7MnhIJjDkk"&gt;Cover photo&lt;/a&gt; from &lt;a href="https://unsplash.com/@cbarbalis"&gt;Chris Barbalis&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It all started with the following question: How do we &lt;a href="https://serverfault.com/questions/1096129/safely-store-aws-iam-user-keys-access-and-secret-created-by-iac"&gt;safely store AWS IAM User Keys (Access and Secret) created by IaC&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Imagine the following scenario: you have a Bucket that will host your Frontend assets. Your Frontend lives in another repository and you use, in my example, GitHub Actions to deploy (move) those files to the Bucket. You want to give permission to your GitHub Actions to perform that and only that.&lt;/p&gt;

&lt;p&gt;You can, of course, add your credentials to the repository but you will give it too many permissions.&lt;/p&gt;

&lt;p&gt;You can create a User, in your infrastructure, that has only the permission needed by GitHub Actions. This sounds way better already. The issue is: where do we store this User's keys?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We can have the keys as Output of our Stack. It is not safe, everyone that has access to the logs from your GitHub Action can see it (imagine an open source repository).&lt;/li&gt;
&lt;li&gt;We can store these keys in SSM. It works but everyone that has access to SSM has access to it (not a big deal in my case).&lt;/li&gt;
&lt;li&gt;We can store these keys in Secrets Manager. It works but everyone that has access to Secrets Manager has access to it (not a big deal in my case).&lt;/li&gt;
&lt;li&gt;We can generate the keys manually. It works and only the person that generated the keys will know it (hopefully store them in GitHub Secrets and forget about it).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All the steps above work to some extent. The only one that doesn't require any manual effort is option 1. which is the least secure one.&lt;/p&gt;

&lt;p&gt;The best approach, from my understanding, is another one. I would like to go through it step by step and how to setup it up using AWS CDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenID Connect (OIDC)
&lt;/h2&gt;

&lt;p&gt;Since October of 2021 GitHub Actions allow it: &lt;a href="https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/"&gt;https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenID Connect allows you to connect an external provider (GitHub Actions) with your AWS account. You can read more about it here: &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The external provider will assume a defined role that has the permissions (policies) we need. You can read more about IAM roles here: &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Breaking it into steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need a policy that allows us to move files into the Bucket (nothing more than that);&lt;/li&gt;
&lt;li&gt;We need a role that will be assumed by GitHub Actions and this role needs to use the policy from step 1.&lt;/li&gt;
&lt;li&gt;We need an OIDC identity provider to connect our GitHub Actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;GitHub and AWS have an extensive documentation about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"&gt;Configuring OpenID Connect in Amazon Web Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html"&gt;Creating OpenID Connect (OIDC) identity providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-actions/configure-aws-credentials#assuming-a-role"&gt;aws-actions/configure-aws-credentials assuming role&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam-readme.html#openid-connect-providers"&gt;AWS CDK IAM Module - OpenID Connect Providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How does it look in the code?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let's imagine we have a Bucket
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bucket&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;ol&gt;
&lt;li&gt;Let's create the OIDC following the documentation
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OpenIdConnectProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GitHubProvider&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://token.actions.githubusercontent.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;clientIds&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;sts.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Referred as "Audience" in the documentation&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Let's create our role (AWS CDK makes it easy to link the role with the identity provider)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Role&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;assumedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;OpenIdConnectPrincipal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;StringEquals&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;token.actions.githubusercontent.com:aud&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;sts.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// "Audience" from above&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;StringLike&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;token.actions.githubusercontent.com:sub&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;repo:&amp;lt;ORGANIZATION&amp;gt;/&amp;lt;REPOSITORY&amp;gt;:*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Organization and repository that are allowed to assume this role&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;&lt;em&gt;The example above is a bit different from the one documented by GitHub. Here I'm making it more flexible (all branches, for example, can use it).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;assumedBy&lt;/code&gt; property can be customized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can play with the conditions: &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;You can use other properties from the payload: &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token"&gt;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Let's grant the necessary permission to our role
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grantPut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once deployed everything is ready on the AWS side, now we go to the GitHub side.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let's update our GitHub Action to assume the role
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before&lt;/span&gt;
&lt;span class="nn"&gt;...&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;Configure AWS Credentials 🔧&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;aws-actions/configure-aws-credentials@v1.6.1&lt;/span&gt; &lt;span class="c1"&gt;# Version at the time of this writing&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# We are loading keys stored in secrets&lt;/span&gt;
    &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
    &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
&lt;span class="pi"&gt;-&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;npm run deploy&lt;/span&gt; &lt;span class="c1"&gt;# Just an example&lt;/span&gt;

&lt;span class="c1"&gt;# After&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt; &lt;span class="c1"&gt;# This is required for actions/checkout (GitHub documentation mentions that)&lt;/span&gt;
&lt;span class="nn"&gt;...&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;Configure AWS Credentials 🔧&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;aws-actions/configure-aws-credentials@v1.6.1&lt;/span&gt; &lt;span class="c1"&gt;# Version at the time of this writing&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# We are loading keys stored in secrets&lt;/span&gt;
    &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::&amp;lt;ACCOUNT&amp;gt;:role/&amp;lt;ROLE-NAME&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# You can specify the role name when creating it or AWS will automatically generate one for you&lt;/span&gt;
    &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-central-1&lt;/span&gt;
&lt;span class="pi"&gt;-&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;npm run deploy&lt;/span&gt; &lt;span class="c1"&gt;# Just an example&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now safely remove the secrets from GitHub and also don't need any "special" user to deploy through GitHub Actions.&lt;/p&gt;




&lt;p&gt;This helps me a lot to "wrap" the knowledge and I believe it can help other people too.&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"&gt;https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html"&gt;https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam-readme.html"&gt;https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_iam-readme.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aws-actions/configure-aws-credentials"&gt;https://github.com/aws-actions/configure-aws-credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.plainenglish.io/no-more-aws-access-keys-in-github-secrets-6fea25c9e1a4"&gt;https://aws.plainenglish.io/no-more-aws-access-keys-in-github-secrets-6fea25c9e1a4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://awsteele.com/blog/2021/09/15/aws-federation-comes-to-github-actions.html"&gt;https://awsteele.com/blog/2021/09/15/aws-federation-comes-to-github-actions.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/mmiranda/github-actions-autenticando-na-aws-via-oidc-2cf"&gt;https://dev.to/mmiranda/github-actions-autenticando-na-aws-via-oidc-2cf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>cdk</category>
      <category>github</category>
      <category>iac</category>
    </item>
    <item>
      <title>Importing existing AWS resources using AWS CDK</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Mon, 21 Feb 2022 12:20:35 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/importing-existing-aws-resources-using-aws-cdk-2l22</link>
      <guid>https://dev.to/viniciuskneves/importing-existing-aws-resources-using-aws-cdk-2l22</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;a href="https://unsplash.com/photos/NPsqco-tNR0" rel="noopener noreferrer"&gt;Cover photo&lt;/a&gt; from &lt;a href="https://unsplash.com/@gavla" rel="noopener noreferrer"&gt;Gavin Allanwood&lt;/a&gt; on &lt;a href="https://unsplash.com/" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I like infrastructure and I advocate AWS CDK for one reason: it allows us to use Typescript (or other languages) which is less of a blocker for new contributors in frontend. It is verbose but it does a lot of the weight lifting for you and is extremely flexible as well.&lt;/p&gt;

&lt;p&gt;I've always been curious about the following situation: most companies start setting up their infrastructure through AWS Console. At some point Infrastructure as Code is introduced. How do we close the gap between them? Shall we code everything from scratch?&lt;/p&gt;

&lt;p&gt;For a while (end of 2019) we can &lt;a href="https://aws.amazon.com/blogs/aws/new-import-existing-resources-into-a-cloudformation-stack/" rel="noopener noreferrer"&gt;import existing AWS resources into a CloudFormation Stack&lt;/a&gt;. The blog post from AWS does a good job explaining how it works. There is another &lt;a href="https://rusyasoft.github.io/aws,%20cdk/2021/05/23/cdk-existing-resource/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; that explains how to do it with AWS CDK.&lt;/p&gt;

&lt;p&gt;My goal here is to make it visual and with code examples. If you follow the steps from both blog posts you are good to go but I found myself confused with some names in between and decided to summarize it for myself (as I'm pretty sure I won't remember how to do it next time) and maybe help you as well.&lt;/p&gt;

&lt;p&gt;I'm assuming that you're familiar with AWS CDK and also with CloudFormation. If not, don't hesitate to ask questions and I can try to answer them or guide you to other resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS CDK output
&lt;/h2&gt;

&lt;p&gt;AWS CDK "simply" translates some code we write into a CloudFormation template in this case, a &lt;code&gt;.json&lt;/code&gt; file by the end. It is this file that is used by AWS CloudFormation to setup our infrastructure.&lt;/p&gt;

&lt;p&gt;The output from AWS CDK can be found inside the &lt;code&gt;cdk.out/&lt;/code&gt; folder. The CloudFormation template from your stack is in the file &lt;code&gt;MyStackName.template.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;AWS CDK creates this file whenever we run &lt;code&gt;synth&lt;/code&gt; or &lt;code&gt;deploy&lt;/code&gt; (which runs &lt;code&gt;synth&lt;/code&gt; beforehand). The main difference is that &lt;code&gt;deploy&lt;/code&gt; uploads this file to AWS CloudFormation, while &lt;code&gt;synth&lt;/code&gt; "only" creates it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing existing AWS resources
&lt;/h2&gt;

&lt;p&gt;I will manually create a bucket in my account and run a small example (the steps are similar for more resources but it takes more time). I called it &lt;code&gt;viniciuskneves-aws-cdk-real-bucket&lt;/code&gt; and you can see it in the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154742321-51ab18d5-8c7f-4cbf-a53e-18ba86b773c5.png" class="article-body-image-wrapper"&gt;&lt;img alt="Created S3 Bucket" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154742321-51ab18d5-8c7f-4cbf-a53e-18ba86b773c5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to import an existing resource to our stack, we first need to create the stack. We can create a stack with a dummy resource as follows:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&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="s2"&gt;aws-cdk-lib&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;Construct&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="s2"&gt;constructs&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;Bucket&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="s2"&gt;aws-cdk-lib/aws-s3&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AwsCdkTypescriptStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DummyBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DummyBucket&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;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Easier to remove later&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;&lt;em&gt;The bucket removal policy was set to delete as this bucket will be deleted later and it is not our real bucket, which will be imported later on.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now we can run &lt;code&gt;deploy&lt;/code&gt; and, after deploying our Stack, we should be able to see it in AWS CloudFormation console:&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%2Fuser-images.githubusercontent.com%2F1734873%2F154743207-44004b28-f184-4859-ba66-d5f0c03c6e16.png" class="article-body-image-wrapper"&gt;&lt;img alt="Created CloudFormation Stack" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154743207-44004b28-f184-4859-ba66-d5f0c03c6e16.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Following the instructions from AWS, we need to create a template with the resource we would like to import in this case, a S3 bucket. We can rely on AWS CDK to do the job for us. To create a S3 bucket, we need the following piece of code:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&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="s2"&gt;aws-cdk-lib&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;Construct&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="s2"&gt;constructs&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;Bucket&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="s2"&gt;aws-cdk-lib/aws-s3&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AwsCdkTypescriptStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DummyBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DummyBucket&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;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RealBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RealBucket&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;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RETAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Recommended in order to avoid data loss&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;



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

&lt;/div&gt;

&lt;p&gt;Now we can run &lt;code&gt;synth&lt;/code&gt; to obtain our CloudFormation template. This template contains a bucket but it is not yet deployed, we will use it to manually import the resource through the AWS console.&lt;/p&gt;

&lt;p&gt;We can go to our stack in AWS CloudFormation console, click &lt;code&gt;Stack Actions&lt;/code&gt; and &lt;code&gt;Import resources into stack&lt;/code&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%2Fuser-images.githubusercontent.com%2F1734873%2F154744057-9ef509f0-9ec5-4571-8590-4a500236e363.png" class="article-body-image-wrapper"&gt;&lt;img alt="Import resources into AWS CloudFormation stack" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154744057-9ef509f0-9ec5-4571-8590-4a500236e363.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There we select the file created by &lt;code&gt;synth&lt;/code&gt; in the step above (&lt;code&gt;cdk.out/MyStackName.template.json&lt;/code&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%2Fuser-images.githubusercontent.com%2F1734873%2F154744208-52564a78-f548-4065-beb3-30ef71537c0a.png" class="article-body-image-wrapper"&gt;&lt;img alt="Select template file from synth" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154744208-52564a78-f548-4065-beb3-30ef71537c0a.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This step tells CloudFormation which new template to use.&lt;br&gt;
The next step is how to link the CloudFormation template with an existing resource. AWS S3 uses the bucket name for linking, so we need to input the bucket name (from the bucket created earlier) here:&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%2Fuser-images.githubusercontent.com%2F1734873%2F154747626-136e900c-94af-4b92-8d3f-27c34d0ab2e1.png" class="article-body-image-wrapper"&gt;&lt;img alt="Identify S3 bucket resource in CloudFormation" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154747626-136e900c-94af-4b92-8d3f-27c34d0ab2e1.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the last step you will see the applied changes and our imported bucket will be there:&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%2Fuser-images.githubusercontent.com%2F1734873%2F154749075-23e4e0f8-43b2-41b8-8406-e050322d3f9e.png" class="article-body-image-wrapper"&gt;&lt;img alt="Changes applied to CloudFormation stack after import resource" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154749075-23e4e0f8-43b2-41b8-8406-e050322d3f9e.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a while (in our example it happens almost immediately as we're talking about a single resource) you will find the resource imported in your stack:&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%2Fuser-images.githubusercontent.com%2F1734873%2F154749260-511fe8d2-bb56-4a37-9646-606893b5c961.png" class="article-body-image-wrapper"&gt;&lt;img alt="CloudFormation events importing resource" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154749260-511fe8d2-bb56-4a37-9646-606893b5c961.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154749541-216cb1d4-c8d4-4899-a901-6d64c3a8f94b.png" class="article-body-image-wrapper"&gt;&lt;img alt="List of all S3 resources from CloudFormation template" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154749541-216cb1d4-c8d4-4899-a901-6d64c3a8f94b.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can try to run deploy again and we will get a message that no changes have been found in the stack, which means success to us:&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%2Fuser-images.githubusercontent.com%2F1734873%2F154749980-27f15879-3c0e-48f4-a15b-8551fc177ad5.png" class="article-body-image-wrapper"&gt;&lt;img alt="AWS CDK deploy no changes" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154749980-27f15879-3c0e-48f4-a15b-8551fc177ad5.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From now on we can make changes in the stack and it will be applied as expected, as if the resource had been created by us, in AWS CDK, since the beginning:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RealBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RealBucket&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;websiteIndexDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RETAIN&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;After deploying, we've:&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%2Fuser-images.githubusercontent.com%2F1734873%2F154750620-c859236a-6bdf-451f-af16-d836c703e770.png" class="article-body-image-wrapper"&gt;&lt;img alt="Static web hosting enabled for bucket" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154750620-c859236a-6bdf-451f-af16-d836c703e770.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can finally remove the &lt;code&gt;DummyBucket&lt;/code&gt; and as it had a destroy policy, the bucket will be removed as expected (no need to manually delete it):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&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="s2"&gt;aws-cdk-lib&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;Construct&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="s2"&gt;constructs&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;Bucket&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="s2"&gt;aws-cdk-lib/aws-s3&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="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AwsCdkTypescriptStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RealBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RealBucket&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;websiteIndexDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RETAIN&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;After deploying, we've:&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%2Fuser-images.githubusercontent.com%2F1734873%2F154750988-1b0c198c-f0d6-498b-8c2b-f8dc1ca18a3f.png" class="article-body-image-wrapper"&gt;&lt;img alt="Resources list with only one bucket" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154750988-1b0c198c-f0d6-498b-8c2b-f8dc1ca18a3f.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Importing resources that don't follow defaults
&lt;/h2&gt;

&lt;p&gt;Our example above followed a clean approach: the real bucket had no extra configuration, it was the default, the same setup from a bucket created from AWS CDK without any configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would happen if the bucket had, for example, static webhosting setup?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It will allow us to import the resource as we did in the example above. One curiosity: the defaults, from AWS CDK, won't override already set configuration.&lt;/p&gt;

&lt;p&gt;If we deploy the stack, it won't disable static webhosting, for example. The only way to disable it (overwrite it), is to enable it through CDK, deploy, and then remove it from CDK and deploy again.&lt;/p&gt;

&lt;p&gt;Even if you run &lt;code&gt;cdk diff&lt;/code&gt; you won't get this as a diff as it will compare the output of &lt;code&gt;synth&lt;/code&gt; with the template that has been uploaded to CloudFormation. Stack drift won't highlight it as well which is weird.&lt;/p&gt;

&lt;p&gt;It is a good approach as it won't magically overwrite everything that has been working. It could be tricky though if you don't know that a resource had been imported and some defaults don't apply to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would happen if the template had some setup, like static webhosting, when importing an existing resource?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is the opposite of the situation above. The resource we create in AWS CDK to be imported has some setup and is not the default resource.&lt;/p&gt;

&lt;p&gt;There are two cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Imported resource and template have the same setup (static webhosting enabled): no problem. The resource is imported and changes made in CDK will apply once you deploy.&lt;/li&gt;
&lt;li&gt;Imported resource and template have a different setup (static webhosting enabled in template): the difference won't be applied but drift will warn you that there is a difference.&lt;/li&gt;
&lt;/ol&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%2Fuser-images.githubusercontent.com%2F1734873%2F154757270-48799f6f-a112-40bd-8e83-b6e33e50dfe7.png" class="article-body-image-wrapper"&gt;&lt;img alt="Drift from imported resource" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F1734873%2F154757270-48799f6f-a112-40bd-8e83-b6e33e50dfe7.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I hope it helps you, as it helped me, to visualize the process described in both blog posts mentioned in the introduction. I also went a bit further to explore drifts in the CloudFormation stack but it is a bit weird (check the examples above).&lt;/p&gt;

&lt;p&gt;If you've further insights/questions, feel free to drop them in the comments!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awscdk</category>
      <category>iac</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Prettier, ESLint and Typescript</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Mon, 20 Sep 2021 11:21:55 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/prettier-eslint-and-typescript-491j</link>
      <guid>https://dev.to/viniciuskneves/prettier-eslint-and-typescript-491j</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover image from &lt;a href="https://marvel-b1-cdn.bc0a.com/f00000000075552/www.perforce.com/sites/default/files/image/2019-03/image-blog-linting-important-use-lint-tools.png"&gt;peforce.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I decided to write this article to sum up a struggle of mine. We've started a new project in the company, Prettier was set up, ESLint was set up and at some point, we added Typescript. By the end, Typescript was also set up. CI was linting, commit hooks were also linting, VSCode was fixing the code, and so on (that is what I thought).&lt;br&gt;
At some point I was playing with the project and realized that some files were being warned by my editor but not when running the linter (&lt;code&gt;npm run lint&lt;/code&gt; in my case). I got triggered. I have a hard time accepting that something works but I can't understand unless it is an external tool that I didn't have to set up myself but that was not the case here.&lt;/p&gt;

&lt;p&gt;In this article, I will summarize some understandings that I've about integrating all the tools above. The main focus is how to set up Prettier, how to set up ESLint, how to integrate both, and by the end how to add Typescript to it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prettier
&lt;/h2&gt;

&lt;p&gt;The first tool I want to explore is &lt;a href="https://prettier.io/"&gt;Prettier&lt;/a&gt;. I would leave it to you to read more about what it is but, in short, it is a code formatter. What does it mean? It means that it will keep your codebase consistent (in terms of coding style). Do you use &lt;code&gt;;&lt;/code&gt;? If yes, it will ensure that all your files have it, for example. I like it for two reasons: we barely have to discuss code formatting and it is easy to onboard new members to the team.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At the time of this writing, Prettier is in version 2.4.1, so keep in mind things might change (especially formatting) in future versions.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How to set up Prettier?
&lt;/h3&gt;

&lt;p&gt;I will consider you have a project set up already so in short, you need to install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i prettier &lt;span class="c"&gt;#--save-dev and --save-exact are recommended&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now you can start using Prettier. You don't need any configuration (if you don't want it). You can run it against your codebase with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prettier &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; at the end means to run across your whole codebase. You can run for a specific file or pattern if you want.&lt;br&gt;
This command will print the files formatted, nothing special. A more useful command happens when you add &lt;code&gt;--write&lt;/code&gt; flag. Instead of printing the formatted code, it will write to the origin file.&lt;/p&gt;

&lt;p&gt;Let's create a file called &lt;code&gt;index.js&lt;/code&gt; with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run &lt;code&gt;npx prettier index.js&lt;/code&gt;, the output will be:&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;a&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It automatically adds the &lt;code&gt;;&lt;/code&gt; for us but it is not saved in the file. If we run &lt;code&gt;npx prettier index.js --write&lt;/code&gt; though, the file will change and the &lt;code&gt;;&lt;/code&gt; will be added to it.&lt;/p&gt;

&lt;p&gt;Cool, that is the simplest setup we can have with Prettier. The &lt;a href="https://prettier.io/docs/en/options.html"&gt;default rules are documented on their website&lt;/a&gt; and can be customized (a bit). We will take a look into it next but before I want to mention another flag: &lt;code&gt;--check&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--check&lt;/code&gt; flag, &lt;code&gt;npx prettier index.js --check&lt;/code&gt;, is useful if you just want to check if a file (or the codebase with &lt;code&gt;.&lt;/code&gt;) is Prettier compliant. It is useful for CIs and git hooks, for example, if you just want to warn the user (you can also enable &lt;code&gt;--write&lt;/code&gt; in these scenarios).&lt;/p&gt;

&lt;p&gt;If we consider the following code again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run &lt;code&gt;npx prettier index.js --check&lt;/code&gt;, we get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checking formatting...
[warn] index.js
[warn] Code style issues found in the above file(s). Forgot to run Prettier?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prettier configuration
&lt;/h3&gt;

&lt;p&gt;You can configure Prettier to some extend. You can do it via the CLI or via a &lt;a href="https://prettier.io/docs/en/configuration.html"&gt;configuration file&lt;/a&gt;, which is more adequate. The configuration file could be in a variety of formats so you can choose the one that fits you best.&lt;/p&gt;

&lt;p&gt;Add the configuration file to the root of your project (you can have configurations/folder but I would leave it up to you to explore this path) and start adding rules to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.prettierrc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"semi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration file and the following code, again, the &lt;code&gt;--check&lt;/code&gt; run will succeed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npx prettier index.js --check&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checking formatting...
All matched files use Prettier code style!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On top of that, you can also extend configuration and set up a few other things. Check their &lt;a href="https://prettier.io/docs/en/configuration.html"&gt;configuration documentation&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://eslint.org/"&gt;ESLint&lt;/a&gt; has been around for a while. In short, it does a bit more than Prettier as it analyzes your code to find problems (or patterns that you don't want, like variables that are not used should be removed). Again, I invite you to read ESLint documentation if you want to go deeper into the topic. I like ESLint for the simple reason it helps me to find problems and configure some patterns in the project (it might be useful when onboarding new people). It is extremely extensible as well in case you're interested.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;At the time of this writing, ESLint is in version 7.32.0, so keep in mind things might change (especially formatting) in future versions. Version 8 is in beta at the moment.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to set up ESLint?
&lt;/h3&gt;

&lt;p&gt;In short, quite similar to Prettier but you need the configuration file. I will consider you have a project set up already so, in short, you need to install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i eslint &lt;span class="c"&gt;#--save-dev is recommended&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need a configuration file. You can create one by yourself or you can run the command below that bootstraps one for you (it can add a lot of presets already):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx eslint &lt;span class="nt"&gt;--init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start with an empty configuration file though, it is enough to run ESLint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .eslintrc.js&lt;/span&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now run it, similar to Prettier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx eslint &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;One thing to note here: ESLint runs only on &lt;code&gt;.js&lt;/code&gt; files (by default).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's consider the same example as before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npx eslint index.js&lt;/code&gt; and we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:1  error  Parsing error: The keyword 'const' is reserved
✖ 1 problem (1 error, 0 warnings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simply the issue with a default ESLint configuration. It considers ES5 by default, so &lt;code&gt;const&lt;/code&gt; is not allowed yet, and some older setup might make sense to your project but not in general.&lt;/p&gt;

&lt;p&gt;We can spend hours configuring ESLint but in general, we get a default from a style guide (AirBnB for example) and apply it to our project. If you use the init command you can do so.&lt;/p&gt;

&lt;p&gt;Let's install &lt;a href="https://www.npmjs.com/package/eslint-config-airbnb-base"&gt;Airbnb ESLint configuration&lt;/a&gt;, it also requires &lt;code&gt;eslint-plugin-import&lt;/code&gt; to be installed (following their documentation) so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i eslint-config-airbnb-base eslint-plugin-import &lt;span class="c"&gt;# --save-dev is recommended&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we extend it in our configuration, so it will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;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;extends&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="s1"&gt;eslint-config-airbnb-base&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or `airbnb-base`, you can omit `eslint-config-`&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;Running &lt;code&gt;npx eslint index.js&lt;/code&gt; again we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:7   error  'a' is assigned a value but never used  no-unused-vars
1:12  error  Missing semicolon                       semi

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool! Now we get errors defined by the AirBnB guide. We can use the &lt;code&gt;--fix&lt;/code&gt; option, which works similar to &lt;code&gt;--write&lt;/code&gt; from Prettier, in case we want to fix the errors when possible.&lt;/p&gt;

&lt;p&gt;ESLint allows you to extensively configure it if you want. It goes beyond the scope here and I will leave it up to you to explore and play with it: &lt;a href="https://eslint.org/docs/user-guide/configuring/"&gt;https://eslint.org/docs/user-guide/configuring/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prettier + ESLint
&lt;/h2&gt;

&lt;p&gt;There are a lot of tutorials online on how to connect both. I want to take a different approach and try to reason about each tool and how they connect.&lt;/p&gt;

&lt;p&gt;I will assume that we have the following Prettier configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.prettierrc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"semi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will assume that we have the following ESLint 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="c1"&gt;// .eslintrc.js&lt;/span&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;extends&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="s1"&gt;eslint-config-airbnb-base&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;I will assume the following script to run both tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&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="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run Prettier check, we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checking formatting...
All matched files use Prettier code style!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool! If we run ESLint, we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:12  error  Missing semicolon  semi
3:23  error  Missing semicolon  semi

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not so cool! Running ESLint with &lt;code&gt;--fix&lt;/code&gt; will fix these issues. Now if we run Prettier again we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checking formatting...
[warn] index.js
[warn] Code style issues found in the above file(s). Forgot to run Prettier?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run Prettier with &lt;code&gt;--write&lt;/code&gt; it will fix but then ESLint will fail again. It will be like this forever. If the goal were just formatting, I would say pick one of the tools and ignore the other, but we want the power of both tools, especially as ESLint is more than just formatting your code.&lt;/p&gt;

&lt;p&gt;Prettier provides two packages that integrate with ESLint.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/prettier/eslint-config-prettier"&gt;&lt;code&gt;eslint-config-prettier&lt;/code&gt;&lt;/a&gt;: turns off rules that might conflict with Prettier.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/prettier/eslint-plugin-prettier"&gt;&lt;code&gt;eslint-plugin-prettier&lt;/code&gt;&lt;/a&gt;: adds Prettier rules to ESLint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's go step by step. First let's go and install &lt;code&gt;eslint-config-prettier&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i eslint-config-prettier &lt;span class="c"&gt;# --save-dev recommended&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our new &lt;code&gt;.eslintrc.js&lt;/code&gt; will look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;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;extends&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="s1"&gt;eslint-config-airbnb-base&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;eslint-config-prettier&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;Considering the file below, again:&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;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&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="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was a valid file for Prettier but invalid for ESLint. Using the new configuration, it becomes valid as the conflicting &lt;a href="https://github.com/prettier/eslint-config-prettier/blob/main/index.js#L73"&gt;rule &lt;code&gt;semi&lt;/code&gt; has been disabled&lt;/a&gt;.&lt;br&gt;
It is fine if we want to ignore the rules from Prettier but in general, we want Prettier rules to override ESLint rules.&lt;br&gt;
In case we delete the Prettier configuration file and use its defaults (which requires &lt;code&gt;;&lt;/code&gt;), running Prettier check will result in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checking formatting...
[warn] index.js
[warn] Code style issues found in the above file(s). Forgot to run Prettier?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file is not valid anymore as it is missing the &lt;code&gt;;&lt;/code&gt; but the ESLint run won't fail, as the Prettier rules have been disabled when running ESLint.&lt;/p&gt;

&lt;p&gt;One important thing to note here: the order used by &lt;code&gt;extends&lt;/code&gt;, in the ESLint configuration, matters. If we use the following order we will get an error as AirBnB rules will override Prettier disabled rules when running ESLint:&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;extends&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="s1"&gt;eslint-config-prettier&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;eslint-config-airbnb-base&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;Running &lt;code&gt;npx eslint index.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:12  error  Missing semicolon  semi
3:23  error  Missing semicolon  semi

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To mitigate this issue let's install the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i eslint-plugin-prettier &lt;span class="c"&gt;# --save-dev recommended&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then update our &lt;code&gt;.eslintrc.js&lt;/code&gt; file to:&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;extends&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="s1"&gt;eslint-config-airbnb-base&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;plugin:prettier/recommended&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;We replaced &lt;code&gt;eslint-config-prettier&lt;/code&gt; with &lt;code&gt;plugin:prettier/recommended&lt;/code&gt;. Check ESLint docs about extending a plugin: &lt;a href="https://eslint.org/docs/user-guide/configuring/configuration-files#using-a-configuration-from-a-plugin"&gt;https://eslint.org/docs/user-guide/configuring/configuration-files#using-a-configuration-from-a-plugin&lt;/a&gt;&lt;br&gt;
I also recommend you to check what &lt;code&gt;eslint-plugin-prettier&lt;/code&gt; is doing with our ESLint configuration: &lt;a href="https://github.com/prettier/eslint-plugin-prettier/blob/a3d6a2259cbda7b2b4a843b6d641b298f03de5ad/eslint-plugin-prettier.js#L66-L75"&gt;https://github.com/prettier/eslint-plugin-prettier/blob/a3d6a2259cbda7b2b4a843b6d641b298f03de5ad/eslint-plugin-prettier.js#L66-L75&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running ESLint again we will get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:12  error  Insert `;`  prettier/prettier
3:23  error  Insert `;`  prettier/prettier

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to note here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We are getting &lt;code&gt;;&lt;/code&gt; errors again, that have been disabled earlier with &lt;code&gt;eslint-config-prettier&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;The error is coming from the rule &lt;code&gt;prettier/prettier&lt;/code&gt;, which is added by the plugin. All prettier validations will be reported as &lt;code&gt;prettier/prettier&lt;/code&gt; rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Typescript
&lt;/h2&gt;

&lt;p&gt;Let's start from the very basic: running ESLint against TS files.&lt;br&gt;
Right now, running ESLint against your codebase would be &lt;code&gt;npx eslint .&lt;/code&gt;. That is fine until you want to run it against files that are not ending with &lt;code&gt;.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's have these two files in our code base:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;npx eslint .&lt;/code&gt; we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:7   error  'a' is assigned a value but never used  no-unused-vars
1:12  error  Insert `;`                              prettier/prettier

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs against our JS file but not our TS file. To run against TS files you need to add &lt;code&gt;--ext .js,.ts&lt;/code&gt; to the ESLint command. By default, ESLint will only check for &lt;code&gt;.js&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;npx eslint . --ext .js,.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/index.js
1:7   error  'a' is assigned a value but never used  no-unused-vars
1:12  error  Insert `;`                              prettier/prettier

/index.ts
1:7   error  'a' is assigned a value but never used  no-unused-vars
1:12  error  Insert `;`                              prettier/prettier

✖ 4 problems (4 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Working like a charm so far. Let's add some real TS code and run it again. The TS file will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running ESLint only against the &lt;code&gt;.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:8  error  Parsing error: Unexpected token :

✖ 1 problem (1 error, 0 warnings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ESLint doesn't know, by default, how to parse Typescript files. It is a similar problem we faced when running ESLint for the first time with ES5 defaults.&lt;br&gt;
ESLint has a configuration in which you can specify the parser you want to use. There is also a package, as you could imagine, that handles this parsing for us. It is called &lt;a href="https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser"&gt;&lt;code&gt;@typescript-eslint/parser&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @typescript-eslint/parser &lt;span class="c"&gt;# --save-dev recommended&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's configure ESLint to use the new parser:&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;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@typescript-eslint/parser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;extends&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="s1"&gt;eslint-config-airbnb-base&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;plugin:prettier/recommended&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;Running ESLint again (&lt;code&gt;npx eslint index.ts&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:7   error  'a' is assigned a value but never used  no-unused-vars
1:20  error  Insert `;`                              prettier/prettier

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool! Now we can run ESLint on TS files. Nonetheless, we don't have any rules being used so we need to configure or use some styleguide, like the one we used by AirBnB before.&lt;br&gt;
There is &lt;a href="https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin"&gt;&lt;code&gt;@typescript-eslint/eslint-plugin&lt;/code&gt;&lt;/a&gt; that offers us some defaults. Let's go with it for now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @typescript-eslint/eslint-plugin &lt;span class="c"&gt;# --save-dev recommended&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding it to our 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;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@typescript-eslint/parser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;extends&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="s1"&gt;eslint-config-airbnb-base&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;plugin:@typescript-eslint/recommended&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;plugin:prettier/recommended&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;Now running &lt;code&gt;npx eslint index.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:7   error    Type number trivially inferred from a number literal, remove type annotation  @typescript-eslint/no-inferrable-types
1:7   warning  'a' is assigned a value but never used                                        @typescript-eslint/no-unused-vars
1:20  error    Insert `;`                                                                    prettier/prettier

✖ 3 problems (2 errors, 1 warning)
  2 errors and 0 warnings potentially fixable with the `--fix` option.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool! Now we have also proper linting in our Typescript file. We can also see that the Prettier rule still applies as expected.&lt;/p&gt;

&lt;p&gt;Bear in mind that &lt;code&gt;typescript-eslint&lt;/code&gt; is overriding &lt;code&gt;eslint-config-airbnb-base&lt;/code&gt; in this case. It means that some rules won't work in TS files that are still valid on JS files. Let's have the files below to see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js and index.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;a&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both files are identical. Running &lt;code&gt;npx eslint . --ext .js,.ts&lt;/code&gt; we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/index.js
  2:1  error    'a' is constant                         no-const-assign
  2:1  warning  'a' is assigned a value but never used  @typescript-eslint/no-unused-vars

/index.ts
  2:1  warning  'a' is assigned a value but never used  @typescript-eslint/no-unused-vars

✖ 3 problems (1 error, 2 warnings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/typescript-eslint/typescript-eslint/blob/1c1b572c3000d72cfe665b7afbada0ec415e7855/packages/eslint-plugin/src/configs/eslint-recommended.ts#L13"&gt;&lt;code&gt;no-const-assign&lt;/code&gt; rule is overwritten by &lt;code&gt;typescript-eslint&lt;/code&gt; for &lt;code&gt;.ts&lt;/code&gt; files&lt;/a&gt; so we don't get the same error for both files.&lt;br&gt;
To overcome it, we need to change the order of the extended configurations, &lt;code&gt;typescript-eslint&lt;/code&gt; comes first and &lt;code&gt;eslint-config-airbnb-base&lt;/code&gt; next. If we do so:&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;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@typescript-eslint/parser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;extends&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;plugin:@typescript-eslint/recommended&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;eslint-config-airbnb-base&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;plugin:prettier/recommended&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;npx eslint . --ext .js,.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/index.js
  2:1  error    'a' is constant                         no-const-assign
  2:1  error    'a' is assigned a value but never used  no-unused-vars
  2:1  warning  'a' is assigned a value but never used  @typescript-eslint/no-unused-vars

/index.ts
  2:1  error    'a' is constant                         no-const-assign
  2:1  error    'a' is assigned a value but never used  no-unused-vars
  2:1  warning  'a' is assigned a value but never used  @typescript-eslint/no-unused-vars

✖ 6 problems (4 errors, 2 warnings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool! Now we get the same error for both files.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One side note: In this example, I have a codebase with JS/TS, it might not be your case and you might also use another style guide where conflicts won't happen.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That's all folks!
&lt;/h2&gt;

&lt;p&gt;I hope this article helped you to learn or clarify some concepts behind ESLint, Prettier, and Typescript playing together.&lt;/p&gt;

&lt;p&gt;In short, you've to understand which files ESLint will analyze and the order of the configurations you want. Image adding now this into a Vue project, for example, you need to add &lt;code&gt;.vue&lt;/code&gt; to &lt;code&gt;--ext .js,.ts,.vue&lt;/code&gt; and add (or configure) some style guide which will add some rules to your project.&lt;/p&gt;

&lt;p&gt;Most boilerplates will already have some lint set up and you will mostly disable some rules but in case you want to customize it or update packages (especially major bumps), it is important to understand how to perform the changes and the impacts it might have in your project.&lt;/p&gt;

&lt;p&gt;That's is all! Happy linting!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>lint</category>
      <category>prettier</category>
    </item>
    <item>
      <title>Creating a GitHub Action for your Slack release BOT</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Tue, 29 Jun 2021 10:39:10 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/creating-a-github-action-for-your-slack-release-bot-ppj</link>
      <guid>https://dev.to/viniciuskneves/creating-a-github-action-for-your-slack-release-bot-ppj</guid>
      <description>&lt;p&gt;Almost all of us love Slack applications and BOTs that automate something. It could be from an alert, so you're notified as soon as a problem happens, or just a random good morning GIF. In this short tutorial, I would like to go through the process of creating a GitHub Action that posts a message as soon as a release happens (that is our use case) but the main topic is how you can automate things with GitHub Actions, how to reuse "things" across multiple actions and how to start "simple".&lt;/p&gt;

&lt;p&gt;At Homeday we use such a process to announce new releases from our component library &lt;a href="https://github.com/homeday-de/homeday-blocks"&gt;Homeday Blocks&lt;/a&gt; to our &lt;code&gt;#frontend&lt;/code&gt; Slack channel and also to announce product releases to the relevant squad's channels. The release BOT looks like the one below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AF85-0va--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ikynsx7702pyi9n5p0ny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AF85-0va--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ikynsx7702pyi9n5p0ny.png" alt="Release BOT example from Homeday Blocks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-requisite
&lt;/h2&gt;

&lt;p&gt;You need to have a Slack application to perform the next steps. Slack has great documentation on how to setup a new application: &lt;a href="https://api.slack.com/start"&gt;https://api.slack.com/start&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main feature/functionality your application needs to have is: "Incoming Webhooks". It will allow you, later, to create a webhook that will post a message where you want. Each webhook will have an unique URL which you should keep private.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a GitHub Action to trigger the webhook
&lt;/h2&gt;

&lt;p&gt;GitHub Actions got quite popular in the past 1-2 years and are a good way to setup continuous integration to our applications on GitHub. It has way more features than just "run tests on every pull request" but I will leave it up to their docs to walk you through the multitude of options it offers: &lt;a href="https://docs.github.com/en/actions"&gt;https://docs.github.com/en/actions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short, a workflow has a name, a trigger, and job(s). The name helps us to easily identify what the workflow is supposed to do. The trigger defines when the workflow will run, for example, every push, every pull request, every comment, every release... The jobs are a sequence of steps that the workflow will perform, these can be GitHub Actions. Again, GitHub docs will do a better job than me explaining each of these blocks.&lt;/p&gt;

&lt;p&gt;I would like to start creating a generic workflow that will trigger for every push:&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;# Action name&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;CD&lt;/span&gt;

&lt;span class="c1"&gt;# Action triggers&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="s"&gt;main&lt;/span&gt;

 &lt;span class="c1"&gt;# Action jobs&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;deploy&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.3.4&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm run build:app&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm run deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow above is an example of how you can build and deploy your application for every push on &lt;code&gt;main&lt;/code&gt;. Now let's enhance it to notify a channel, on Slack, every time the application is deployed.&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;# Action name&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;CD&lt;/span&gt;

&lt;span class="c1"&gt;# Action triggers&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="s"&gt;main&lt;/span&gt;

 &lt;span class="c1"&gt;# Action jobs&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;deploy&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.3.4&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm run build:app&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm run deploy&lt;/span&gt;
  &lt;span class="na"&gt;slack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&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.3.4&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;Get latest commit info&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;echo "::set-output name=TITLE::$(git show -1 --format='%s' -s)"&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest_commit&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;Announce on Slack 📢&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;curl ${{ secrets.SLACK_RELEASE_BOT_WEBHOOK_URL }} \&lt;/span&gt;
            &lt;span class="s"&gt;--request POST \&lt;/span&gt;
            &lt;span class="s"&gt;--header 'Content-type: application/json' \&lt;/span&gt;
            &lt;span class="s"&gt;--data \&lt;/span&gt;
              &lt;span class="s"&gt;'{&lt;/span&gt;
                &lt;span class="s"&gt;"blocks": [&lt;/span&gt;
                  &lt;span class="s"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"type": "header",&lt;/span&gt;
                    &lt;span class="s"&gt;"text": {&lt;/span&gt;
                      &lt;span class="s"&gt;"type": "plain_text",&lt;/span&gt;
                      &lt;span class="s"&gt;"text": "${{ steps.latest_commit.outputs.TITLE }}"&lt;/span&gt;
                    &lt;span class="s"&gt;}&lt;/span&gt;
                  &lt;span class="s"&gt;}&lt;/span&gt;
                &lt;span class="s"&gt;]&lt;/span&gt;
              &lt;span class="s"&gt;}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It adds a job by the end, called &lt;code&gt;slack&lt;/code&gt;. This jobs &lt;code&gt;needs&lt;/code&gt; the &lt;code&gt;deploy&lt;/code&gt; job to run first so we only announce the release after the deployment. It gets the latest commit message to be used as release title (this is completely open to customization) and afterward, it sends a POST request to our webhook.&lt;/p&gt;

&lt;p&gt;Two things are important to mention here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SLACK_RELEASE_BOT_WEBHOOK_URL&lt;/code&gt; is defined as a secret in the repository&lt;/li&gt;
&lt;li&gt;We reference the output of one step in another step with the help of &lt;code&gt;set-output&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, after deployment, we will have a message with the commit message in Slack. Quite similar to the intro image, the main difference is that there I've added some other blocks into the message.&lt;/p&gt;

&lt;p&gt;## How to reuse it?&lt;/p&gt;

&lt;p&gt;It works fine for the application we setup but now we need to add it to another application. The easiest way to do it is to copy-paste. It works fine until it doesn't 😅&lt;/p&gt;

&lt;p&gt;If we want to keep consistency or help others to setup their workflows, copying a "random" &lt;code&gt;curl&lt;/code&gt; might not be the best experience and can lead to a lot of errors, we can create our own GitHub Action. Clear advantages are easy to reuse and, most important, documentation.&lt;/p&gt;

&lt;p&gt;Again, GitHub Actions docs do a great job here but it might lead you to unanswered questions, like: which kind of action should I create? Docker? Javascript? Composite?&lt;/p&gt;

&lt;p&gt;The simplest, for this scenario, way to share the &lt;code&gt;curl&lt;/code&gt; step through different workflows is creating a composite action. Composite actions allow you to group a sequence of steps without much extra setup. All you need is one file by the end.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;There is, of course, one limitation. Composite actions won't allow you to reuse other actions inside of it. In short: you can't use the &lt;code&gt;uses&lt;/code&gt; key in your steps.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You need to create a new repository and add a file called &lt;code&gt;action.yml&lt;/code&gt;. This file will have the following template:&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;Slack Release BOT&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Slack Release BOT used by us&lt;/span&gt;
&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;webhook_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Slack webhook URL&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Message title&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;composite'&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;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&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;curl ${{ inputs.webhook_url }} \&lt;/span&gt;
          &lt;span class="s"&gt;--request POST \&lt;/span&gt;
          &lt;span class="s"&gt;--header 'Content-type: application/json' \&lt;/span&gt;
          &lt;span class="s"&gt;--data \&lt;/span&gt;
            &lt;span class="s"&gt;'{&lt;/span&gt;
              &lt;span class="s"&gt;"blocks": [&lt;/span&gt;
                &lt;span class="s"&gt;{&lt;/span&gt;
                  &lt;span class="s"&gt;"type": "header",&lt;/span&gt;
                  &lt;span class="s"&gt;"text": {&lt;/span&gt;
                    &lt;span class="s"&gt;"type": "plain_text",&lt;/span&gt;
                    &lt;span class="s"&gt;"text": "${{ inputs.title }}"&lt;/span&gt;
                  &lt;span class="s"&gt;}&lt;/span&gt;
                &lt;span class="s"&gt;}&lt;/span&gt;
              &lt;span class="s"&gt;]&lt;/span&gt;
            &lt;span class="s"&gt;}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;inputs&lt;/code&gt; allow you to pass data from your main workflow to this action through &lt;code&gt;with&lt;/code&gt;, the full example will show it. We encapsulated the &lt;code&gt;curl&lt;/code&gt; into its own action, allowing all applications to use it once we push the code to our repository.&lt;/p&gt;

&lt;p&gt;The example below shows how the main workflow will look like given that we have the above action under &lt;code&gt;github-username/my-action&lt;/code&gt;:&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;# Action name&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;CD&lt;/span&gt;

&lt;span class="c1"&gt;# Action triggers&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="s"&gt;main&lt;/span&gt;

 &lt;span class="c1"&gt;# Action jobs&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;deploy&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.3.4&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm run build:app&lt;/span&gt;
      &lt;span class="pi"&gt;-&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;npm run deploy&lt;/span&gt;
      &lt;span class="na"&gt;slack&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&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.3.4&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;Get latest commit info&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;echo "::set-output name=TITLE::$(git show -1 --format='%s' -s)"&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;latest_commit&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;github-username/my-action@main&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;webhook_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SLACK_RELEASE_BOT_WEBHOOK_URL }}&lt;/span&gt;
              &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.latest_commit.outputs.TITLE }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of performing the &lt;code&gt;curl&lt;/code&gt; ourselves, we just use the new action, and using &lt;code&gt;with&lt;/code&gt; we can pass the required variables.&lt;/p&gt;

&lt;p&gt;Now we have a centralized repository where we can standardize the release, make its usage easier, and, on top of that, document what is going on, which would be hard if you just copy a &lt;code&gt;curl&lt;/code&gt; from another workflow.&lt;/p&gt;

&lt;p&gt;## How about the next steps?&lt;/p&gt;

&lt;p&gt;There are other ways to create an action: docker and javascript. They are indeed more advanced and give you more flexibility, not something required by our example at the moment. In case we need to start adding conditions, fetch different, data and not so "step by step" approach, it makes sense to go for a Docker/Javascript action as it will become easier to manipulate data there.&lt;/p&gt;

&lt;p&gt;The idea here is to start simple and develop further only if needed. You can group a sequence of steps using a composite action and reuse them across all your applications. It is also important to follow a release process and avoid that applications crash in case something gets updated.&lt;/p&gt;




&lt;p&gt;If you would like to check the actual final code from our Slack release BOT Action, you can check it in this repository: &lt;a href="https://github.com/homeday-de/slack-release-bot-action"&gt;https://github.com/homeday-de/slack-release-bot-action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to use it if it helps you. You can also check this pull request where we replace a &lt;code&gt;curl&lt;/code&gt; step with the new action for every release of our component library: &lt;a href="https://github.com/homeday-de/homeday-blocks/pull/744"&gt;https://github.com/homeday-de/homeday-blocks/pull/744&lt;/a&gt;&lt;/p&gt;

</description>
      <category>slackbot</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Vue custom input</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Tue, 09 Mar 2021 19:44:56 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/vue-custom-input-bk8</link>
      <guid>https://dev.to/viniciuskneves/vue-custom-input-bk8</guid>
      <description>&lt;p&gt;Most of us have faced it: build a custom input component. There are multiple reasons behind it but in general, it has custom styles and we should be able to reuse it.&lt;/p&gt;

&lt;p&gt;Although it might sound simple it has some gotchas and from time to time we end up going through the documentation to check some details. It gets a bit more complicated if you're not that familiar with few Vue concepts.&lt;/p&gt;

&lt;p&gt;Last month, February 2021, it happened again. When possible I try to help people in a Vue Slack group and this question popped up once again. Not exactly this question but the user had issues building a custom input component. The problem was related to some concepts.&lt;/p&gt;

&lt;p&gt;To consolidate this knowledge for myself, and use it as some sort of documentation for others, I decided to wrap up the process of writing a custom input.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Table of contents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;v-model and &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The wrong custom input component&lt;/li&gt;
&lt;li&gt;
The happy custom input component

&lt;ul&gt;
&lt;li&gt;Adding validation (or operation on data change)&lt;/li&gt;
&lt;li&gt;Combining computed and &lt;code&gt;v-model&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Extra: the &lt;code&gt;model&lt;/code&gt; property&lt;/li&gt;
&lt;li&gt;So what?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  v-model and &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Once we start building forms with Vue we learn the directive &lt;code&gt;v-model&lt;/code&gt;. It does a lot of the hard work for us: it binds a value to an input. It means that whenever we change the input's value the variable will also be updated.&lt;/p&gt;

&lt;p&gt;The official docs do a great job explaining how it works: &lt;a href="https://vuejs.org/v2/guide/forms.html"&gt;https://vuejs.org/v2/guide/forms.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short we can have the following template and we're fine:&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="c"&gt;&amp;lt;!-- UsernameInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    Username
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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="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;UsernameInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Initial value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll have an input that has &lt;code&gt;Initial value&lt;/code&gt; as the initial value and the username data will be automatically updated once we change the input's value.&lt;/p&gt;

&lt;p&gt;The problem with the above component is that we can't reuse it. Imagine we've a page where we need the username and the e-mail, the above component won't handle the e-mail case as the data is inside the component itself, not somewhere else (like the parent component, for example). That is where custom input components shine and also one of its challenges: keep the &lt;code&gt;v-model&lt;/code&gt; behavior consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong custom input component
&lt;/h2&gt;

&lt;p&gt;Well, why am I showing this example? The answer is: this is the first approach most of us will try.&lt;/p&gt;

&lt;p&gt;Let's see how we are going to use our custom input component:&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="c"&gt;&amp;lt;!-- App.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;custom-input&lt;/span&gt; &lt;span class="na"&gt;:label=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;CustomInput&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;./components/CustomInput.ue&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="k"&gt;default&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;App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CustomInput&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;model&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="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom input expects a &lt;code&gt;label&lt;/code&gt; and a &lt;code&gt;v-model&lt;/code&gt; in this case and will look like the component below:&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="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    {{ label }}
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;:name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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="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;CustomInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, it expects the &lt;code&gt;label&lt;/code&gt; as property and computes the &lt;code&gt;name&lt;/code&gt; on top of that (it could also be a property). Second, it expects a &lt;code&gt;value&lt;/code&gt; property and binds it to the &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; through &lt;code&gt;v-model&lt;/code&gt;. The reason behind that can be found &lt;a href="https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components"&gt;in the docs&lt;/a&gt; but in short, when we use &lt;code&gt;v-model&lt;/code&gt; in a custom component it will get &lt;code&gt;value&lt;/code&gt; as a property which is the value from the &lt;code&gt;v-model&lt;/code&gt; variable used. In our example, it'll be the value from &lt;code&gt;model&lt;/code&gt; defined in &lt;code&gt;App.vue&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we try the code above, it'll work as expected, but why is it wrong? If we open the console we will see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It complains that we're mutating a property. The way Vue works is: the child component has props that come from the parent component and the child component emits changes to the parent component. Using &lt;code&gt;v-model&lt;/code&gt; with the &lt;code&gt;value&lt;/code&gt; prop that we got from the parent component violates it.&lt;/p&gt;

&lt;p&gt;Another way to see this issue is rewriting the &lt;code&gt;App.vue&lt;/code&gt; like 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="c"&gt;&amp;lt;!-- App.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;custom-input&lt;/span&gt; &lt;span class="na"&gt;:label=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt; &lt;span class="na"&gt;:value=&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The main difference is using &lt;code&gt;:value&lt;/code&gt; instead of &lt;code&gt;v-model&lt;/code&gt;. In this case, we're just passing &lt;code&gt;model&lt;/code&gt; to the &lt;code&gt;value&lt;/code&gt; property. The example still works and we get the same message in the console.&lt;/p&gt;

&lt;p&gt;The next step is to rework the example above and make sure it works as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The happy custom input component
&lt;/h2&gt;

&lt;p&gt;The happy custom input component doesn't mutate its prop but emits the changes to the parent component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components"&gt;The docs&lt;/a&gt; have this exact example but we'll go a bit further here. If we follow the docs, our &lt;code&gt;CustomInput&lt;/code&gt; should look like the one below:&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="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    {{ label }}
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;:name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;:value=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"$emit('input', $event.target.value)"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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="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;CustomInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is enough to make it work. We can even test it against both &lt;code&gt;App.vue&lt;/code&gt;, the one using &lt;code&gt;v-model&lt;/code&gt;, where everything works as expected, and the one using &lt;code&gt;:value&lt;/code&gt; only, where it doesn't work anymore as we stopped mutating the property.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding validation (or operation on data change)
&lt;/h3&gt;

&lt;p&gt;In case we need to do something when the data changes, for example checking if it is empty and show an error message, we need to extract the emit. We'll have the following changes to our component:&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="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
...
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;:name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;:value=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"onInput"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we add the empty check:&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="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
...
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ error }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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="na"&gt;error&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="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nx"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Value should not be empty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It kind of works, first it doesn't show any errors and if we type then delete it'll show the error message. The problem is that the error message never disappears. To fix that we need to add a watcher to the value property and clean the error message whenever it is updated.&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="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could achieve a similar result adding an &lt;code&gt;else&lt;/code&gt; inside &lt;code&gt;onInput&lt;/code&gt;. Using the watcher enables us to validate before the user updates the input value, if desirable.&lt;/p&gt;

&lt;p&gt;If we add more things we most probably will be expanding this component even more and things are spread all over the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block. To group things a bit we can try a different approach: use computed together with &lt;code&gt;v-model&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Combining computed and &lt;code&gt;v-model&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of listening to the &lt;code&gt;input&lt;/code&gt; event and then emitting it again, we can leverage the power of &lt;code&gt;v-model&lt;/code&gt; and &lt;code&gt;computed&lt;/code&gt;. It is the closest we can get to the wrong approach but still make it right 😅&lt;br&gt;
Let's rewrite our component like that:&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="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
...
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;:name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="nx"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can get rid of the &lt;code&gt;onInput&lt;/code&gt; method and also from the watcher as we can handle everything within &lt;code&gt;get/set&lt;/code&gt; functions from the computed property.&lt;/p&gt;

&lt;p&gt;One cool thing we can achieve with that is the usage of modifiers, like &lt;code&gt;.trim/number&lt;/code&gt; that would need to be manually written before.&lt;/p&gt;

&lt;p&gt;This is a good approach for simple input components. Things can get a bit more complex and this approach doesn't fulfill all the use cases, if that is the case we need to go for binding value and listening to events. A good example is if you want to support the &lt;code&gt;.lazy&lt;/code&gt; modifier in the parent component, you will need to manually listen to &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;change&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extra: the &lt;code&gt;model&lt;/code&gt; property
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://vuejs.org/v2/api/#model"&gt;&lt;code&gt;model&lt;/code&gt; property&lt;/a&gt; allows you to customize a bit the &lt;code&gt;v-model&lt;/code&gt; behavior. You can specify which property will be mapped, the default is &lt;code&gt;value&lt;/code&gt;, and which event will be emitted, the default is &lt;code&gt;input&lt;/code&gt; or &lt;code&gt;change&lt;/code&gt; when &lt;code&gt;.lazy&lt;/code&gt; is used.&lt;/p&gt;

&lt;p&gt;This is especially useful if you want to use the &lt;code&gt;value&lt;/code&gt; prop for something else, as it might make more sense for a specific context, or just want to make things more explicit and rename &lt;code&gt;value&lt;/code&gt; to &lt;code&gt;model&lt;/code&gt;, for example. In most cases, we might use it to customize checkboxes/radios when getting objects as input.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what?
&lt;/h2&gt;

&lt;p&gt;My take comes from how complex your custom input needs to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It was created to centralized the styles in one component and its API is pretty much on top of Vue's API: &lt;code&gt;computed&lt;/code&gt; + &lt;code&gt;v-model&lt;/code&gt;. It falls pretty much on our example, it has simple props and no complex validation.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    {{ label }}
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;:name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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="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;CustomInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Everything else (which means that you need to tweak a lot the previous setup to support what you need): listeners, watchers and whatelse you might need. It might have multiple states (think of async validation where a loading state might be useful) or you want to support &lt;code&gt;.lazy&lt;/code&gt; modifier from the parent component, are good examples to avoid the first approach.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- CustomInput.vue --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    {{ label }}
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;:name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;:value=&lt;/span&gt;&lt;span class="s"&gt;"value"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;input=&lt;/span&gt;&lt;span class="s"&gt;"onInput"&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;change=&lt;/span&gt;&lt;span class="s"&gt;"onChange"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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="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;CustomInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="cm"&gt;/* Can add validation here
  watch: {
    value: {
      handler(newValue, oldValue) {

      },
    },
  }, */&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Can add validation here&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Supports .lazy&lt;/span&gt;
      &lt;span class="c1"&gt;// Can add validation here&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Thanks &lt;a class="mentioned-user" href="https://dev.to/danilowoz"&gt;@danilowoz&lt;/a&gt; for reviewing it&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vue</category>
    </item>
    <item>
      <title>Multiple builds targeting different browsers</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Mon, 24 Aug 2020 13:07:41 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/multiple-builds-targeting-different-browsers-jcl</link>
      <guid>https://dev.to/viniciuskneves/multiple-builds-targeting-different-browsers-jcl</guid>
      <description>&lt;p&gt;Every quarter at &lt;a href="https://www.homeday.de"&gt;Homeday&lt;/a&gt; we have something called PEW. PEW stands for Product Engineering Week, which translates to a week where you cancel all your meetings and work on a topic you would like to explore. It can be done in groups or alone, it is up to you. Last PEWs I've done work regarding tests, assets compression and some Puppeteer as a service. This quarter I decided to go for build optimization and I would like to explore the topic in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  My idea
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;How about building different versions of our applications targeting different browsers so each browser downloads only the "fallbacks" it needs?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is in short what I thought. Now I'm going to explore how is our current development lifecycle and where we can try to add this idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development lifecycle
&lt;/h2&gt;

&lt;p&gt;At Homeday we build mainly SPAs using Vue. So by the end of our development lifecycle we create a bunch of assets that are uploaded to S3 (in our case) and work as an application.&lt;/p&gt;

&lt;p&gt;To "create a bunch of assets" we use Webpack which builds our code, creating one version of it at the end. This version is used by all our clients, which means that lots of different browsers will be using this very same version. Below you can visualize the current build process, from code to assets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GSouoJ3R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bjyj7o3hxxtr7dq0yglf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GSouoJ3R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bjyj7o3hxxtr7dq0yglf.png" alt="Current build process" width="880" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By "different browsers will be using this very same version" I mean that we have to be ready for some old browsers (some applications still need to support IE 11 which has a significant market share for us). So basically our version should support IE 11 and also the latest version on Chrome, for example. IE 11 doesn't have the same Javascript/CSS support as the latest Chrome, so at the end our code fallback to something that works on IE 11, adding polyfills and transpiling what is needed. This adds some extra Kb to our assets which the latest Chrome users don't need but they end up downloading.&lt;/p&gt;

&lt;p&gt;The theory is exactly that one. The thing I needed to check now if how feasible it would be to ship different code for both browsers or how many browsers we would like to split it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Targeting browsers when building
&lt;/h2&gt;

&lt;p&gt;When we build our application using Webpack there are different loaders that ensure our code becomes a single (or multiple) JS/CSS file at the end. Well known loaders like &lt;a href="https://github.com/babel/babel-loader"&gt;&lt;code&gt;babel-loader&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://webpack.js.org/loaders/postcss-loader/"&gt;&lt;code&gt;postcss-loader&lt;/code&gt;&lt;/a&gt; ensure that our code works cross browser. The open question is: How do they know which browsers they have to fallback to? They can have their own defaults but there should be, somehow, a way to specify which browsers it should take into consideration.&lt;/p&gt;

&lt;p&gt;There is one file called &lt;code&gt;.browserslistrc&lt;/code&gt; (or an entry in &lt;code&gt;package.json&lt;/code&gt;) that specify the browsers you expect your project to support. This file has a well defined structure and has its own project: &lt;a href="https://github.com/browserslist/browserslist"&gt;&lt;code&gt;browserslist&lt;/code&gt;&lt;/a&gt;. Loaders, like &lt;code&gt;babel-loader&lt;/code&gt; and &lt;code&gt;postcss-loader&lt;/code&gt;, use the browsers you specify in your &lt;code&gt;.browserslistrc&lt;/code&gt; file to know which browsers they have to fallback to.&lt;/p&gt;

&lt;p&gt;You can define not just one browser but a range of browsers with &lt;code&gt;browserslist&lt;/code&gt;, I recommend you to check the project if you're not aware how to define those queries.&lt;/p&gt;

&lt;p&gt;Now that we can specify the browsers we want to support, we need to check the browsers' distribution among our projects and check the savings we could have when targeting them in the build process. The browsers' distribution comes from Google Analytics in our case. I did this process for 3 of our projects and summarized it below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Project 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current build (which supports IE 11 but doesn't need it): 273Kb&lt;/li&gt;
&lt;li&gt;Chrome 84: 241Kb (12% - 32Kb)&lt;/li&gt;
&lt;li&gt;Safari 13: 250Kb (9% - 23Kb)&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;Project 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current build (which suuports IE 11 and it is necessary): 302Kb&lt;/li&gt;
&lt;li&gt;Chrome 84: 269Kb (11% - 33Kb)&lt;/li&gt;
&lt;li&gt;Safari 13: 277Kb (8% - 25Kb)&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;Project 3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current build (which supports IE 11 and it is necessary): 544Kb&lt;/li&gt;
&lt;li&gt;Chrome 83+: 504Kb (8% - 40Kb)&lt;/li&gt;
&lt;li&gt;Safari 13: 516Kb (5% - 28Kb)&lt;/li&gt;
&lt;/ul&gt;


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

&lt;p&gt;&lt;em&gt;All values are GZIP and counts for all JS + CSS files generated in the build&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All in all modern browsers can save between ~20Kb - ~40Kb which is definitely a good number (it is not as good as the results I've got from Brotli compression during another PEW work but it is definitely something that we can work on).&lt;/p&gt;

&lt;p&gt;Now that the idea is valid, it is time to implement it. The first step is to do multiple builds of our projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple builds
&lt;/h2&gt;

&lt;p&gt;Using browserslist we can specify &lt;a href="https://github.com/browserslist/browserslist#configuring-for-different-environments"&gt;different environments&lt;/a&gt; which allows us to set an environment variable (&lt;code&gt;BROWSERSLIST_ENV&lt;/code&gt;) to select which environment we want to build to.&lt;/p&gt;

&lt;p&gt;Now you can read the &lt;code&gt;.browserslistrc&lt;/code&gt; file and select which environments are set and build based on them. We created a small script that does this job. You can setup it differently, what matters at the end is the possibility of running one command and building all the different versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browserslist/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browserslistConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.browserslistrc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browserslistConfigKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browserslistConfig&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&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;_&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;defaults&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Browserslist default is removed and built separately&lt;/span&gt;

&lt;span class="nx"&gt;browserslistConfigKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Here we build the app like: BROWSERSLIST_ENV=${key} npm run build:production&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;I've removed parts of the code that are not necessary for the example.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So right now what happens is the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have a &lt;code&gt;.browserslistrc&lt;/code&gt; file with environments set
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;since 2019

[chrome]
chrome 84

[safari]
safari 13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We build for each environment

&lt;ol&gt;
&lt;li&gt;Load first envionment, which is &lt;code&gt;chrome&lt;/code&gt; in this case.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;BROWSERSLIST_ENV=chrome npm run build:production&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The entry point will be in &lt;code&gt;/dist/chrome&lt;/code&gt;, so we will have &lt;code&gt;/dist/chrome/index.html&lt;/code&gt; and &lt;code&gt;/dist/js/...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Load second envionment, which is &lt;code&gt;safari&lt;/code&gt; in this case.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;BROWSERSLIST_ENV=safari npm run build:production&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The entry point will be in &lt;code&gt;/dist/safari&lt;/code&gt;, so we will have &lt;code&gt;/dist/safari/index.html&lt;/code&gt; and &lt;code&gt;/dist/js/...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build default case&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;npm run build:production&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The entry point will be in &lt;code&gt;/dist&lt;/code&gt;, so we will have &lt;code&gt;/dist/index.html&lt;/code&gt; and &lt;code&gt;/dis/js/...&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we can note from here is that we still have the default &lt;code&gt;/dist/index.html&lt;/code&gt; working as expected and all the assets are in the shared folders, &lt;code&gt;/dist/js&lt;/code&gt; for example. The image below summarizes this process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zN6CONQN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5vjjlm7k0w7zd41gmcdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zN6CONQN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5vjjlm7k0w7zd41gmcdj.png" alt="Multiple builds process" width="880" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's check where we are going. We have multiple &lt;code&gt;index.html&lt;/code&gt; files now. Each &lt;code&gt;index.html&lt;/code&gt; points to a different entry point, a &lt;code&gt;.js&lt;/code&gt; file in this case. This &lt;code&gt;.js&lt;/code&gt; file is located in &lt;code&gt;/dist/js&lt;/code&gt;. So what we need to do now is to route the browser to the specific &lt;code&gt;index.html&lt;/code&gt; that uses the built version of our app for that browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Routing multiple builds
&lt;/h2&gt;

&lt;p&gt;Once we're done with multiple builds of our application we can simply deploy it. Deploy means copying the files under &lt;code&gt;/dist&lt;/code&gt; to somewhere, which is S3 in our case. What happens now is that our application works exactly as before. The reason behind it is that our &lt;code&gt;default build&lt;/code&gt; creates &lt;code&gt;/dist/index.html&lt;/code&gt; which is exactly how we were building our project.&lt;/p&gt;

&lt;p&gt;What we need to do now is to route some of the requests to the new &lt;code&gt;index.html&lt;/code&gt; files, under &lt;code&gt;/chrome&lt;/code&gt; and &lt;code&gt;/safari&lt;/code&gt; sub-directories. We need to route only &lt;code&gt;index.html&lt;/code&gt; as all the assets are living in the same sub-directories (&lt;code&gt;/js&lt;/code&gt; and &lt;code&gt;/css&lt;/code&gt;) as before.&lt;/p&gt;

&lt;p&gt;At Homeday we have CloudFront in front of S3 which means we can levarage the powers of &lt;a href="mailto:Lambda@Edge"&gt;Lambda@Edge&lt;/a&gt;. Lambda@Edge allows you to run a Lambda function (if you're not familiar, please check the &lt;a href="https://aws.amazon.com/lambda/"&gt;official docs&lt;/a&gt; within CloudFront lifecycle events. You can also check the &lt;a href="https://aws.amazon.com/lambda/edge/"&gt;Lambda@Edge official docs&lt;/a&gt; if you want to go deeper in the topic.&lt;/p&gt;

&lt;p&gt;We can place a Lambda function between CloudFront and S3, which allows us to route the request to S3 based on the &lt;code&gt;User-Agent&lt;/code&gt; that we get from the request. We can compare the &lt;code&gt;User-Agent&lt;/code&gt; with our queries in the browserslist definition and decide which route to take or just go to the default one (which would be the case without this Lambda function). This process should happen only for &lt;code&gt;index.html&lt;/code&gt; and &lt;code&gt;service-workers.js&lt;/code&gt; as we have a PWA here. The Lambda function can look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;matchesUA&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browserslist-useragent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browserslist/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;INDEX_HTML_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;index&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;html/&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;SERVICE_WORKER_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;service-worker&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js/&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;BROWSERSLIST_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.browserslistrc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BROWSERSLIST_KEYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;BROWSERSLIST_CONFIG&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&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;_&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;defaults&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Records&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;cf&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;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;INDEX_HTML_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;SERVICE_WORKER_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// You can do it in the same Regex or leave it explicit as we do&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getUserAgentFromHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="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;uri&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;uri&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;userAgent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userAgent&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;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BROWSERSLIST_KEYS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;BROWSERSLIST_CONFIG&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;browsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;allowHigherVersions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;matchesUA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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="s2"&gt;`Redirect to &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; version`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="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;Serving default version&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;request&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;Once the user downloads the "right" &lt;code&gt;index.html&lt;/code&gt;, it will fetch the required assets and serve the right version of the application for that user. There are 3 images below that represent the request scenarios. Consider that none of the files are cached in CloudFront/Browser.&lt;/p&gt;

&lt;p&gt;Requesting &lt;code&gt;index.html&lt;/code&gt; from a random browser that is not Chrome/Safari, which means we fallback to default (or what we had before). Lambda function doesn't do any routing job now and just forwards the request.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QLa8Ydz4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/t5kpms0uajzy6l3swgss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QLa8Ydz4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/t5kpms0uajzy6l3swgss.png" alt="Request from default User-Agent" width="880" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Requesting &lt;code&gt;index.html&lt;/code&gt; from a Chrome browser, which means we should route to &lt;code&gt;/chrome/index.html&lt;/code&gt;. Lambda function detects the &lt;code&gt;User-Agent&lt;/code&gt; and routes the request to the right file, in this case &lt;code&gt;/chrome/index.html&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LoYLEMPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ha5sv5szqciehqw3fiib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LoYLEMPp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ha5sv5szqciehqw3fiib.png" alt="Request from Chrome User-Agent" width="880" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Requesting &lt;code&gt;app.1.js&lt;/code&gt; from a Chrome browser. As it is not &lt;code&gt;index.html&lt;/code&gt; we shouldn't do anything. Lambda function doesn't do any routing job now and just forwards the request.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YqzVsv-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jqzzbdsqpq6wu0md1eb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YqzVsv-e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jqzzbdsqpq6wu0md1eb5.png" alt="Asset request from Chrome User-Agent" width="880" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to consider
&lt;/h2&gt;

&lt;p&gt;All in all the described approach works as expected. Nevertheless there are other things I would recommend doing as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not build for specific browsers. Build for a range of browsers. If you build for Chrome 83 and Chrome 84, for example, the changes of having the same output are quite high. Play with Browserslist queries and find the one that fits you best, also take a look into your analytics to understand the best approach to take.&lt;/li&gt;
&lt;li&gt;Your build time is going to increase. You can build in parallel as well but at the end it is going to increase. So leverage the amount of builds you would like to have.&lt;/li&gt;
&lt;li&gt;If you use CDN, as we use CloudFront, forwarding a header will imply on "loosening" your caching strategy, so have it in mind and do not forward all the headers. In this case we need just &lt;code&gt;User-Agent&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Automate and make use of IaC (Infrastructure as Code). As we've everything in AWS, I end up using CloudFormation. Actually I used AWS SAM, as it makes it easier to define Lambda functions, but at the end I still need to use CloudFormation syntax for CloudFront distribution for example.

&lt;ul&gt;
&lt;li&gt;This step can be done completely in a next iteration but I definitely recommend you to check it out. Imagine you update your &lt;code&gt;.browserslistrc&lt;/code&gt; file. You need to deploy your Lambda again. Publish it. Update CloudFront distribution to use it. And whatever comes after. If everything is automated, at the end you run a command that does all those steps for you.&lt;/li&gt;
&lt;li&gt;If you use CloudFront as well and you're outside &lt;code&gt;us-east-1&lt;/code&gt; you will have few issues with Lambda@Edge as this function should be in &lt;code&gt;us-east-1&lt;/code&gt; and not in any other region to work.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Another way of achieving something similar is using &lt;a href="https://github.com/TrigenSoftware/bdsl-webpack-plugin"&gt;bdsl-webpack-plugin&lt;/a&gt;. This approach has some drawbacks and it becomes cumbersome when using Service Workers. Nevertheless it is a way easier to be implemented.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thank you for coming this far =]
&lt;/h2&gt;

&lt;p&gt;I hope you enjoyed this article as much as I enjoyed exploring this topic. The benefits of such approach are quite clear but the implementation is a bit overwhelming.&lt;/p&gt;

&lt;p&gt;We, Homeday, are currently not using it in production and I'm keen to give it a try and collect some metrics. I love to explore this kind of topic and work beyond the code, exploring architecture improvements and so on. I hope in the next PEWs I can explore similar topics and share our learnings as well!&lt;/p&gt;

</description>
      <category>webpack</category>
      <category>aws</category>
      <category>vue</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Webpack concepts: Loaders and Plugins</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Wed, 29 Jul 2020 17:56:20 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/webpack-concepts-loaders-and-plugins-5ed0</link>
      <guid>https://dev.to/viniciuskneves/webpack-concepts-loaders-and-plugins-5ed0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first time I write a series of articles. I decided to structure it like that as I don't have the full picture of what I'm going to be writing, I've just the first part of it written, and I also want to avoid a 10min reading article at the end.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I've been doing Vue, professionally, for a while now. As usual, whenever I create/setup a new project, I use Vue CLI which abstracts a lot of the heavy weight of creating a new project, which is great, especially if you've ever tried to setup a full Webpack config by yourself. The same applies if you use other CLIs to generate your project.&lt;br&gt;
Nonetheless, I want to walk through the process of setuping a Webpack config by myself and highlight some benefits and drawbacks of using Vue CLI on the way. I will try to mimic as much as possible the behavior of Vue CLI but avoid the usage of its plugins for example.&lt;/p&gt;

&lt;p&gt;Why I want to do that? Well, as sort of documentation/study for myself (I can't remember the last time I went through each Webpack loader and customized it, which is great, don't get me wrong) and also, maybe, to help people to understand the full process and challenges that comes with such setup (so you might get some feeling about why CLIs do a great job for us).&lt;/p&gt;

&lt;p&gt;The content will be split between Webpack, Vue and enhancements. First I will go through Webpack and its concepts, focusing more on examples than in the theory (&lt;a href="https://webpack.js.org/concepts/" rel="noopener noreferrer"&gt;Webpack docs&lt;/a&gt; are great if we need to deep dive into some concepts). After that I want to be able to have a bare minimum Webpack config to build a Vue project. At the end I will explore some "enhancements", that is how I decided to call it, to my Webpack config. By enhancements I mean everything that "enhances" our developer experience or add some flavor to our build (file compressing, for example, or splitting...).&lt;/p&gt;




&lt;h2&gt;
  
  
  Webpack
&lt;/h2&gt;

&lt;p&gt;Most probably you've heard about Webpack before, if not, check their &lt;a href="https://webpack.js.org" rel="noopener noreferrer"&gt;official website&lt;/a&gt; which has tons of documentation about it. I think this image, from the offical website, summarizes quite well what Webpack does for us:&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%2F7ninxhbqd0o7d8scf96c.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%2F7ninxhbqd0o7d8scf96c.png" alt="Webpack bundle process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you start reading the documentation you will find out that the first two dependencies you've to install in order to use Webpack are &lt;code&gt;webpack&lt;/code&gt; and &lt;code&gt;webpack-cli&lt;/code&gt;. From here we can start customizing our Webpack config which tells Webpack how it should process our files.&lt;/p&gt;

&lt;p&gt;The Webpack config starts with the &lt;code&gt;entry&lt;/code&gt; property, which specifies the entry file to be processed with Webpack. Let's create the following &lt;code&gt;webpack.config.js&lt;/code&gt; file, which tells Webpack that the entry point is &lt;code&gt;./src/main.js&lt;/code&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;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/main.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If we add any Javascript code to &lt;code&gt;main.js&lt;/code&gt; it will get processed by Webpack and the output will be placed inside &lt;code&gt;dist/&lt;/code&gt; folder (by default). To run Webpack we just need to &lt;code&gt;npx webpack --config webpack.config.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It works (if we have a &lt;code&gt;main.js&lt;/code&gt; file created) but it complains (warns) that the &lt;code&gt;mode&lt;/code&gt; property is not set. From Webpack docs, the &lt;code&gt;mode&lt;/code&gt; property:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Providing the &lt;code&gt;mode&lt;/code&gt; configuration option tells webpack to use its built-in optimizations accordingly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;mode&lt;/code&gt; property can be &lt;code&gt;production&lt;/code&gt;, &lt;code&gt;development&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt;. To set the &lt;code&gt;mode&lt;/code&gt; property we can just pass &lt;code&gt;--mode=MODE_WE_WANT&lt;/code&gt; to our &lt;code&gt;npx webpack&lt;/code&gt; call. To make things more structured we can add two commands to our NPM scripts, one to build for production and another one for development:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build:production"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --config webpack.config.js --mode=production"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build:development"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --config webpack.config.js --mode=development"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;

&lt;p&gt;Ok, now we can add Javascript code to &lt;code&gt;main.js&lt;/code&gt; and build it. Not that useful, but it is the basic setup and we made it work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Loaders
&lt;/h3&gt;

&lt;p&gt;Loaders are the magic behind Webpack. They allow us to process different file types in different ways and the outcome will be a single (or multiple) bundled file. From the docs, loaders are defined as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;webpack enables use of loaders to preprocess files. This allows you to bundle any static resource way beyond JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once we are developing for the web, one main concern that we usually have is: it should work cross browser. Which means, it should fallback features that are not present in old browsers. A project that does this kind of job is &lt;a href="https://babel.dev" rel="noopener noreferrer"&gt;Babel&lt;/a&gt; and we can, via loader, use it with Webpack. We can tell Webpack to process Javascript files with Babel, which will do the job of compiling our code.&lt;/p&gt;

&lt;p&gt;To use Babel with Webpack we need to setup its &lt;a href="https://github.com/babel/babel-loader" rel="noopener noreferrer"&gt;loader&lt;/a&gt;. From the Babel loader docs we endup with the following Webpack config:&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;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/main.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/node_modules/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="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;Let's take a step back first. If the &lt;code&gt;main.js&lt;/code&gt; file contained something that doesn't need to be compiled, it won't make a huge difference here. For example, if it contains &lt;code&gt;console.log('Hello World!')&lt;/code&gt;, the output will be the same with or without the loader. It gets more interesting once we started adding features that are not supported in all browsers.&lt;/p&gt;

&lt;p&gt;First lets setup Babel configuration file, also known as &lt;code&gt;babelrc&lt;/code&gt;, which allows us to use special syntax if we want or use the well known &lt;a href="https://babeljs.io/docs/en/babel-preset-env" rel="noopener noreferrer"&gt;&lt;code&gt;@babel/preset-env&lt;/code&gt;&lt;/a&gt; which:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The setup needed is the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"presets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"@babel/preset-env"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;

&lt;p&gt;Now our project is ready to compile "new" Javascript syntax. Let's take the following code as example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;hey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hey!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;hey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;It uses a new syntax that won't work in old browsers. If we build our file without the Babel config, the output will be something like the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async function hey() {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  await Promise.resolve();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  return 'Hey!';&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;hey();&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;//# sourceURL=webpack:///./src/main.js?&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;If we setup the Babel config file, the output will be like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;throw&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, err); } _next(undefined); }); }; }&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;function hey() {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  return _hey.apply(this, arguments);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;function _hey() {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  _hey = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    return regeneratorRuntime.wrap(function _callee$(_context) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      while (1) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        switch (_context.prev = _context.next) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;          case 0:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;            _context.next = 2;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;            return Promise.resolve();&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;          case 2:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;            return _context.abrupt(&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;return&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, 'Hey!');&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;          case 3:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;          case &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;            return _context.stop();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    }, _callee);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }));&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  return _hey.apply(this, arguments);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;hey();&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;//# sourceURL=webpack:///./src/main.js?&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;Ok, let's break it into smaller pieces first. The first code is basically a copy and paste from the code we wrote. The second one has some magic so we don't see the usage of &lt;code&gt;async/await&lt;/code&gt; for example. &lt;code&gt;@babel/preset-env&lt;/code&gt; is doing its job here, we didn't need to tell it that we would be using a specific feature. Now the question that we might be asking is: what happens if I want to support only browsers that support &lt;code&gt;async/await&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;The answer is: &lt;a href="https://github.com/browserslist/browserslist" rel="noopener noreferrer"&gt;Browserslist&lt;/a&gt;. You might have seen a file called &lt;code&gt;.browserslistrc&lt;/code&gt; in the root of a project or inside &lt;code&gt;package.json&lt;/code&gt;. This file is responsible for specifying which browsers you want to support and is used by multiple tools to bundle your code. Babel is one of them. If you run &lt;code&gt;npx browserslist&lt;/code&gt; in your project you will see a list of browsers it should support. By default, at the time of this writing, the list is the following (which is the &lt;code&gt;defaults&lt;/code&gt; setting from &lt;code&gt;browserslist&lt;/code&gt;):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

and_chr 81
and_ff 68
and_qq 10.4
and_uc 12.12
android 81
baidu 7.12
chrome 83
chrome 81
chrome 80
edge 83
edge 81
edge 18
firefox 78
firefox 77
firefox 76
firefox 68
ie 11
ios_saf 13.4-13.5
ios_saf 13.3
ios_saf 12.2-12.4
kaios 2.5
op_mini all
op_mob 46
opera 69
opera 68
safari 13.1
safari 13
samsung 12.0
samsung 11.1-11.2


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

&lt;/div&gt;

&lt;p&gt;What we can do here is to setup our own &lt;code&gt;.browserslistrc&lt;/code&gt; file with modern browsers, for example &lt;code&gt;last 2 Chrome versions&lt;/code&gt;, and run our build again. The output will be the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async function hey() {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  await Promise.resolve();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  return 'Hey!';&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;hey();&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;//# sourceURL=webpack:///./src/main.js?&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;As the last 2 versions of Chrome support &lt;code&gt;async/await&lt;/code&gt;, Babel won't transpile it so we will endup with "modern" code. As a side note, if you're not 100% sure of which browsers you need to support and so on, leave the default values and don't mess with your bundle.&lt;/p&gt;

&lt;p&gt;Things are getting more interesting now. We are already using the Webpack magic to process our files. Now we are going to explore another Webpack concept, called plugins which will add more power to our build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plugins
&lt;/h3&gt;

&lt;p&gt;Plugins do what loaders can't do. Ok, not that easy to understand but they hook into Webpack's lifecycle and do something. Webpack itself has a broad list of &lt;a href="https://webpack.js.org/plugins/" rel="noopener noreferrer"&gt;plugins&lt;/a&gt; that you can choose and each of them add a specific "power" to our build.&lt;/p&gt;

&lt;p&gt;Let's take the example from Vue CLI, the environment variables. You can define some environment variables, they should begin with &lt;code&gt;VUE_APP&lt;/code&gt;, and they will be available inside your application.&lt;/p&gt;

&lt;p&gt;If we want to do something similar, let's say we want to define &lt;code&gt;BASE_URL&lt;/code&gt; as an environment variable, we need to use a plugin for that. Webpack provides a plugin called &lt;a href="https://webpack.js.org/plugins/define-plugin/" rel="noopener noreferrer"&gt;DefinePlugin&lt;/a&gt; which &lt;code&gt;allows you to create global constants which can be configured at compile time.&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Webpack config will look like:&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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DefinePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process.env&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;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now we can use &lt;code&gt;process.env.BASE_URL&lt;/code&gt; inside our code and it will be replaced by &lt;code&gt;'localhost'&lt;/code&gt;. Vue CLI does an extra job and allows you to define &lt;code&gt;.env&lt;/code&gt; files which is out of scope for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final config
&lt;/h3&gt;

&lt;p&gt;Our final config, after all the setup above, will look like the following:&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;webpack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;webpack&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&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;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/main.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/node_modules/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DefinePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process.env&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;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="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;So far we will process the file &lt;code&gt;src/main.js&lt;/code&gt;, it will use the DefinePlugin to replace &lt;code&gt;process.env.BASE_URL&lt;/code&gt; by &lt;code&gt;'localhost'&lt;/code&gt; and it will be processed by Babel which will take into account our &lt;code&gt;.browserslistrc&lt;/code&gt; definition to add (or not) missing features.&lt;/p&gt;

&lt;p&gt;That is the basic of Webpack setup. You can imagine the heavy lift CLIs do and I hope we can understand a bit better how things work under the hood.&lt;/p&gt;

</description>
      <category>webpack</category>
      <category>vue</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Go async in Vue 3 with Suspense</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Mon, 22 Jun 2020 07:54:48 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/go-async-in-vue-3-with-suspense-4860</link>
      <guid>https://dev.to/viniciuskneves/go-async-in-vue-3-with-suspense-4860</guid>
      <description>&lt;p&gt;Vue 3 is coming with a some exciting new features. Composition API is the hottest one at the moment but there are others that excite me as much as it.&lt;/p&gt;

&lt;p&gt;One of those new features is called &lt;code&gt;Suspense&lt;/code&gt; and it really excites me about the benefits it brings. You might have heard about it already but I will try to show some examples of the usage of &lt;code&gt;Suspense&lt;/code&gt; and where it can be beneficial.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Suspense is a state of mental uncertainty, anxiety, of being undecided, or of being doubtful. In a dramatic work, suspense is the anticipation of the outcome of a plot or of the solution to an uncertainty, puzzle, or mystery, particularly as it affects a character for whom one has sympathy. - &lt;a href="https://en.wikipedia.org/wiki/Suspense"&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Back to Vue, &lt;code&gt;Suspense&lt;/code&gt; is a component, that you don't need to import or do any kind of setup, with two &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; that allows you to render a &lt;code&gt;#fallback&lt;/code&gt; while the main component you want to load is not ready.&lt;/p&gt;

&lt;p&gt;Ok, it seems vague. I will try to give you an example of how it is used. I also recommend you to take a look into its test cases, especially the &lt;a href="https://github.com/vuejs/vue-next/blob/8b85aaeea9b2ed343e2ae19958abbd9e5d223a77/packages/runtime-core/__tests__/components/Suspense.spec.ts#L45-L69"&gt;first one&lt;/a&gt; to get familiar with it.&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;Suspense&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#default&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Here the component I want to render --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#fallback&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Here a fallback component to be shown while my component is not ready --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Suspense&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the basic blueprint of it and it tackles a really common use case: the &lt;code&gt;v-if&lt;/code&gt; loading condition.&lt;/p&gt;

&lt;p&gt;I consider it the first benefit of &lt;code&gt;Suspense&lt;/code&gt;, as now we've some standard way of dealing with this scenario. Before &lt;code&gt;Suspense&lt;/code&gt; each developer could choose the way they want to implement it, they still can, and it was kind of a nightmare in situations where multiple components were loaded, so you would have &lt;code&gt;loadingHeader&lt;/code&gt;, &lt;code&gt;loadingFooter&lt;/code&gt;, &lt;code&gt;loadingMain&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;At the beginning I wrote "while the main component you want to load is not ready", what it means is that the main component has some kind of async work, which plays nicely with an &lt;code&gt;async setup()&lt;/code&gt; from the new Composition API.&lt;/p&gt;

&lt;p&gt;Let's say we have the following component with some async work to be done in &lt;code&gt;setup&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;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;I've some async work to do before I can render&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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="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;MyAsyncComponent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;someAsyncWork&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we want to use this component somewhere but we want to have a proper loading while it is not ready.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Suspense&lt;/code&gt; makes it more intuitive how it works and it really helps readability, check it:&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;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Suspense&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#default&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;MyAsyncComponent&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#fallback&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Loading... Please wait.&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Suspense&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;MyAsyncComponent&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;@/components/MyAsyncComponent.vue&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="k"&gt;default&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;App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyAsyncComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another cool thing about it is that you can have multiple &lt;code&gt;Suspense&lt;/code&gt; components defined and have different fallbacks for each of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I handle errors?
&lt;/h2&gt;

&lt;p&gt;Imagine the following: the call to &lt;code&gt;someAsyncWork&lt;/code&gt; threw an exception. How do we handle it with &lt;code&gt;Suspense&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;errorCapture&lt;/code&gt; hook to listen to errors and conditionally render our &lt;code&gt;Suspense&lt;/code&gt;. The component will look like the following:&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;template&amp;gt;&lt;/span&gt;
  // Here we conditionally render based on error
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;I failed to load&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Suspense&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#default&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;MyAsyncComponent&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#fallback&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Loading... Please wait.&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Suspense&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onErrorCaptured&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;MyAsyncComponent&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;@/components/MyAsyncComponent.vue&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="k"&gt;default&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;App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyAsyncComponent&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&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="nx"&gt;onErrorCaptured&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be honest it is quite a boilerplate if you're doing it in multiple places and can be a bit cumbersome if you've multiple &lt;code&gt;Suspenses&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I do encourage you to wrap this logic, and even improve it to your use case, in a new component. The following example shows a simple wrapper on top of it:&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;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Suspense&lt;/span&gt; &lt;span class="na"&gt;v-else&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#default&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#fallback&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;slot&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"fallback"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/slot&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Suspense&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;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;ref&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onErrorCaptured&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;SuspenseWithError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ref&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="nx"&gt;onErrorCaptured&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So you can use it like 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;template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;SuspenseWithError&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#default&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;MyAsyncComponent&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#fallback&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Loading... Please wait.&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#error&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;I failed to load&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/SuspenseWithError&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;MyAsyncComponent&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;@/components/MyAsyncComponent.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;SuspenseWithError&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;@/components/SuspenseWithError.vue&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="k"&gt;default&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;App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MyAsyncComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SuspenseWithError&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bear in mind that it is a simple and compact implementation that has not been tested in a real application. It also does not distinguished errors which might not be the ideal scenario for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suspense with Vue Router
&lt;/h2&gt;

&lt;p&gt;The main goal of this Dose is to show how to use the &lt;code&gt;Suspense&lt;/code&gt; with Vue Router. All the other examples above were made to introduce &lt;code&gt;Suspense&lt;/code&gt; and its power.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Suspense&lt;/code&gt; plays nicely with Vue Router. What I mean is that you can &lt;code&gt;Suspense&lt;/code&gt; your &lt;code&gt;&amp;lt;router-view&amp;gt;&lt;/code&gt; and in case the view has an async setup, you can show a fallback.&lt;/p&gt;

&lt;p&gt;To be more specific: You can create your loading component that will be shown whenever your view is not ready due some async work that has to be performed.&lt;/p&gt;

&lt;p&gt;You can achieve the same behavior with the navigation guards but for most of the cases, which do not involve a complex setup, the combination &lt;code&gt;&amp;lt;router-view&amp;gt;&lt;/code&gt;, &lt;code&gt;Suspense&lt;/code&gt; and async setup do a nice job!&lt;/p&gt;

&lt;p&gt;The example below shows how it can be implemented:&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;Suspense&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#default&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;router-view&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;template&lt;/span&gt; &lt;span class="na"&gt;#fallback&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;span&amp;gt;&lt;/span&gt;I'm a loading screen, I'm waiting the view to be ready!&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/template&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Suspense&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  All in all
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Suspense&lt;/code&gt; can be used to show a fallback element when an async work is needed in the main component&lt;/li&gt;
&lt;li&gt;One component can have multiple suspended components inside&lt;/li&gt;
&lt;li&gt;Error handling can be done with &lt;code&gt;onErrorCaptured&lt;/code&gt; hook&lt;/li&gt;
&lt;li&gt;A wrapper can be created to extract the error logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Suspense&lt;/code&gt; plays nicely with Vue Router once we want to show a loading screen&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final result is shown below and you can also check the sample code here: &lt;a href="https://github.com/viniciuskneves/vue-3-suspense"&gt;vue-3-suspense&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3oFYtHEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.ibb.co/8dNYkC5/super-high-resolution.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3oFYtHEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.ibb.co/8dNYkC5/super-high-resolution.gif" alt="Final example" width="880" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>vue</category>
      <category>vue3</category>
      <category>suspense</category>
    </item>
    <item>
      <title>Creating a Twitter BOT for Berlin English speakers</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Wed, 01 Apr 2020 08:39:53 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/creating-a-twitter-bot-for-berlin-english-speakers-33p7</link>
      <guid>https://dev.to/viniciuskneves/creating-a-twitter-bot-for-berlin-english-speakers-33p7</guid>
      <description>&lt;h1&gt;
  
  
  Creating a Twitter BOT for Berlin English speakers
&lt;/h1&gt;

&lt;p&gt;I'm going to walk you through the process of creating &lt;a href="https://twitter.com/Berlinglish"&gt;@Berlinglish&lt;/a&gt;, a Twitter BOT that tweets Berlin's news in English for non-German speakers.&lt;br&gt;
The &lt;a href="https://github.com/viniciuskneves/berlinglish"&gt;project&lt;/a&gt; was developed using Javascript. It is an AWS Lambda function that has an AWS CloudWatch scheduler as trigger. The function crawls Berlin's latest news and tweets it =]&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--DhnsLjud--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1245252515085594626/r0cuOGIb_normal.jpg" alt="Berlin in English profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Berlin in English
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @berlinglish
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Hello Berlin! 🇩🇪
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      19:45 PM - 31 Mar 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1245074868107960321" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1245074868107960321" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1245074868107960321" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I'm working from home since mid-March due the Corona outbreak. On the first days I had been constantly reading the news about it but there is a problem: I live in Berlin and I don't speak proper German.&lt;br&gt;
Berlin has its official &lt;a href="https://www.berlin.de/en/news/"&gt;English News channel&lt;/a&gt; which I think is super cool. It also has its official Twitter account &lt;a href="https://twitter.com/Berlin_de_News"&gt;@Berlin_de_News&lt;/a&gt; which tweets their news in German.&lt;br&gt;
The issue here is that they don't offer an English option. The Twitter account tweets only the German's news so if you want to have the "latest" English news you would have to open their website.&lt;br&gt;
That was my main motivation to create &lt;a href="https://twitter.com/Berlinglish"&gt;@Berlinglish&lt;/a&gt;, a bot that would tweet Berlin's News in English. The idea is that you can get notified every time that there is an update.&lt;/p&gt;



&lt;p&gt;Enough of introduction and motivation. From now on I'm going to dive into how it was implemented and I would love to have your feedback. I hope the project evolves over time, I can see a lot of room for improvements, from tech to new ideas!&lt;/p&gt;

&lt;p&gt;The project consists in 2 basic structures: Crawler and Twitter API =]&lt;br&gt;
I'm going to also talk about the deployment, using AWS SAM in this case, and at the end I invite you to contribute (not just tech-wise) and share it =]&lt;/p&gt;
&lt;h2&gt;
  
  
  Crawler
&lt;/h2&gt;

&lt;p&gt;First let me mention which webpage I'm crawling: &lt;a href="https://www.berlin.de/en/news/"&gt;https://www.berlin.de/en/news/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea is to go and grab the URL and title of every article in this page and tweet it. Thankfully this page is statically generated so I don't need to worry about any async requests made to extract the data that I need. This means that I need to download the source of the page and then parse it somehow.&lt;/p&gt;
&lt;h3&gt;
  
  
  Downloading page source
&lt;/h3&gt;

&lt;p&gt;There are a lot of different ways of doing it. You can even do it from your terminal if you want: &lt;code&gt;curl https://www.berlin.de/en/news/&lt;/code&gt;.&lt;br&gt;
I chose &lt;a href="https://github.com/axios/axios"&gt;axios&lt;/a&gt; as I do use it almost everyday at work. You don't need a library to do it and axios is indeed and overkill here.&lt;/p&gt;

&lt;p&gt;Nevertheless, the code with axios looks like the following:&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;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.berlin.de&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NEWS_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/en/news/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchArticles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;NEWS_PATH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code is quite straightforward. I'm using &lt;code&gt;BASE_URL&lt;/code&gt; and &lt;code&gt;NEWS_PATH&lt;/code&gt; because I will need them later. The HTML that we want is under &lt;code&gt;.data&lt;/code&gt; property from axios response.&lt;/p&gt;

&lt;p&gt;That is all we need to do to grab the data that we need, now we need to parse it!&lt;/p&gt;

&lt;h3&gt;
  
  
  Parsing page source
&lt;/h3&gt;

&lt;p&gt;The parsing step should be simple. Given an HTML document as input I want to extract some structured information out of it. My first idea is: take the article title and the article link. So every tweet will contain the title and the link to the original article. It is similar to what &lt;a href="https://twitter.com/Berlin_de_News"&gt;@Berlin_de_News&lt;/a&gt; does:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--u6Z3bYfZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/863021046710112256/KsLc51AW_normal.jpg" alt="Berlin aktuell profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Berlin aktuell
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @berlin_de_news
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Neue Webseiten für digitale Kulturevents in Berlin &lt;a href="https://t.co/WY3qdmJtxH"&gt;dlvr.it/RSrPvC&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      16:29 PM - 30 Mar 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1244663060792356864" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1244663060792356864" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1244663060792356864" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;To parse the HTML, I've choosen &lt;a href="https://cheerio.js.org"&gt;cheerio&lt;/a&gt; which allows you to "jQuery" the input. This way I can navigate and select parts of the HTML document that I want to extract the data from.&lt;/p&gt;

&lt;p&gt;The parsing code looks like the one below:&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;cheerio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cheerio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;parseArticles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// HTML is `response.data` from `fetchArticles`&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cheerio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// `.special` might include some "random" articles&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;#hnews&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.special&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.heading&lt;/span&gt;&lt;span class="dl"&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="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;toArray&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;Fetched articles: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;articles&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;articles&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;I navigate through all the &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; from an specific part of the page and &lt;code&gt;.map&lt;/code&gt; them. There are some specific things like &lt;code&gt;#hnews&lt;/code&gt;, &lt;code&gt;.parent()&lt;/code&gt; and &lt;code&gt;.not()&lt;/code&gt; that are rules I followed to find the articles section. This is a sensitive part but it does the job for now. The same result could be achieved using other selectors as well.&lt;/p&gt;

&lt;p&gt;The result is the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Article title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.berlin.de/path/to/article/title&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;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Article title 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.berlin.de/path/to/article/title-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This concludes our crawler: it fetches the page and parses so we have a more structure data to work.&lt;/p&gt;

&lt;p&gt;Next step is to tweet the extracted articles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tweeting
&lt;/h2&gt;

&lt;p&gt;First step was to create a Twitter account/app.&lt;/p&gt;

&lt;p&gt;&lt;del&gt;Thankfully the handler &lt;code&gt;@Berlin_en_News&lt;/code&gt; was not yet taken and it would be perfect for this case as the German version (official) is called &lt;code&gt;@Berlin_de_News&lt;/code&gt;.&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;The paragraph above is outdated. Once I started writing the article I created &lt;code&gt;@Berlin_en_News&lt;/code&gt; which was perfect but it got locked due a mistake of mine. After more than a week without getting unlocked I gave up and created a new account. That is when &lt;code&gt;@Berlinglish&lt;/code&gt; was born. It is basically Berlin + English =]&lt;/p&gt;

&lt;p&gt;Now I've all the necessary keys to use the &lt;a href="https://developer.twitter.com/en/docs/basics/getting-started"&gt;Twitter API&lt;/a&gt; and I just need to start tweeting.&lt;/p&gt;

&lt;p&gt;I ended up using a library called &lt;a href="https://github.com/desmondmorris/node-twitter"&gt;twitter&lt;/a&gt; to do so. It is not necessary to use a library as Twitter API seems really friendly but my goal was not to optimize or so in the beginning, I wanted to make it work first =]&lt;/p&gt;

&lt;p&gt;This is the code needed to get the library ready to use (all Twitter keys are environment varibles):&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;Twitter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twitter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Twitter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;consumer_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumer_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_API_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;access_token_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;access_token_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_ACCESS_TOKEN_SECRET&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;To tweet we need to use the following API: &lt;a href="https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update"&gt;POST statuses/update&lt;/a&gt;. It has a lot of different parameters. At the beginning I'm ignoring most of them. I'm just using the &lt;code&gt;place_id&lt;/code&gt; so it shows that the tweet is from Berlin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gw_Rc6i8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/cl0j9Hz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gw_Rc6i8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/cl0j9Hz.png" alt="Example's Tweet" width="613" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following code walks through the process of tweeting:&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;placeId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;3078869807f9dd36&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Berlin's place ID&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;postTweet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;statuses/update&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;// `client` was instantiated above&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Tweet content&lt;/span&gt;
    &lt;span class="na"&gt;place_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;placeId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;newArticles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// `newArticles` come from the crawler&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;postTweet&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;`Read more: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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;Tweet response: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&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 BOT is almost ready. It misses on important aspect: it shouldn't tweet the same article again. So far it doesn't know which articles it already tweeted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Filtering new articles
&lt;/h3&gt;

&lt;p&gt;This process denifitely needs to be improved but it does the job for now (again) =]&lt;/p&gt;

&lt;p&gt;I fetch the BOT's timeline and compare it with the articles' titles. The only tricky thing is that Twitter won't exactly use the article URL in the tweet itself, so some dirty "magic" had to be written for now. As I said, it does the job for now =]&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;homeTimeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;statuses/user_timeline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;responseTitles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tweet&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;tweet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&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="c1"&gt;// Dirty "magic" 🙈&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;Last tweets titles: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;responseTitles&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;responseTitles&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;articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tweets&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;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;fetchArticles&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;homeTimeline&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;newArticles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tweets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place I'm "sure" that it will tweet only the new articles.&lt;/p&gt;

&lt;p&gt;Now the BOT itself is done. There is one major issue: I need to run it on my machine. The next step is to deploy it so it runs automatically =]&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;I chose to deploy it to Lambda by convenience as I'm more familiar to it and this BOT won't run all day long. It will run every 30min (using a CloudWatch scheduler) at the moment which means that it would be a good use case for Lambda.&lt;/p&gt;

&lt;p&gt;Everything was deployed using &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html"&gt;AWS SAM&lt;/a&gt; as I wanted to try the tool in a real project. It gives you a lot of flexibility but also some challenges if you compare it to &lt;a href="https://serverless.com"&gt;Serverless Framework&lt;/a&gt; for example.&lt;/p&gt;

&lt;p&gt;You can check the PR where I added the deployment here: &lt;a href="https://github.com/viniciuskneves/berlinglish/pull/4"&gt;https://github.com/viniciuskneves/berlinglish/pull/4&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The configuration file &lt;code&gt;template.yaml&lt;/code&gt; (which is used by SAM) is divided into 3 important blocks that I'm going to explore: Resources, Globals and Parameters.&lt;/p&gt;

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

&lt;p&gt;In my case I'm using a Lambda Function and a CloudWatch scheduler as resources. The CloudWatch scheduler is automatically created for us once we define it as an event source for our function. The trickiest part here is to know how to define a schedule, which you would have to go through the docs if you want to understand it a bit better: &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html"&gt;https://docs.aws.amazon.com/eventbridge/latest/userguide/scheduled-events.html&lt;/a&gt;&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;Resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;TwitterBotFunction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Defining an AWS Lambda Function&lt;/span&gt;
   &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
   &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.handler&lt;/span&gt;
     &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;Scheduler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# CloudWatch Scheduler automatically created&lt;/span&gt;
         &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Schedule&lt;/span&gt;
         &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Schedule execution for every 30min&lt;/span&gt;
           &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
           &lt;span class="na"&gt;Schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rate(30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minutes)'&lt;/span&gt; &lt;span class="c1"&gt;# Runs every 30min&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Globals
&lt;/h3&gt;

&lt;p&gt;Those are global settings applied to our resources. I could have defined them inside each resource for example but it doesn't make sense for the project so far.&lt;/p&gt;

&lt;p&gt;I'm setting my runtime, which is Node.js for this project, a timeout for Lambda and also my environment variables which are used by my function (Twitter keys).&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;Globals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;Function&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs12.x&lt;/span&gt;
   &lt;span class="na"&gt;Timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
   &lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;Variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;TWITTER_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;TwitterApiKey&lt;/span&gt;
       &lt;span class="na"&gt;TWITTER_API_SECRET_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;TwitterApiSecretKey&lt;/span&gt;
       &lt;span class="na"&gt;TWITTER_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;TwitterAccessToken&lt;/span&gt;
       &lt;span class="na"&gt;TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!Ref&lt;/span&gt; &lt;span class="s"&gt;TwitterAccessTokenSecret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is missing now is where do those keys come from, that is why I've added a Parameters block.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parameters
&lt;/h3&gt;

&lt;p&gt;Those are the parameters my build is expecting. I've decided to setup it like that in a way to avoid hardcoding the keys. There are different strategies here and I went for the quickest one for now.&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;Parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;TwitterApiKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Twitter API Key&lt;/span&gt;
   &lt;span class="na"&gt;NoEcho&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
   &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
 &lt;span class="na"&gt;TwitterApiSecretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Twitter API Secret Key&lt;/span&gt;
   &lt;span class="na"&gt;NoEcho&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
   &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
 &lt;span class="na"&gt;TwitterAccessToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Twitter Access Token&lt;/span&gt;
   &lt;span class="na"&gt;NoEcho&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
   &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
 &lt;span class="na"&gt;TwitterAccessTokenSecret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Twitter Access Token Secret&lt;/span&gt;
   &lt;span class="na"&gt;NoEcho&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
   &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;String&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, once I call the deployment command I need to pass those parameters as arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam deploy &lt;span class="nt"&gt;--parameter-overrides&lt;/span&gt; &lt;span class="nv"&gt;TwitterApiKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TWITTER_API_KEY&lt;/span&gt; &lt;span class="nv"&gt;TwitterApiSecretKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TWITTER_API_SECRET_KEY&lt;/span&gt; &lt;span class="nv"&gt;TwitterAccessToken&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TWITTER_ACCESS_TOKEN&lt;/span&gt; &lt;span class="nv"&gt;TwitterAccessTokenSecret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TWITTER_ACCESS_TOKEN_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Contribute and share
&lt;/h2&gt;

&lt;p&gt;I hope I could briefly share the idea behind the BOT and I also hope you could understand it. Please, don't hesitate to ask, I will try my best to help you.&lt;/p&gt;

&lt;p&gt;It has been a fun process, some learnings as Twitter account blocked by mistake, but at the end it was useful, at least to me. Now I don't need to open the news website every day and can just wait until I get notified about a new tweet =]&lt;/p&gt;

&lt;p&gt;I would appreciate if you could share the project so it would help other people as well, especially in Berlin =]&lt;br&gt;
I would also appreciate if you want to contribute to the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New ideas: add images to tweets, add comments... Anything that could be done on Twitter level to enhance the experience.&lt;/li&gt;
&lt;li&gt;Project maintenance: I've setup some &lt;a href="https://github.com/viniciuskneves/berlinglish/issues"&gt;issues on GitHub&lt;/a&gt; and you're more than welcome to give it a try.&lt;/li&gt;
&lt;li&gt;New sources: do you have any other sources that are worth adding? Let me know and we can work on it.&lt;/li&gt;
&lt;li&gt;New city/topic: would you like to have it in your city as well? For a specific topic? Let's make it happen =]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you and #StayHome =]&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>lambda</category>
      <category>bot</category>
    </item>
    <item>
      <title>Securing your component library with visual testing</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Wed, 11 Dec 2019 09:56:44 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/securing-your-component-library-with-visual-testing-1lk</link>
      <guid>https://dev.to/viniciuskneves/securing-your-component-library-with-visual-testing-1lk</guid>
      <description>&lt;p&gt;Find out how we added visual testing to our component library (&lt;a href="https://blocks.homeday.dev/" rel="noopener noreferrer"&gt;Homeday-blocks&lt;/a&gt;) and why I think it is important to have visual testing in your test toolkit.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj8l0mex2q0c8hohqn37o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj8l0mex2q0c8hohqn37o.jpg" alt="This is me in the middle of a release"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;As an introduction I want to share how I started with visual testing and why I decided to add it to our component library.&lt;/p&gt;

&lt;p&gt;Currently we’re working on a new &lt;a href="https://www.homeday.de/" rel="noopener noreferrer"&gt;Homeday&lt;/a&gt;’s project and I decided to enhance our unit tests with some integration and visual tests. The idea is to have a secure and robust project, in which we feel comfortable adding new features, refactoring code and fixing future issues — which hopefully we will not have 😅&lt;/p&gt;

&lt;p&gt;We’re using Cypress in this project, so I decided to integrate screenshot comparison tool with it and see how that works together. Unfortunately it didn’t work that well. I’ve tried two tools: &lt;a href="https://github.com/meinaart/cypress-plugin-snapshots" rel="noopener noreferrer"&gt;cypress-plugin-snapshots&lt;/a&gt; and &lt;a href="https://github.com/palmerhq/cypress-image-snapshot" rel="noopener noreferrer"&gt;cypress-image-snapshot&lt;/a&gt;. Both are great, as they are free, however when dealing with visual testing you have to be ready to adjust some parameters and find magic numbers that work for you. As it is not my main focus to really dig deep into how visual testing really works (maybe in the future), I decided to find a better and mature tool to do so. I ended up using Percy. &lt;em&gt;Just as a disclaimer: I’m not being sponsored by Percy.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Reading through Percy docs, I saw that they have an &lt;a href="https://docs.percy.io/docs/storybook" rel="noopener noreferrer"&gt;easy solution to integrate with Storybook&lt;/a&gt;. The good news is that our component library already uses Storybook. The bad news is that not all the components have stories (which means they don’t appear in Storybook). &lt;a href="https://github.com/homeday-de/homeday-blocks/pull/28" rel="noopener noreferrer"&gt;Since we introduced unit tests in our component library&lt;/a&gt; I’ve been thinking about ways to expand it and make our component library more robust then it currently is. That’s why I ended up trying out visual testing.&lt;/p&gt;

&lt;p&gt;Every quarter we have a week to develop and try new stuff in the company. It is called PEW (Product Engineering Week). It works as following: Week before you select your topic; Monday morning you do a quick intro to your topic; Friday afternoon you present your results. During this week all ours meetings are canceled so we can really focus on our topics. My PEW project this quarter is to add visual testing to our component library =]&lt;/p&gt;

&lt;h1&gt;
  
  
  First steps
&lt;/h1&gt;

&lt;p&gt;As I mentioned before, I’m going to use Percy. In order to do that you need an account and add your project there. It just asks you to authorize access to your repo account, that way you can see the status of the visual testing in your PR. This is not mandatory, but it helps you to keep track of your tests, specially if you’re opening a PR or so. I’m also relying on Storybook as our component library already uses it.&lt;/p&gt;

&lt;p&gt;Most of what I'm going to show here can be found in &lt;a href="https://docs.percy.io/docs/storybook" rel="noopener noreferrer"&gt;Percy docs for Storybook as well&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You just need one dependency to start using Percy + Storybook: &lt;code&gt;@percy/storybook&lt;/code&gt; 😅&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;--save-dev&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; @percy/storybook &lt;span class="c"&gt;# --save-exact is optional&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing the dependency we need a &lt;code&gt;PERCY_TOKEN&lt;/code&gt; environment variable to secure and match the local project and the Percy project. I also recommend to add it to your CI, as it is probably the tool that will fire your tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PERCY_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;my secret token here&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we run Percy, we need to first build Storybook, otherwise Percy doesn’t know where to find the stories. Now we can simply run &lt;code&gt;npx percy-storybook&lt;/code&gt; command and it is going to screenshot every single story from Storybook.&lt;/p&gt;

&lt;p&gt;With this simple setup, we can already benefit from the powers of visual testing. It is even better if you’re just starting a project, so you can carefully cover each case you want with visual testing. As we already have an in-use, project we can’t do that. We needed some further adoptions that I'll describe in the following section.&lt;/p&gt;

&lt;h1&gt;
  
  
  Knobs, repetitive stories and other corner cases
&lt;/h1&gt;

&lt;p&gt;Our component library is open source, so I’ve also &lt;a href="https://percy.io/Homeday/homeday-blocks" rel="noopener noreferrer"&gt;opened the Percy builds&lt;/a&gt;, so everyone that is interested can follow it as well.&lt;/p&gt;

&lt;p&gt;The first issue I encountered was related to our Homepage. After running the &lt;a href="https://percy.io/Homeday/homeday-blocks/builds/3241948" rel="noopener noreferrer"&gt;first test&lt;/a&gt; I saw it took a &lt;a href="https://percy.io/Homeday/homeday-blocks/builds/3241948/view/201881887/1280?mode=diff&amp;amp;browser=chrome&amp;amp;snapshot=201881887" rel="noopener noreferrer"&gt;screenshot from it&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgrmz3wzm6kzs781rshue.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgrmz3wzm6kzs781rshue.png" alt="Welcome component snapshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have a Welcome component which renders the first page in Storybook, so it becomes our Home. I don’t want to visual test it, so I had to find a way to ignore it. Percy docs are pretty clear about that, you simply &lt;a href="https://github.com/homeday-de/homeday-blocks/pull/228/files#diff-a5c3d3fb0f63426eca060505117f0aa7" rel="noopener noreferrer"&gt;add &lt;code&gt;skip&lt;/code&gt; parameter to your story and it gets ignored&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;storiesOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addParameters&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;percy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second issue was similar to the first one, but the scenario is a bit different. We have some components with knobs, which allow the users from Storybook to play with them. It basically binds the component’s props with some inputs, so you can nicely interact with the component. I don’t want to test those. I need to create all possible variations in stories so I can take screenshots from all of them and compare over time. The knobs are great for users as they can play with the components, but not that useful when dealing with visual testing. In order to avoid that, I had to skip some Playground Stories (that is how we call the stories with knobs) and &lt;a href="https://github.com/homeday-de/homeday-blocks/pull/228/files#diff-1f1f5507543faff529485b8f01f98952" rel="noopener noreferrer"&gt;refactor some stories as well&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;stories&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Playground 🎛&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HdButton&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
      &amp;lt;HdButton
        :modifier=modifier
      &amp;gt;{{ text }}&amp;lt;/HdButton&amp;gt;
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;percy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of our stories makes sense only on mobile view, as it adapts the component on mobile. This is our third issue, which can be solved simply by adjusting the configuration. Instead of taking screenshots on mobile and desktop, I’ve added a parameter to &lt;a href="https://github.com/homeday-de/homeday-blocks/pull/228/files#diff-10a468c172e80b63ec243a6cda3087b6" rel="noopener noreferrer"&gt;specify the screen size&lt;/a&gt;. Just as a reminder: Every extra screenshot will add to your account limit and also will take extra time to process, so I decided to really avoid the unnecessary screenshots from the beginning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;percy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;375&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 last issue: CI integration. As the project is open source, it means that people can fork it and open a PR. We (developers from the company) follow the same approach. We have our forks and open PRs from them to the main repo. We use Travis as CI, but it happens on other CIs as well, it doesn’t share environment variables to fork builds, due security issues. It means that &lt;code&gt;PERCY_TOKEN&lt;/code&gt; won’t be available when CI builds. Without &lt;code&gt;PERCY_TOKEN&lt;/code&gt;, the Percy command fails, which also fails the CI build. To avoid that, I had to &lt;a href="https://github.com/homeday-de/homeday-blocks/pull/228/files#diff-b9cfc7f2cdf78a7f4b91a753d10865a2R32" rel="noopener noreferrer"&gt;add a check in the test command&lt;/a&gt; for the existence of the &lt;code&gt;PERCY_TOKEN&lt;/code&gt;. If it does not exist, we skip the tests. If you want to run Percy in this scenario, you have to checkout the desired branch and run tests locally, with &lt;code&gt;PERCY_TOKEN&lt;/code&gt; exported. Alternatively, you can create a new branch in the origin repo that will trigger the build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"test:percy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"if [ $PERCY_TOKEN ]; then percy-storybook; else echo 'Skipping test: PERCY_TOKEN is not defined'; fi"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Final thoughts
&lt;/h1&gt;

&lt;p&gt;You can find the final PR &lt;a href="https://github.com/homeday-de/homeday-blocks/pull/228" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Fell free to add your comments and questions there.&lt;/p&gt;

&lt;p&gt;The integration Percy + Storybook works smoothly and it is relatively simple to add it to a project. Percy also offers a pretty good free tier so you don’t have to worry about investing some money from the beginning.&lt;/p&gt;

&lt;p&gt;There are still some challenges to be tackled, especially components that request some interaction or event to happen. Those need to be better explored and tested. It will probably lead to new stories being written.&lt;/p&gt;

&lt;p&gt;Percy also allows you to customize your integration even deeper if you want, I do suggest you to read its &lt;a href="https://docs.percy.io/docs/storybook" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; if you need to do such a thing.&lt;/p&gt;

&lt;p&gt;I would recommend you to give it a try and also share your learnings and sorrows [=&lt;/p&gt;

&lt;p&gt;Happy coding \o/&lt;/p&gt;

&lt;h6&gt;
  
  
  Thanks to &lt;a href="https://medium.com/@ilyas.amezouar" rel="noopener noreferrer"&gt;Ilyas&lt;/a&gt;, &lt;a href="https://dev.to/originalexe"&gt;Ante&lt;/a&gt; and &lt;a href="https://medium.com/@sinisa.grubor" rel="noopener noreferrer"&gt;Sinisa&lt;/a&gt; for reviewing the text.
&lt;/h6&gt;

</description>
      <category>vue</category>
      <category>percy</category>
      <category>testing</category>
      <category>javascript</category>
    </item>
    <item>
      <title>"Ich bin eine Kartoffel" and your German skills during Hacktoberfest</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Thu, 03 Oct 2019 13:52:20 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/ich-bin-eine-kartoffel-and-your-german-skills-during-hacktoberfest-nm1</link>
      <guid>https://dev.to/viniciuskneves/ich-bin-eine-kartoffel-and-your-german-skills-during-hacktoberfest-nm1</guid>
      <description>&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%2Fraw.githubusercontent.com%2Fviniciuskneves%2Fichbineinekartoffel.de%2Fmaster%2Fichbineinekartoffel.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%2Fraw.githubusercontent.com%2Fviniciuskneves%2Fichbineinekartoffel.de%2Fmaster%2Fichbineinekartoffel.png" alt="Ich bin eine Kartoffel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been living in Germany since February. Coming from Brazil I can tell you that my German skills are no the best. I can also tell you that German is not easy to learn coming from Brazil (it might be easier for you depending on which languages you already know).&lt;/p&gt;

&lt;p&gt;Nevertheless even before I came to Germany I knew one word: Kartoffel. Apparently it is the one of the most well known German words worldwide and it is heavily used among foreigners.&lt;/p&gt;

&lt;p&gt;After some months here I realized that Kartoffel is a powerful word and also a German cliché. You can hear from almost all new students the following sentence: Ich bin eine Kartoffel. The translations is straightforward: I'm a potato.&lt;/p&gt;

&lt;p&gt;"Ich bin eine Kartoffel" + &lt;a href="http://theoldpurple.com" rel="noopener noreferrer"&gt;purple&lt;/a&gt; + some inspiration from &lt;a href="https://twitter.com/NikkitaFTW" rel="noopener noreferrer"&gt;Sara Vieira&lt;/a&gt; and her talk "Build dumb shit" I decided to build &lt;a href="https://ichbineinekartoffel.de" rel="noopener noreferrer"&gt;ichbineinekartoffel.de&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I think the website talks by itself but you can contribute to make it even better and spread the word Kartoffel to the rest of the world. I've opened some issues where you can play a bit and help me to improve this masterpiece.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/viniciuskneves" rel="noopener noreferrer"&gt;
        viniciuskneves
      &lt;/a&gt; / &lt;a href="https://github.com/viniciuskneves/ichbineinekartoffel.de" rel="noopener noreferrer"&gt;
        ichbineinekartoffel.de
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Ich bin eine Kartoffel.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&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%2Fraw.githubusercontent.com%2Fviniciuskneves%2Fichbineinekartoffel.de%2Fmaster%2Fsoyouspeakgerman.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%2Fraw.githubusercontent.com%2Fviniciuskneves%2Fichbineinekartoffel.de%2Fmaster%2Fsoyouspeakgerman.png" alt="So you speak German? Tell me which phrases do you know besides "&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>contributorswanted</category>
    </item>
    <item>
      <title>CSSConf EU 2019 in a short opinionated recap</title>
      <dc:creator>Vinicius Kiatkoski Neves</dc:creator>
      <pubDate>Tue, 04 Jun 2019 12:20:00 +0000</pubDate>
      <link>https://dev.to/viniciuskneves/cssconf-eu-2019-in-a-short-opinionated-recap-1ien</link>
      <guid>https://dev.to/viniciuskneves/cssconf-eu-2019-in-a-short-opinionated-recap-1ien</guid>
      <description>&lt;p&gt;This is a summary of what happened during &lt;a href="https://2019.cssconf.eu/schedule/"&gt;CSSConf EU 2019&lt;/a&gt; regarding its talks for those whom missed it or have been there and would like a recap. I haven't watched all the talks so I will give some opinion on the ones I've watched and ⭐ the ones I consider a "must watch".&lt;/p&gt;

&lt;p&gt;I've also added the sketch notes from &lt;a href="https://twitter.com/lisi_linhart"&gt;@lisi_linhart&lt;/a&gt;, &lt;a href="https://twitter.com/wilfredspringer"&gt;@wilfredspringer&lt;/a&gt;, &lt;a href="https://twitter.com/clairikine"&gt;@clairikine&lt;/a&gt; and &lt;a href="https://twitter.com/SarahOnDaCloud"&gt;@SarahOnDaCloud&lt;/a&gt; which are a great summary of what have happened on each talk.&lt;/p&gt;

&lt;p&gt;If you have some extra material that you would like to add here, let me know! It could be extra sketch notes, videos, comments, tweets... Everything you thing might be relevant to add to this list!&lt;/p&gt;

&lt;p&gt;Hope you enjoy!&lt;/p&gt;




&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/dle9t_DlTI4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Talks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why do we call HTML tags "tags"? A journey through the history of CSS, mathematics, and CS to find out! by Andrés Cuervo&lt;/li&gt;
&lt;li&gt;⭐ Hello subgrid! by Rachel Andrew&lt;/li&gt;
&lt;li&gt;Using DevTools to understand modern CSS layouts by Chen Hui Jing&lt;/li&gt;
&lt;li&gt;⭐ Modern Typographic Systems with Variable Fonts by Jason Pamental&lt;/li&gt;
&lt;li&gt;⭐ Don’t Believe the Rumors: Writing Tests for CSS is Possible by Gil Tayar&lt;/li&gt;
&lt;li&gt;Let’s web dev like it’s 1999! by Ben Ilegbodu&lt;/li&gt;
&lt;li&gt;Improving Website Performance with CSS Containment by Manuel Rego&lt;/li&gt;
&lt;li&gt;⭐ Class 🥊Clash by Alex Tait&lt;/li&gt;
&lt;li&gt;A Working Theory of Components by Elyse Holladay&lt;/li&gt;
&lt;li&gt;⭐ Design System Magic with CSS Houdini by Samuel Richard&lt;/li&gt;
&lt;li&gt;Closing Keynote by Sareh Heidari&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;a href="https://2019.cssconf.eu/speakers/andres-cuervo/#talk"&gt;Why do we call HTML tags "tags"? A journey through the history of CSS, mathematics, and CS to find out!&lt;/a&gt; by &lt;a href="https://twitter.com/acwervo"&gt;Andrés Cuervo&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;The name of the talk says by itself. Nothing technical but quite interesting. If you ever wanted to know why we call it &lt;em&gt;tags&lt;/em&gt; this is the right talk. Andrés goes through  the history and how the terminology &lt;em&gt;tag&lt;/em&gt; was coined.&lt;br&gt;
Slides are available here: &lt;a href="https://slides.cwervo.com/mdx-slides/css-conf-eu-2019/?mode=#0"&gt;https://slides.cwervo.com/mdx-slides/css-conf-eu-2019/?mode=#0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/oWwJA1QLItA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  ⭐ &lt;a href="https://2019.cssconf.eu/speakers/rachel-andrew/#talk"&gt;Hello subgrid!&lt;/a&gt; by &lt;a href="https://twitter.com/rachelandrew"&gt;Rachel Andrew&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;It was a masterpiece! She walked through some subgrid examples and what problems they do solve (make children elements align based on parent's grid). A really nice summary on how subgrids work and how we can use them. This includes a lot of the material used during the talk: &lt;a href="https://www.smashingmagazine.com/2018/07/css-grid-2/"&gt;https://www.smashingmagazine.com/2018/07/css-grid-2/&lt;/a&gt; and &lt;a href="https://www.smashingmagazine.com/2019/05/display-grid-subgrid/"&gt;https://www.smashingmagazine.com/2019/05/display-grid-subgrid/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slides are available here: &lt;a href="https://noti.st/rachelandrew/i6gUcF"&gt;https://noti.st/rachelandrew/i6gUcF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/vxOj7CaWiPU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://2019.cssconf.eu/speakers/chen-hui-jing/#talk"&gt;Using DevTools to understand modern CSS layouts&lt;/a&gt; by &lt;a href="https://twitter.com/hj_chen"&gt;Chen Hui Jing&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Really nice demos on how to use Flexbox and DevTools to debug it. It would be nice to watch if you haven't played with Flexbox or if you would like to refresh your knowledge a bit. You can find all the demos and play with them here (There is also an awesome readme): &lt;a href="https://github.com/huijing/slides/tree/gh-pages/58-cssconf-2019"&gt;https://github.com/huijing/slides/tree/gh-pages/58-cssconf-2019&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ZRtzk0371tk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  ⭐ &lt;a href="https://2019.cssconf.eu/speakers/jason-pamental/#talk"&gt;Modern Typographic Systems with Variable Fonts&lt;/a&gt; by &lt;a href="https://twitter.com/jpamental"&gt;Jason Pamental&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Another masterpiece in my opinion. He explains variable fonts and how we can benefit from it, some tips on typography and he also talks about the future with progressive font enrichment. You can check more about it here: &lt;a href="https://rwt.io/typography-tips/progressive-font-enrichment-reinventing-web-font-performance"&gt;https://rwt.io/typography-tips/progressive-font-enrichment-reinventing-web-font-performance&lt;/a&gt; and &lt;a href="https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts/"&gt;https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts/&lt;/a&gt;&lt;br&gt;
Slides are available here: &lt;a href="https://noti.st/jpamental/2gsS6v"&gt;https://noti.st/jpamental/2gsS6v&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/0AsAjHPupKQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  ⭐ &lt;a href="https://2019.cssconf.eu/speakers/gil-tayar/#talk"&gt;Don’t Believe the Rumors: Writing Tests for CSS is Possible&lt;/a&gt; by &lt;a href="https://twitter.com/giltayar"&gt;Gil Tayar&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Nice introduction to visual testing using tools like Cypress. There were some really simple and nice examples and why we should test our CSS and why it is not as complicated as we think. Gil even show some tools to automate part of the process and make it faster as this kind of testing might be quite expensive. Code examples: &lt;a href="https://github.com/giltayar/css-testing-rumors"&gt;https://github.com/giltayar/css-testing-rumors&lt;/a&gt;&lt;br&gt;
You can also find a tutorial about it here: &lt;a href="https://css-tricks.com/an-intro-to-web-app-testing-with-cypress-io/"&gt;https://css-tricks.com/an-intro-to-web-app-testing-with-cypress-io/&lt;/a&gt;&lt;br&gt;
Official documentation: &lt;a href="https://www.cypress.io"&gt;https://www.cypress.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Dl_XMd_1F6E"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://2019.cssconf.eu/speakers/ben-ilegbodu/#talk"&gt;Let’s web dev like it’s 1999!&lt;/a&gt; by &lt;a href="https://twitter.com/benmvp"&gt;Ben Ilegbodu&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Ben shows his first website back in time. How it actually worked and how was it to develop websites in 1999. Really nice walk through the history of the web development. If you've never seen &lt;code&gt;&amp;lt;frame&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;frameset&amp;gt;&lt;/code&gt; in action then you should have fun watching this talk.&lt;br&gt;
Slides are available here: &lt;a href="http://www.benmvp.com/slides/2019/cssconfeu/webdev.html#/"&gt;http://www.benmvp.com/slides/2019/cssconfeu/webdev.html#/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/TJxxzKoaLNE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://2019.cssconf.eu/speakers/manuel-rego/#talk"&gt;Improving Website Performance with CSS Containment&lt;/a&gt; by &lt;a href="https://twitter.com/regocas"&gt;Manuel Rego&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;The new CSS &lt;code&gt;contain&lt;/code&gt; property (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/contain"&gt;https://developer.mozilla.org/en-US/docs/Web/CSS/contain&lt;/a&gt;) in a nutshell. Visual examples of what happens to the DOM and the benefits of using it.&lt;br&gt;
Slides are available here: &lt;a href="https://people.igalia.com/mrego/talks/cssconf-eu-2019-css-containment/"&gt;https://people.igalia.com/mrego/talks/cssconf-eu-2019-css-containment/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/iqcO-5_KkJ4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://2019.cssconf.eu/speakers/estefany-aguilar/#talk"&gt;CSS Logical Properties&lt;/a&gt; by &lt;a href="https://twitter.com/teffcode"&gt;Estefany Aguilar&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Deeply technical (CSS Logical Properties) and with the cutest slides I've ever seen! Impossible to make a short summary of it, you need to read, play and watch the talk to try to understand it a bit better. Some examples: &lt;a href="https://github.com/teffcode/logical-properties-examples"&gt;https://github.com/teffcode/logical-properties-examples&lt;/a&gt;&lt;br&gt;
Slides are available here: &lt;a href="https://docs.google.com/presentation/d/1-GOSaggySGIKmcNuDPNPE6ZZEM2H_oF2AOdcMvlG7kk/edit#slide=id.g562dbb611a_0_0"&gt;https://docs.google.com/presentation/d/1-GOSaggySGIKmcNuDPNPE6ZZEM2H_oF2AOdcMvlG7kk/edit#slide=id.g562dbb611a_0_0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/cnwOfaE5GEk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  ⭐ &lt;a href="https://2019.cssconf.eu/speakers/alex-tait/#talk"&gt;Class 🥊Clash&lt;/a&gt; by &lt;a href="https://twitter.com/AT_Fresh_Dev"&gt;Alex Tait&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Have you ever heard of Functional CSS? If not this is a really nice introduction to it. The idea is to have classes that do a single job, which means add a specific CSS property to the element instead of a class that has a bunch of properties and it becomes hard to reuse it.A lot of examples and answers to the problems it solve. Alex mentions &lt;a href="https://github.com/tachyons-css/tachyons"&gt;Tachyons&lt;/a&gt; as a good Functional CSS library. You can also read more about it here: &lt;a href="https://rangle.io/blog/styling-with-functional-css/"&gt;https://rangle.io/blog/styling-with-functional-css/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/vJvgXPCVo30"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://2019.cssconf.eu/speakers/elyse-holladay/#talk"&gt;A Working Theory of Components&lt;/a&gt; by &lt;a href="http://www.elyseholladay.com"&gt;Elyse Holladay&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/RObdwvLLVvM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  ⭐ &lt;a href="https://2019.cssconf.eu/speakers/samuel-richard/#talk"&gt;Design System Magic with CSS Houdini&lt;/a&gt; by &lt;a href="https://twitter.com/Snugug"&gt;Samuel Richard&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Have you ever seen Houdini in action? If not this is the right place. Samuel explains what is Houdini and why it is so magical with some cool examples!&lt;br&gt;
Some Houdini resources can be found in the tweet below:&lt;br&gt;
Liquid error: internal&lt;br&gt;
Slides are available here: &lt;a href="https://talks.page.link/houdini-design-systems"&gt;https://talks.page.link/houdini-design-systems&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/1W79T2ibd5Y"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://2019.cssconf.eu/speakers/sareh-heidari/#talk"&gt;Closing Keynote&lt;/a&gt; by &lt;a href="https://twitter.com/Sareh88"&gt;Sareh Heidari&lt;/a&gt;&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Liquid error: internal&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/S2iI52SfoKE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>css</category>
      <category>cssconf</category>
      <category>cssconfeu</category>
    </item>
  </channel>
</rss>
