<?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: Tommy Brunn</title>
    <description>The latest articles on DEV Community by Tommy Brunn (@nevon).</description>
    <link>https://dev.to/nevon</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F128651%2F2958e7a8-8ce6-4f10-a93d-b266d74ffe86.jpeg</url>
      <title>DEV Community: Tommy Brunn</title>
      <link>https://dev.to/nevon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nevon"/>
    <language>en</language>
    <item>
      <title>Tailscale VPN Exit Node with Split-Tunneling using WireGuard and Namespaces</title>
      <dc:creator>Tommy Brunn</dc:creator>
      <pubDate>Sat, 05 Sep 2026 07:25:59 +0000</pubDate>
      <link>https://dev.to/nevon/tailscale-vpn-exit-node-with-split-tunneling-using-wireguard-and-namespaces-2lkj</link>
      <guid>https://dev.to/nevon/tailscale-vpn-exit-node-with-split-tunneling-using-wireguard-and-namespaces-2lkj</guid>
      <description>&lt;p&gt;Like all other mid-thirties nerds, I run a janky homelab out of my living room. In addition to being my daily driver for gaming and coding, my trusty desktop computer also serves as a media server and &lt;em&gt;content acquisition system&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In a previous iteration, I had been using Tailscale with Mullvad as a VPN provider. This was extremely easy to set up and worked pretty great, but it came with two downsides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;All traffic had to be routed through a Mullvad exit node. The internet is already a challenging place to access thanks to Cloudflare inundating anyone using Firefox and Linux with captchas, but if you use a VPN exit node it gets even worse.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://korben.info/en/mullvad-cofounder-funding-far-right.html" rel="noopener noreferrer"&gt;Part of my subscription cost goes to funding the far-right ethno-fascist party Örebropartiet&lt;/a&gt;, via Mullvad co-founder Daniel Berntsson.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I took this as an opportunity to improve my setup and get rid of Mullvad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Expose services I have running on my desktop in my Tailnet, with MagicDNS working.&lt;/li&gt;
&lt;li&gt;QBittorrent exits via VPN at all times.&lt;/li&gt;
&lt;li&gt;Any device in my Tailnet &lt;em&gt;can&lt;/em&gt; exit via VPN, if I allow it. This includes the desktop that also acts as the exit node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The overall solution that I am aiming for will look 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;          tailnet (MagicDNS, DERP relays)
                    ▲                              ▲
                    │ tailscale0                   │ tailscale-exit
                    │ (root ns)                    │ (ivpn ns)
┌───────────────────┴──────────────────────────────┼────────────────────┐
│   desktop            tailscaled #1               │                    │
│                                                  │ ← passes through   │
│   eth0 ── internet                               │                    │
│   veth-tsr 10.99.0.1/30 ────────────────┐        │                    │
└─────────────────────────────────────────┼────────┼────────────────────┘
                                          │        │
┌─────────────────────────────────────────┼────────┼────────────────────┐
│ ivpn namespace                          │        │                    │
│   ivpn                 tailscaled #2 ───┘        │                    │
│     (--advertise-exit-node)                      │                    │
│   qBittorrent-nox                                │                    │
│   veth-tsn 10.99.0.2/30                          │                    │
│   vpn-interface ── WireGuard ────────────────────┴──▶ IVPN           │
└───────────────────────────────────────────────────────────────────────┘


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

&lt;/div&gt;



&lt;p&gt;This article serves in large part to remind myself how this was actually set up, in case I have to do it again sometime, but perhaps it can be useful also to others.&lt;/p&gt;

&lt;h2&gt;
  
  
  VPN namespace
&lt;/h2&gt;

&lt;p&gt;Before involving Tailscale, where things start getting a bit complicated, I first followed &lt;a href="https://blog.thea.codes/nordvpn-wireguard-namespaces/" rel="noopener noreferrer"&gt;Split tunneling using Wireguard and namespaces&lt;/a&gt; to set up just the VPN namespace and running Qbittorrent-nox inside that namespace.&lt;/p&gt;

&lt;p&gt;The linked article walks through all the steps. Rather than using NordVPN, I chose to use &lt;a href="https://www.ivpn.net/" rel="noopener noreferrer"&gt;IVPN&lt;/a&gt;. They have a similar focus on privacy as Mullvad does, and while I honestly can't say the service is as good as Mullvad's, they get a pass for not funding far-right parties in my country.&lt;/p&gt;

&lt;p&gt;At the end of that article, what I ended up with was a Wireguard namespace called "ivpn" which was connected to an IVPN endpoint. Qbittorrent-nox, which is running as a SystemD service, is set up to use that namespace via &lt;code&gt;NetworkNamespacePath=/var/run/netns/ivpn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point, goal number 2 is achieved. All traffic from Qbittorrent now goes through the VPN. If I want to funnel some other program through the VPN, I &lt;em&gt;can&lt;/em&gt; do so, like &lt;code&gt;sudo ip netns exec ivpn &amp;lt;command&amp;gt;&lt;/code&gt;, but it's obviously not very ergonomic. Let's use Tailscale to solve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailscale in the root namespace
&lt;/h2&gt;

&lt;p&gt;Setting up my desktop to be a regular Tailscale client is no problem. The &lt;code&gt;tailscale&lt;/code&gt; package in Arch already comes with a systemd service definition, so it's just a matter of enabling it with &lt;code&gt;sudo systemctl enable --now tailscaled.service&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tailscale will automatically update &lt;code&gt;/etc/resolv.conf&lt;/code&gt; to point to the Tailscale resolver, so MagicDNS will just work out of the box. I think I may have run &lt;code&gt;tailscale up&lt;/code&gt; once manually first, in order to authenticate.&lt;/p&gt;

&lt;p&gt;At this point, goal 1 is achieved. Other devices on my Tailnet can now access services I expose on my desktop. This is really nothing special - it's just a standard Tailscale setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailscale VPN exit node
&lt;/h2&gt;

&lt;p&gt;Here's where things get dicey. In Tailscale, a device can advertise itself as an exit node, allowing other clients to exit through it. The &lt;em&gt;normal&lt;/em&gt; way to do this would be to spin up a cloud VM somewhere and configure it to be a Tailscale exit node. But I already have this perfectly good namespace right here on my desktop where all the traffic gets routed through my VPN.&lt;/p&gt;

&lt;p&gt;So the plan is to run a &lt;em&gt;second&lt;/em&gt; instance of &lt;code&gt;tailscaled&lt;/code&gt; inside the VPN namespace. My physical machine will end up showing up as two different devices in Tailscale - one is the root namespace where I am just a normal Tailscale client, and one is the ivpn namespace which is going to be a Tailscale exit node. In theory, I should be able to select my ivpn device as an exit node from my desktop, and route all the traffic from my desktop via the exit node and to IVPN, just the same as if it was two physically separate devices.&lt;/p&gt;

&lt;p&gt;Let's try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/tailscale-exit.service
&lt;/span&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Tailscale exit node in the ivpn namespace&lt;/span&gt;
&lt;span class="py"&gt;Documentation&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://tailscale.com/docs/&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wg-netns@ivpn.service&lt;/span&gt;
&lt;span class="py"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wg-netns@ivpn.service&lt;/span&gt;
&lt;span class="py"&gt;BindsTo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wg-netns@ivpn.service&lt;/span&gt;
&lt;span class="py"&gt;PartOf&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wg-netns@ivpn.service&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;

&lt;span class="c"&gt;# Tailscaled rewrites /etc/resolv.conf through the bind-mount, clobbering the
# upstream nameserver in /etc/netns/ivpn/resolv.conf, so we have to add that back
# on startup.
&lt;/span&gt;&lt;span class="py"&gt;ExecStartPre&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/mkdir -p /etc/netns/ivpn&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPre&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/bash -c 'echo "nameserver &amp;lt;VPN_DNS_RESOLVER&amp;gt;" &amp;gt; /etc/netns/ivpn/resolv.conf'&lt;/span&gt;

&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip netns exec ivpn /usr/sbin/tailscaled &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--state=/var/lib/tailscale-exit.state &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--socket=/run/tailscale-exit.sock &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--tun=tailscale-exit &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--port=41641&lt;/span&gt;

&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/bash -c 'for i in {1..50}; do [ -S /run/tailscale-exit.sock ] &amp;amp;&amp;amp; exit 0; sleep 0.2; done; exit 1'&lt;/span&gt;

&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip netns exec ivpn /usr/bin/tailscale &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--socket=/run/tailscale-exit.sock up &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--accept-dns=true &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--advertise-exit-node &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--hostname=%H-ivpn &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--reset&lt;/span&gt;

&lt;span class="py"&gt;ExecStopPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip netns exec ivpn /usr/sbin/tailscaled &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;        &lt;span class="s"&gt;--socket=/run/tailscale-exit.sock --cleanup&lt;/span&gt;

&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you wanted to have multiple exit nodes available, for example in different countries supported by your VPN provider, you should template the file above instead of hardcoding "ivpn". If you do, I'd recommend putting things like the VPN DNS and other VPN specifics into an environment file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This defines the service &lt;code&gt;tailscale-exit.service&lt;/code&gt;, and as you can see we run tailscale in the &lt;code&gt;ivpn&lt;/code&gt; network namespace instead of the root.&lt;/p&gt;

&lt;p&gt;Starting this with &lt;code&gt;sudo systemctl enable --now tailscale-exit.service&lt;/code&gt; makes a new device &lt;code&gt;$(hostname)-ivpn&lt;/code&gt; show up in the Tailscale device list. Before it becomes available as an exit node, you have to allow it in the Tailscale admin panel.&lt;/p&gt;

&lt;p&gt;Using this as an exit node from a different device, such as a phone, now works. You can test it out by selecting the exit node and then querying something like ipinfo.io and seeing that the public IP is that of the VPN provider.&lt;/p&gt;

&lt;p&gt;At startup, tailscaled in the vpn namespace will likely complain about a few optimizations we can make. I didn't notice any difference, but following Tailscale's documentation, I added this override to set some parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight systemd"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/system/wg-netns@ivpn.service.d/override.conf&lt;/span&gt;
&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="c"&gt;# https://tailscale.com/docs/features/exit-nodes?tab=linux#advertise-a-device-as-an-exit-node&lt;/span&gt;
&lt;span class="nt"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/usr/bin/ip netns exec %i bash -c 'echo 1 &amp;gt; /proc/sys/net/ipv4/ip_forward'
&lt;span class="nt"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/usr/bin/ip netns exec %i bash -c 'echo 1 &amp;gt; /proc/sys/net/ipv4/conf/all/forwarding'
&lt;span class="nt"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/usr/bin/ip netns exec %i bash -c 'echo 1 &amp;gt; /proc/sys/net/ipv6/conf/all/forwarding'

&lt;span class="c"&gt;# GRO offload quirk on the WG interface&lt;/span&gt;
&lt;span class="c"&gt;# https://tailscale.com/docs/reference/best-practices/performance#linux-optimizations-for-subnet-routers-and-exit-nodes&lt;/span&gt;
&lt;span class="nt"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;-/usr/bin/ip netns exec %i bash -c '/usr/bin/ethtool -K $(wg show interfaces) rx-udp-gro-forwarding on rx-gro-list off 2&amp;gt;/dev/null || true'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Same host client
&lt;/h3&gt;

&lt;p&gt;Selecting my new exit node from the root tailscale client on the same machine, &lt;code&gt;tailscale up --exit-node=$(hostname)-ivpn --accept-dns=true --exit-node-allow-lan-access=true&lt;/code&gt;, nothing works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nslookup google.com
&lt;span class="gp"&gt;;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; communications error to 100.100.100.100#53: timed out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In fact, it manages to also kill the exit node, which goes offline shortly after selecting it. This is quite impressive if you think about it, as that's running in a different network namespace. Nothing the root daemon does to root-namespace routing should be able to kill connectivity inside the vpn namespace, unless the namespace's traffic actually transits the root routing table - which it does!&lt;/p&gt;

&lt;p&gt;When the root daemon selects the exit node, it installs &lt;code&gt;0.0.0.0/0 dev tailscale0&lt;/code&gt; in its routing table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ip route show table 52
default dev tailscale0
&amp;lt;tailnet ips&amp;gt; dev tailscale0
throw 127.0.0.0/8
throw 172.17.0.0/16 
throw 172.18.0.0/16
throw 192.168.49.0/24
throw 192.168.50.0/24
throw 192.168.122.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/tailscale/tailscale/blob/5201273aec737d6372ab7423c31c04ca3ca2a0c2/tsconst/linuxfw.go#L39-L42" rel="noopener noreferrer"&gt;Tailscale exempts its own sockets from that table using &lt;code&gt;fwmark 0x80000&lt;/code&gt;&lt;/a&gt;, which is why a normal node can still reach DERP while using an exit node. But the kernel WireGuard module's packets don't carry that mark. They get matched by the Tailscale table's default route and shoved into the tunnel, addressed to the exit node, which lives inside the namespace whose underlay is now broken.&lt;/p&gt;

&lt;p&gt;We can confirm this by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip route get &amp;lt;VPN_ENDPOINT_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this returns &lt;code&gt;dev tailscale0&lt;/code&gt; instead of the physical interface, that's the bug.&lt;/p&gt;

&lt;p&gt;It's a chicken-and-egg problem: the IVPN tunnel's underlay is being routed through the tunnel that depends on the IVPN tunnel.&lt;/p&gt;

&lt;p&gt;We can solve this the same way that Tailscale does, by marking the underlay to keep it out of Tailscale's routing table. We &lt;em&gt;could&lt;/em&gt; piggyback on their existing routing rule by just tagging the packets from our vpn interface with the same mark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;ivpn wg &lt;span class="nb"&gt;set&lt;/span&gt; &amp;lt;vpn-interface&amp;gt; fwmark 0x80000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that's quite fragile. If they change their mark, this breaks. Instead, we can define our own routing rule and mark.&lt;/p&gt;

&lt;p&gt;Instead of executing this manually or from some systemd unit, we can have &lt;a href="https://github.com/dadevel/wg-netns" rel="noopener noreferrer"&gt;wg-netns&lt;/a&gt; do it for us when it sets up the wireguard namespace, as per the original split-tunneling article. All that's needed is to add the mark to the interface in the wireguard config file:&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;# /etc/wireguard/ivpn.yaml&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;ivpn&lt;/span&gt;
&lt;span class="na"&gt;base_netns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;managed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;dns-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;&amp;lt;vpn-dns-server&amp;gt;&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&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;&amp;lt;vpn-interface-name&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;fwmark&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0x1000&lt;/span&gt; &lt;span class="c1"&gt;# Adding mark&lt;/span&gt;
  &lt;span class="na"&gt;mtu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1420&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;vpn-address&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;private-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;vpn-private-key&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;peers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;public-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;vpn-public-key&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;vpn-peer-endpoint&amp;gt;&lt;/span&gt;
    &lt;span class="na"&gt;allowed-ips&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;0.0.0.0/0&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;::/0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we add a the routing rules in our wg-netns override (&lt;code&gt;sudo systemctl edit wg-netns@ivpn.service&lt;/code&gt;), to make sure that those packets directly use the main routing table instead of Tailscale's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# wg-netns@ivpn.service.d/override.conf
&lt;/span&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="c"&gt;# Exclude the ivpn packets (marked by 4096/0x1000 in ivpn.yaml) from Tailscale routing
&lt;/span&gt;&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip rule add fwmark 4096/4096 lookup main priority 5000&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip rule add fwmark 4096/4096 lookup default priority 5001&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip rule add fwmark 4096/4096 unreachable priority 5002&lt;/span&gt;

&lt;span class="py"&gt;ExecStopPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-/usr/bin/ip rule del priority 5000&lt;/span&gt;
&lt;span class="py"&gt;ExecStopPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-/usr/bin/ip rule del priority 5001&lt;/span&gt;
&lt;span class="py"&gt;ExecStopPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-/usr/bin/ip rule del priority 5002&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restarting &lt;code&gt;wg-netns@ivpn.service&lt;/code&gt;, the exit node now works. We can confirm this by making a request through the vpn namespace:&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;sudo &lt;/span&gt;ip netns &lt;span class="nb"&gt;exec &lt;/span&gt;ivpn curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://ipinfo.io

&lt;span class="c"&gt;# We can also see that it's being routed to the physical interface,&lt;/span&gt;
&lt;span class="c"&gt;# though we have to mark it manually&lt;/span&gt;
ip route get &amp;lt;VPN_ENDPOINT_IP&amp;gt; mark 0x1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real path between the two daemons
&lt;/h2&gt;

&lt;p&gt;With the way things are currently configured, every byte goes ISP → Tailscale DERP → back to my machine → IVPN → out. This is extremely inefficient, considering all I want to do is shove a byte from my machine to another namespace on the same machine and then to IVPN.&lt;/p&gt;

&lt;p&gt;To resolve this, we have to give a data path between the two namespaces, using a veth pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# wg-netns@ivpn.service.d/override.conf
&lt;/span&gt;
&lt;span class="c"&gt;# This file also contains the directives enabling IP forwarding,
# the GRO tweak, and the routing rules. I'm omitting for brevity.
&lt;/span&gt;
&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="c"&gt;# Clean up direct link in case it already exists
&lt;/span&gt;&lt;span class="py"&gt;ExecStartPre&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-/usr/bin/ip link del veth-tsr&lt;/span&gt;

&lt;span class="c"&gt;# Set up direct veth link to avoid relaying
&lt;/span&gt;&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip link add veth-tsr type veth peer name veth-tsn&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip link set veth-tsn netns %i&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip addr add 10.99.0.1/30 dev veth-tsr&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip link set veth-tsr up&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip netns exec %i ip addr add 10.99.0.2/30 dev veth-tsn&lt;/span&gt;
&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip netns exec %i ip link set veth-tsn up&lt;/span&gt;

&lt;span class="c"&gt;# Bypass Tailscale's route table
&lt;/span&gt;&lt;span class="py"&gt;ExecStartPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip rule add to 10.99.0.0/30 lookup main priority 5003&lt;/span&gt;
&lt;span class="c"&gt;# Remove rule
&lt;/span&gt;&lt;span class="py"&gt;ExecStopPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip rule del priority 5003&lt;/span&gt;

&lt;span class="c"&gt;# Remove link
&lt;/span&gt;&lt;span class="py"&gt;ExecStopPost&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/ip link del veth-tsr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The namespace daemon enumerates 10.99.0.2 as a local endpoint and advertises it. The root daemon's marked sockets look up main, find the connected /30, and go direct. After restarting everything, &lt;code&gt;tailscale status&lt;/code&gt; should no longer show &lt;code&gt;relay (&amp;lt;somewhere&amp;gt;)&lt;/code&gt; and &lt;code&gt;tailscale ping $(hostname)-ivpn&lt;/code&gt; should take 0ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  End result
&lt;/h2&gt;

&lt;p&gt;At this point, all three goals are achieved:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;My desktop root namespace acts as a regular Tailscale client, which can expose any services running on it to the rest of the tailnet.&lt;/li&gt;
&lt;li&gt;Qbittorrent-nox runs in the vpn namespace, so all traffic exits via IVPN.&lt;/li&gt;
&lt;li&gt;A second tailscale daemon runs in the same vpn namespace, advertising itself as an exit node, which can be used either by other devices in my tailnet or by the tailscale daemon running in the root namespace on the same physical machine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unlike my previous setup, this does not depend on me to remember to enable Tailscale before starting Qbittorrent, and I can choose whether I want to exit regular traffic over the VPN or just directly via my ISP. MagicDNS just works, so I can resolve other devices on my tailnet and vice-versa. And not a single SEK goes to political parties spreading hatred in my country.&lt;/p&gt;

&lt;p&gt;I cobbled this together, so there's probably improvements that can be made. It feels a bit fragile to be so dependent on details of how Tailscale sets up routing, and I'm not 100% sure of the ordering of my systemd units, but overall I still think this is a much better option than what I had earlier.&lt;/p&gt;

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

&lt;p&gt;Because the root Tailscale daemon just runs as a regular Tailscale client, and all the complexity is in the exit-node, I can just manage my Tailscale connection using whatever Tailscale GUI I want. For me, that's &lt;a href="https://extensions.gnome.org/extension/10017/tailscale/" rel="noopener noreferrer"&gt;this gnome extension&lt;/a&gt; that integrates with the quick settings menu.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>networking</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Is commercial support a viable way to open-source sustainability?</title>
      <dc:creator>Tommy Brunn</dc:creator>
      <pubDate>Tue, 06 Oct 2020 13:34:02 +0000</pubDate>
      <link>https://dev.to/nevon/supporting-open-source-1n3j</link>
      <guid>https://dev.to/nevon/supporting-open-source-1n3j</guid>
      <description>&lt;p&gt;In the middle of the COVID-19 pandemic, I quit my job as a lead engineer at a &lt;a href="https://info.instabox.io/se-en/"&gt;successful startup&lt;/a&gt; and dedicated myself to open-source. As a generally risk-aware person, this was certainly a move that made me uncomfortable, but coming off of a successful career at &lt;a href="https://www.klarna.com/us/"&gt;Europe's largest fintech unicorn&lt;/a&gt; I felt like I had the leeway to take some risks.&lt;/p&gt;

&lt;p&gt;For the past 3 or so years, I have been developing &lt;a href="https://kafka.js.org"&gt;KafkaJS&lt;/a&gt;, a dependency-free, pure Javascript Apache Kafka client for NodeJS. Over that time, I have seen it grow from a small project mostly internal to the company we were working at, to now powering hundreds of businesses and helping thousands of developers.&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EvCrzOcU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/EjpCkEoXsAAumsB.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&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--fPfC2I6Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/552931785076527104/iQxr0T6a_normal.png" alt="Tommy Brunn profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Tommy Brunn
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @tommybrunn
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P4t6ys1m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Really cool to see that KafkaJS, the project I've been working on with &lt;a href="https://twitter.com/tulios"&gt;@tulios&lt;/a&gt; for the past 3 years, has finally passed kafka-node to become the #1 most downloaded &lt;a href="https://twitter.com/hashtag/kafka"&gt;#kafka&lt;/a&gt; client for &lt;a href="https://twitter.com/hashtag/nodejs"&gt;#nodejs&lt;/a&gt; 🎉&lt;br&gt;&lt;br&gt;&lt;a href="https://t.co/OrqaRtPikv"&gt;kafka.js.org&lt;/a&gt; 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      11:09 AM - 06 Oct 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1313436225416634368" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1313436225416634368" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      1
      &lt;a href="https://twitter.com/intent/like?tweet_id=1313436225416634368" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      2
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  Scaling open-source
&lt;/h1&gt;

&lt;p&gt;Like most open-source projects, one challenge that we always had was finding time to not only develop it further with &lt;a href="https://github.com/tulios/kafkajs/pull/332"&gt;new features&lt;/a&gt; or &lt;a href="https://github.com/tulios/kafkajs/pull/496"&gt;improving existing ones&lt;/a&gt; and handling &lt;a href="https://medium.com/collaborne-engineering/how-we-cut-kafka-data-transfer-costs-by-90-4beb5c464cdf"&gt;valuable community contributions&lt;/a&gt;, but also providing support to our growing community of users.&lt;/p&gt;

&lt;p&gt;At the time of writing, we have approximately 500 developers in &lt;a href="https://kafkajs-slackin.herokuapp.com/"&gt;our Slack community&lt;/a&gt; and there's no shortage of &lt;a href="https://github.com/tulios/kafkajs/issues"&gt;issues being opened on Github&lt;/a&gt; that require triage. Over the years, I've been in numerous Slack conversations and video calls with developers debugging whatever issues they are having. This has been enormously helpful in understanding where the rough edges are and where people tend to get tripped up when building NodeJS applications with Kafka. On a personal level, it has also been very satisfying to get to see how KafkaJS is helping power systems in everything from commodities trading to medtech to commercial retail.&lt;/p&gt;

&lt;p&gt;However, there are only so many hours in a week that I can spend. Although open-source clearly brings value to thousands of companies, it's rare for any of that value to find its way back to the people that create it, and KafkaJS is no exception in that regard. In order to further scale the project and keep a healthy community going, there needs to be a mechanism in place to fund maintenance and development.&lt;/p&gt;

&lt;p&gt;While &lt;a href="https://github.com/sponsors/Nevon"&gt;Github sponsors&lt;/a&gt; and other fundraising tools are laudable efforts, in reality they don't provide any meaningful income except to a rare few extremely widely used projects. Even hugely impactful developers like TJ Holowaychuk &lt;a href="https://github.com/sponsors/tj"&gt;are barely making anything&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commercial support
&lt;/h2&gt;

&lt;p&gt;A time-tested way of funding open-source development is providing commercial support. It allows businesses to accelerate development while simultaneously funding the development of the software they rely on - a win-win.&lt;/p&gt;

&lt;p&gt;While this is typically used to fund larger projects, I don't see any inherent reason why it couldn't work for a medium-sized project such as ours. There are hundreds of businesses using it that could surely benefit from working with one of the few people that know it inside and out - I know because I have already done it on a volunteer basis for years.&lt;/p&gt;

&lt;p&gt;So, given that I'm finding myself willing to take on some risk, I'm giving it a shot!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_FgCTdJt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2s119xmu4l890duae0gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_FgCTdJt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/2s119xmu4l890duae0gw.png" alt="Screenshot of KafkaJS website offering commercial support"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Whether you are using KafkaJS or are thinking of leveraging Kafka in NodeJS, &lt;a href="https://kafka.js.org/#support"&gt;get in touch and save yourself time and effort by working with an expert&lt;/a&gt;. Whether you are just starting out and want to validate your architectural choices, or you are already established and want to solve some gnarly problems together, I'm sure that my experience building secure, performant and maintainable systems in NodeJS can be of help to you!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you aren't in need of support, but just want to support my work anyway, &lt;a href="https://github.com/sponsors/Nevon"&gt;any contribution via Github Sponsors&lt;/a&gt; is greatly appreciated.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>opensource</category>
      <category>javascript</category>
      <category>kafka</category>
    </item>
    <item>
      <title>No Dependency is the Best Dependency</title>
      <dc:creator>Tommy Brunn</dc:creator>
      <pubDate>Wed, 15 Jul 2020 15:10:26 +0000</pubDate>
      <link>https://dev.to/nevon/no-dependency-is-the-best-dependency-14ee</link>
      <guid>https://dev.to/nevon/no-dependency-is-the-best-dependency-14ee</guid>
      <description>&lt;p&gt;Over the past few years, I have been interviewing hundreds of software developers for various positions in tech companies. One question that tends to come up rather frequently is how you pick which package you should depend on. Given that NPM alone currently has around 1 400 000 public packages, chances are that whatever your problem, there are multiple packages that all claim to solve it - leaving you to figure out the best way forward.&lt;/p&gt;

&lt;p&gt;Let's dive into how seemingly most people claim to choose packages, then I will share my alternatives and why any of this matters at all.&lt;/p&gt;

&lt;h1&gt;
  
  
  Github ⭐️ stars and other vanity metrics
&lt;/h1&gt;

&lt;p&gt;Taking the expression "100 Billion Flies Can't Be Wrong" to heart, by far the most common answer I've gotten is that they simply look at the number of stars the repository has on Github.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--usIyHNbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/52x9d8f9a9k0ejigq0in.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--usIyHNbo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/52x9d8f9a9k0ejigq0in.png" alt="Summary of the archived leftpad Github repository showing 1100 stars"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Stars, for those who are unaware, is the version control platform equivalent of a Facebook "like". Clicking the ⭐️ Star button on a repository adds it to your list of starred repository and potentially shares that activity with your followers.&lt;/p&gt;

&lt;p&gt;The theory goes that if other people have deemed this package worthy of a star, it must be good enough for you as well. The benefit of this approach is naturally that it takes very little effort to simply compare a few numbers and pick the highest.&lt;/p&gt;

&lt;p&gt;There are of course other similar metrics that people use. &lt;a href="https://www.npmtrends.com"&gt;Npm Trends&lt;/a&gt; primarily uses number of downloads to compare package popularity over time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmtrends.com/kafkajs-vs-kafka-node-vs-node-rdkafka"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RidzvHBi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ze8mtxijs0w8sxyi7buo.png" alt="Graph showing download trends for KafkaJS, kafka-node and node-rdkafka"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The downside is that these numbers really says nothing about the quality of this package, whether it is still actively maintained (who goes back and removes stars?) or if it has bugs and edge cases that might not affect the majority but could affect you.&lt;/p&gt;

&lt;p&gt;In short, these types of metrics aim to measure the &lt;strong&gt;popularity&lt;/strong&gt; of a package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blogs, articles and tweets 🐦
&lt;/h2&gt;

&lt;p&gt;Another common criteria that I have heard is to look on Twitter or to read blogs to see what other people are recommending.&lt;/p&gt;

&lt;p&gt;This is again a form of external validation of a package's &lt;strong&gt;popularity&lt;/strong&gt;, but this time rather than relying on the wisdom of the crowd, we are choosing to rely on "expert opinion".&lt;/p&gt;

&lt;p&gt;At a surface level, this may perhaps sound fine. We expect that these experts on the internet have done a proper evaluation so that we don't have to. I do the same when choosing which barbecue to buy - I don't try out every one of them, I look at reviews.&lt;/p&gt;

&lt;p&gt;The issue here is that as evidenced by this very article, anyone can put their opinion on the internet - there's no peer review to ensure that my methodology was sound or that I don't have a vested interest in any particular product.&lt;/p&gt;

&lt;p&gt;However, even when an endorsement is done in good faith and a thorough evaluation was done and the facts properly presented, that person simply does not know the context of your project. Just because something was a good fit for them, that doesn't necessarily mean that it's a good fit for you.&lt;/p&gt;

&lt;h1&gt;
  
  
  Activity metrics - the BPM of the maintainer
&lt;/h1&gt;

&lt;p&gt;The MBAs of the development world shun popularity metrics like downloads and stars, and instead looks at "activity metrics", such as number of pull requests and issues, number of commits over time, number of releases and such.&lt;/p&gt;

&lt;p&gt;At the time of writing, &lt;a href="https://openbase.io/"&gt;Openbase.io&lt;/a&gt; was just released to the public, which aims to use these metrics to compare how well-maintained different packages are. For example, &lt;a href="https://openbase.io/js/redis"&gt;it can tell you that &lt;code&gt;redis&lt;/code&gt; (the NodeJS client) has on average 1 commit every 6 days, that the average PR takes 20 days to close and that the average issue stays open for 8 hours&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These types of metrics all serve to measure &lt;strong&gt;activity&lt;/strong&gt;. No one wants to deal with a project where the maintainers are impossible to reach or where bugs stay open for years.&lt;/p&gt;

&lt;p&gt;However, without &lt;em&gt;context&lt;/em&gt;, these metrics are not terribly informative.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some packages are simply &lt;strong&gt;done&lt;/strong&gt; and need no further activity. Simple packages that perform their task well and don't need to be kept up to date with moving targets very rarely need any maintenance - so why would a lack of commits be a negative?&lt;/li&gt;
&lt;li&gt;Depending on the target audience of your package, the issues you receive will be of varying quality. Even the most dedicated maintainer will struggle to close issues that require extended back-and-forths with unresponsive reporters that don't provide repro cases or failing tests.&lt;/li&gt;
&lt;li&gt;A simple way for a package author to improve their metrics is to simply close issues as "wontfix" as soon as possible. Or to integrate services like Dependabot, which keeps your dependencies up to date by creating pull requests that are very easily merged.&lt;/li&gt;
&lt;li&gt;Constant change isn't necessarily a good thing. For complex projects, landing a significant change may require careful thought and discussion over many months. Rushing to release something that's not quite thought through may just introduce bugs or churn as public APIs change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an open-source maintainer, this sort of evaluation criteria, while not without its merits, also has a tinge of entitlement. The vast majority of small (and not so small) open source projects are maintained without compensation by a single author. Expecting that person to be on call and to resolve every issue promptly is an awful attitude that is shockingly common. Later we will come back to what approach we can take to mitigate this risk in a responsible way.&lt;/p&gt;

&lt;h1&gt;
  
  
  Determining quality with this one weird trick...
&lt;/h1&gt;

&lt;p&gt;All of the criteria we have looked at above -&lt;strong&gt;popularity&lt;/strong&gt; and &lt;strong&gt;activity&lt;/strong&gt;- have all been about looking at &lt;em&gt;abstractions&lt;/em&gt; or high-level metrics to indicate something else - &lt;strong&gt;quality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Determining quality requires you to do something that seemingly very few people actually resort to doing - &lt;em&gt;reading code&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Depending on the size and complexity of the project, as well as your familiarity with the subject, you may or may not be able to judge the technical merit of every single piece of software that you interact with. For example, I may be able to make use of Tensorflow to detect faces in an image, but I couldn't without some serious time investment compare the quality of Tensorflow to other competing solutions.&lt;/p&gt;

&lt;p&gt;However, I think people overestimate how complex &lt;em&gt;most&lt;/em&gt; software really is. I bet your average developer could come to an informed decision on the quality of for example &lt;a href="https://github.com/expressjs/express"&gt;&lt;code&gt;express&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/brianc/node-postgres"&gt;&lt;code&gt;pg&lt;/code&gt;&lt;/a&gt; in just a few hours if they weren't so reluctant to even try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seriously, this is my secret.&lt;/strong&gt; Try it next time. Actually sit down and read through the code and understand at least at a high level what it's doing and why. If you don't understand why it's doing something, read up on the subject until you get a decent understanding. Pretty soon you will notice similarities and differences between projects, understand what tradeoffs they made and how easy it will be to evolve the project.&lt;/p&gt;

&lt;p&gt;Be aware, however, that you are looking at a snapshot, not necessarily trajectory. When I first read through &lt;a href="https://github.com/brianc/node-postgres"&gt;&lt;code&gt;pg&lt;/code&gt;&lt;/a&gt; I saw that the protocol code was intermingled within all the other code, and thought that this would make it really hard for the project to evolve. If there was a competing postgres driver at the time, I would at least have had a serious look at that as an alternative. However, nowadays the protocol &lt;a href="https://github.com/brianc/node-postgres/tree/master/packages/pg-protocol/src"&gt;has been refactored and separated out into a separate package&lt;/a&gt; and the remaining codebase looks a lot better. If I had relied only on the quality metric at the time, I would most likely have made a poor choice.&lt;/p&gt;

&lt;h1&gt;
  
  
  Picking a dependency, the hard way
&lt;/h1&gt;

&lt;p&gt;When you choose to take on a new dependency in a project, you are marrying that dependency until death do you part, or you go through a messy and expensive divorce. If the dependency is central enough to your project (ORM, server or DI framework), replacing it may be almost equivalent to a full rewrite. Especially if the project has not been very well structured to isolate different layers, as is often the case. Therefore, it makes sense to invest time in understanding your dependencies and learning about them before it's too late.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the dependency have a sustainable development model (sponsorships, part-/full-time developer, commercial value added services or components)?&lt;/li&gt;
&lt;li&gt;Do I feel confident that if the maintainer stops maintaining the dependency, I could maintain it myself?&lt;/li&gt;
&lt;li&gt;Does an entity with resources have a vested interest in the future of the dependency, and could I or my company be that entity?&lt;/li&gt;
&lt;li&gt;Does my use-case fit with the goals of the dependency?&lt;/li&gt;
&lt;li&gt;If there are significant bugs that affect me, do I feel confident that I could fix them myself?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above points are primarily concerned with &lt;strong&gt;sustainability&lt;/strong&gt;. As we saw from the &lt;code&gt;pg&lt;/code&gt; example, a project being sustainable and having a positive trajectory is more important in the long run than quality at any particular snapshot in time. Or in the words of John Ousterhout:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A little bit of slope makes up for a lot of y-intercept&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Promoting sustainability
&lt;/h2&gt;

&lt;p&gt;Sustainable projects don't happen without either corporate or community support. If a project provides value for you or your company, and you want that project to continue to be supported, you have to contribute with your support.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contribute to &lt;a href="https://opencollective.com/discover?show=open%20source"&gt;open-source projects on OpenCollective&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Become a Github Sponsor of projects you depend on (shill alert: &lt;a href="https://github.com/sponsors/Nevon"&gt;Sponsor my work on KafkaJS&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also contribute your time by providing issue triage and pull requests to address bugs, however, be aware that code contributions also take the maintainer's time, not just when reviewing your contribution, but also because they have to continue to maintain your code for the lifetime of the project. Contributing financially to allow the maintainers to spend more of their time on the project is in many cases more impactful for the long-term health of the project.&lt;/p&gt;

&lt;h1&gt;
  
  
  How about no dependency?
&lt;/h1&gt;

&lt;p&gt;So far we have learned that tying the fate of your project to a dependency is risky business, and mitigating that risk by careful evaluation not just of popularity and activity, but also of quality and sustainability, can be a lot of work. And even when you do put in the work, there is always the risk that you make the wrong choice anyway.&lt;/p&gt;

&lt;p&gt;With this in mind, &lt;strong&gt;I propose that we shift our mindset from "which dependencies should I choose" to "should I use a dependency"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One of the core design choices we made when building &lt;a href="https://kafka.js.org"&gt;KafkaJS&lt;/a&gt; was that we would have little to no runtime dependencies. We chose that because we knew that KafkaJS would be a core part of our users' applications, and as such any dependency that we take on, our users would also have to take on as a transitive dependency. We also knew that these applications would live for quite a long time and power important business processes, so if we chose to take on a dependency that would no longer be maintained, it would be up to us to make sure that our users would not be exposed to new security issues.&lt;/p&gt;

&lt;p&gt;As such, we adopted our "no dependencies" approach - with one small exception, &lt;a href="https://github.com/dcodeIO/long.js"&gt;&lt;code&gt;long.js&lt;/code&gt;&lt;/a&gt;. That exception we chose to allow for because the scope of the dependency was small, we knew that native 64-bit integers for NodeJS was on the horizon, at which point we could get rid of it, and we were confident that we could maintain the library or our own fork ourselves if need be.&lt;/p&gt;

&lt;p&gt;Shifting the mindset from "which dependency should I choose" to "should I use a dependency" meant that we don't need to worry about any of the issues listed above. The downside is of course that we have to maintain more code ourselves - but a lot of the time the code that we actually need is so much less than what dependencies provide. Since we know the context of our project, we can implement just the bit that we need, not cater for the needs of the many.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In short, taking on a dependency has a bigger impact than most people tend to consider. If you are working on a long-term project with actual consequences in case of failure, this is a responsibility you should take seriously. Evaluate your options carefully and weigh the risk vs reward for each dependency.&lt;/p&gt;

&lt;p&gt;And ⭐️s don't matter.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cover image from &lt;a href="https://research.swtch.com/deps"&gt;Russ Cox's excellent article on the same subject&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>npm</category>
      <category>node</category>
      <category>dependencies</category>
      <category>opinion</category>
    </item>
    <item>
      <title>(Sort of) fixing autofocus in iOS Safari</title>
      <dc:creator>Tommy Brunn</dc:creator>
      <pubDate>Tue, 22 Nov 2016 18:05:06 +0000</pubDate>
      <link>https://dev.to/nevon/sort-of-fixing-autofocus-in-ios-safari-4675</link>
      <guid>https://dev.to/nevon/sort-of-fixing-autofocus-in-ios-safari-4675</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally posted on Medium in 2016. It may be out of date at this point.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  (Sort of) Fixing autofocus in iOS Safari
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;One of my colleagues is transitioning to the front-end team that I used to be a part of. To prepare him mentally for his journey into front-end development, I’ve been sending him a newsletter I call Front-End Hack of the Day. I’m posting them to Medium now for the world to enjoy&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine that you are building a form where you would like to help the user out by automatically focusing on the first input field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&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;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"foo@example.com"&lt;/span&gt; &lt;span class="na"&gt;autofocus&lt;/span&gt; &lt;span class="nt"&gt;/&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;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You fire it up and try it out, and it works great. Ship it!&lt;/p&gt;

&lt;p&gt;Sometime later, someone comes to you and says that it’s not working in iOS Safari. Off you go to &lt;a href="http://caniuse.com/#search=autofocus"&gt;caniuse.com&lt;/a&gt; and see that it is indeed not supported in that browser. Oh well, no big deal, we can fix that with a little bit of Javascript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&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="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;el&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;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;autofocus&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="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&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 your great surprise, you discover that this is not working either!&lt;/p&gt;

&lt;p&gt;Turns out, Apple &lt;em&gt;really&lt;/em&gt; doesn’t want you to focus input fields that the user hasn’t tapped on. Not only is the autofocus attribute not supported, but you have in fact made the situation worse!&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X_Nqi2j1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A0N27dTsgnb3VTJzFsnBOIA.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X_Nqi2j1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A0N27dTsgnb3VTJzFsnBOIA.jpeg" alt=""&gt;&lt;/a&gt;What I feel like doing any time I’m developing for Safari&lt;p&gt;&lt;/p&gt;

&lt;p&gt;See, even manually calling focus on the element won’t work until the user has interacted with the page. If the input is inside an iframe and you try to call focus before the user has interacted, the keyboard opens, the input does &lt;strong&gt;not&lt;/strong&gt; get focus, and typing on the keyboard does absolutely nothing. As an added bonus, if the viewport scrolled at all, the useless, blinking cursor will be displayed somewhere outside the input.&lt;/p&gt;

&lt;p&gt;I haven’t been able to find any official resource explaining this decision, but I have to assume that it’s because focusing a field pops up the keyboard, which can be annoying if you didn’t have any intention of filling out the field.&lt;/p&gt;

&lt;h4&gt;
  
  
  Faking focus
&lt;/h4&gt;

&lt;p&gt;We can’t fully emulate the autofocus behavior, but we can get pretty close.&lt;/p&gt;

&lt;p&gt;Focusing a field does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set focus styles&lt;/li&gt;
&lt;li&gt;Scroll the page so the field is somewhere in the middle of the viewport&lt;/li&gt;
&lt;li&gt;Open the keyboard&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;3 is the only thing that Apple has something against, but the other two can be implemented rather easily. I’m going to show you a very specific example, but for your own sanity, I suggest you come up with ways to abstract over this so that you don’t need to worry about whether you’re really focusing the field or if you’re just faking it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Check out &lt;a href="https://github.com/klarna/ui"&gt;@klarna/ui on Github&lt;/a&gt; to see how we are doing it (&lt;a href="https://github.com/klarna/ui/blob/master/lib/features/programmaticFocus/index.js"&gt;programmaticFocus.js&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first part is simple, to set the focus styles, just add a class with the same styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:focus&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nc"&gt;.has-focus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;black&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;Scrolling the input into view is surprisingly simple, thanks to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView"&gt;Element.scrollIntoView&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If we put it all together, we get something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;isIos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/iPad|iPhone/i&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;hasInteracted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;interacted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onTouchStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;interacted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onTouchStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;touchstart&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;onTouchStart&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;interacted&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;FOCUS_TYPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;real&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;FAKE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fake&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;getFocusType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasInteracted&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isIos&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;FOCUS_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REAL&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FOCUS_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FAKE&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;focus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="nx"&gt;getFocusType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;FOCUS_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REAL&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;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;FOCUS_TYPES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FAKE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;has-focus&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;onBlur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;has-focus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onBlur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blur&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onBlur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollIntoView&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;autofocusedInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&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="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;el&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;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;autofocus&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="nx"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;autofocusedInput&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;What we end up with is a field that looks like it has focus and that is centered in the viewport. The keyboard won’t pop up, but that’s as close as we can get.&lt;/p&gt;

&lt;p&gt;Hopefully this has been useful to you. The intention of these posts is not to show you some groundbreaking new front-end technique, but just to share some of the hacks my colleagues and I have had to implement over the years to deal with various browser quirks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This was written mostly from memory, with some input from&lt;/em&gt; &lt;a href="https://medium.com/u/dc0f4cb7debe"&gt;&lt;em&gt;Xavier Via&lt;/em&gt;&lt;/a&gt;&lt;em&gt;, so there may be some inaccuracies. Please leave a comment if I missed something.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>javascript</category>
      <category>hacks</category>
      <category>css</category>
    </item>
    <item>
      <title>Detecting autofilled fields in Javascript</title>
      <dc:creator>Tommy Brunn</dc:creator>
      <pubDate>Mon, 21 Nov 2016 11:46:47 +0000</pubDate>
      <link>https://dev.to/nevon/detecting-autofilled-fields-in-javascript-l46</link>
      <guid>https://dev.to/nevon/detecting-autofilled-fields-in-javascript-l46</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally posted on Medium in 2016. It may be out of date at this point.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the browser autofills a form field, it adds some styling to highlight which fields it has edited. In Chrome, as you can see above, it adds a lovely yellow background color.&lt;/p&gt;

&lt;p&gt;In principle, I think this is a good idea, as it shows the user which fields they should pay attention to in order to see if everything was correctly filled out. However, it will most likely clash with the rest of your sites’ styling, so it would be nice if we could have some more control over it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:-webkit-autofill&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;Much&lt;/span&gt; &lt;span class="err"&gt;nicer&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;bisque&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can add styles by using the vendor prefixed pseudo-class &lt;em&gt;-webkit-autofill&lt;/em&gt;, but if you need to run some Javascript when a field gets autofilled, it gets a lot trickier.&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%2Fcdn-images-1.medium.com%2Fmax%2F916%2F1%2AOACF7P1AqnHU4oKLLh8IHw.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%2Fcdn-images-1.medium.com%2Fmax%2F916%2F1%2AOACF7P1AqnHU4oKLLh8IHw.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://klarna.github.io/ui/" rel="noopener noreferrer"&gt;@klarna/ui&lt;/a&gt;, &lt;a href="https://klarna.com" rel="noopener noreferrer"&gt;Klarna&lt;/a&gt;’s open-source UI component toolkit, we use the floating label pattern. The label is initially displayed like a placeholder, and when you start typing it transitions to a small label at the top of the field.&lt;/p&gt;

&lt;p&gt;This is done by listening for a change event and adding the class &lt;em&gt;is-filled&lt;/em&gt; to the field, which then applies the appropriate zooming and positioning.&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%2Fcdn-images-1.medium.com%2Fmax%2F904%2F1%2AbNXnDCkiIBL0mspFeTpEBg.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%2Fcdn-images-1.medium.com%2Fmax%2F904%2F1%2AbNXnDCkiIBL0mspFeTpEBg.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, no event whatsoever is fired when a field is autofilled, so there’s no way to know when to add the class.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And this is where the hack comes in…&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Change event through animation
&lt;/h4&gt;

&lt;p&gt;We can’t know when the field value changes through autofill, but that’s not the only thing that happens when it gets autofilled — some styles are also applied!&lt;/p&gt;

&lt;p&gt;Unfortunately, we can’t really listen for a style change, but we can listen for the start of an animation, and we can start an animation in response to autofilling using the previously mentioned &lt;em&gt;-webkit-autofill&lt;/em&gt; pseudo-class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;onAutoFillStart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;/\*\*/&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;/\*\*/&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="k"&gt;@keyframes&lt;/span&gt; &lt;span class="n"&gt;onAutoFillCancel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nt"&gt;from&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;/\*\*/&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;to&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;/\*\*/&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:-webkit-autofill&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;Expose&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;hook&lt;/span&gt; &lt;span class="err"&gt;for&lt;/span&gt; &lt;span class="err"&gt;JavaScript&lt;/span&gt; &lt;span class="err"&gt;when&lt;/span&gt; &lt;span class="err"&gt;autofill&lt;/span&gt; &lt;span class="err"&gt;is&lt;/span&gt; &lt;span class="err"&gt;shown&lt;/span&gt;
    &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;JavaScript&lt;/span&gt; &lt;span class="err"&gt;can&lt;/span&gt; &lt;span class="err"&gt;capture&lt;/span&gt; &lt;span class="err"&gt;'animationstart'&lt;/span&gt; &lt;span class="err"&gt;events&lt;/span&gt;
    &lt;span class="nl"&gt;animation-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;onAutoFillStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;Make&lt;/span&gt; &lt;span class="err"&gt;the&lt;/span&gt; &lt;span class="err"&gt;background&lt;/span&gt; &lt;span class="err"&gt;color&lt;/span&gt; &lt;span class="err"&gt;become&lt;/span&gt; &lt;span class="err"&gt;yellow&lt;/span&gt; &lt;span class="err"&gt;really&lt;/span&gt; &lt;span class="err"&gt;slowly&lt;/span&gt;
    &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;background-color&lt;/span&gt; &lt;span class="m"&gt;50000s&lt;/span&gt; &lt;span class="n"&gt;ease-in-out&lt;/span&gt; &lt;span class="m"&gt;0s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;:-webkit-autofill&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;Expose&lt;/span&gt; &lt;span class="err"&gt;a&lt;/span&gt; &lt;span class="err"&gt;hook&lt;/span&gt; &lt;span class="err"&gt;for&lt;/span&gt; &lt;span class="err"&gt;JS&lt;/span&gt; &lt;span class="err"&gt;onAutoFillCancel&lt;/span&gt;
    &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;JavaScript&lt;/span&gt; &lt;span class="err"&gt;can&lt;/span&gt; &lt;span class="err"&gt;capture&lt;/span&gt; &lt;span class="err"&gt;'animationstart'&lt;/span&gt; &lt;span class="err"&gt;events&lt;/span&gt;
    &lt;span class="nl"&gt;animation-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;onAutoFillCancel&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 clarify what’s going on here, we are running the &lt;em&gt;onAutoFillStart&lt;/em&gt; animation when the psuedo class &lt;em&gt;-webkit-autofill&lt;/em&gt; is active, which it will be when Chrome autofills the input and changes the background color.&lt;/p&gt;

&lt;p&gt;Now we can listen for the start of that animation from within our Javascript.&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;AUTOFILLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;is-autofilled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onAutoFillStart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&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;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&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="nx"&gt;AUTOFILLED&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;onAutoFillCancel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&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;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AUTOFILLED&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;onAnimationStart&lt;/span&gt; &lt;span class="o"&gt;=&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;animationName&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;animationName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onAutoFillStart&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="nf"&gt;onAutoFillStart&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="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onAutoFillCancel&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="nf"&gt;onAutoFillCancel&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&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="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;animationstart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onAnimationStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now whenever either the &lt;em&gt;onAutoFillStart&lt;/em&gt; or &lt;em&gt;onAutoFillCancel&lt;/em&gt; animations start playing, our corresponding functions will run, where we will add or remove the &lt;em&gt;is-autofilled&lt;/em&gt; class, or do whatever else we want to do.&lt;/p&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%2Fcdn-images-1.medium.com%2Fmax%2F914%2F1%2AwFilv4jvpGRB5SFngbiCFw.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%2Fcdn-images-1.medium.com%2Fmax%2F914%2F1%2AwFilv4jvpGRB5SFngbiCFw.png"&gt;&lt;/a&gt;Perfectly aligned labels with orange color to show that the field has been autofilled&lt;p&gt;&lt;/p&gt;

&lt;p&gt;I hope this hack will be useful to you. If you want to take a peek at how this is used within &lt;a href="https://github.com/klarna/ui" rel="noopener noreferrer"&gt;@klarna/ui&lt;/a&gt;, &lt;a href="https://github.com/klarna/ui/tree/master/Field" rel="noopener noreferrer"&gt;you can find the implementation of the Field on Github.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>css</category>
    </item>
  </channel>
</rss>
