<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: 🎙 m4dz</title>
    <description>The latest articles on Forem by 🎙 m4dz (@m4dz).</description>
    <link>https://forem.com/m4dz</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%2F755661%2F0ba4b9a4-7a6e-4204-b1d4-0e980f2e6b2e.jpg</url>
      <title>Forem: 🎙 m4dz</title>
      <link>https://forem.com/m4dz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/m4dz"/>
    <language>en</language>
    <item>
      <title>Hold On to Your Socks: High-Speed Data Stream Hosting with Websockets</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Tue, 14 Mar 2023 15:48:16 +0000</pubDate>
      <link>https://forem.com/m4dz/hold-on-to-your-socks-high-speed-data-stream-hosting-with-websockets-2a9h</link>
      <guid>https://forem.com/m4dz/hold-on-to-your-socks-high-speed-data-stream-hosting-with-websockets-2a9h</guid>
      <description>&lt;p&gt;Created by &lt;a href="https://websockets.spec.whatwg.org/"&gt;WHATWG&lt;/a&gt; in 2011 and standardized by &lt;a href="https://datatracker.ietf.org/doc/html/rfc6455"&gt;IETF&lt;/a&gt; in the same year, the &lt;a href="https://en.wikipedia.org/wiki/WebSocket"&gt;WebSocket&lt;/a&gt; protocol has been providing web application developers with the ability to break free from the traditional TCP Client/Server model for real-time data transfer for over 10 years.&lt;/p&gt;

&lt;p&gt;In the initial model, the client requests a resource from the server,the server returns it, and the communication ends. It is not possible for the server to easily "push" messages to the client (such as notifications).&lt;/p&gt;

&lt;p&gt;WebSocket provides support for bidirectional communication (known as &lt;em&gt;full-duplex&lt;/em&gt;) in which the server can now spontaneously "push" data to the client, and where the client can subscribe to messages that interest them and react to them later. Application interactivity, directly in the browser!&lt;/p&gt;

&lt;p&gt;In 2023, how does hosting a WebSocket server application work?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3orifaveUBlsnIAdPO/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3orifaveUBlsnIAdPO/giphy.gif" alt="speaking bart simpson GIF @Giphy" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When A Stanger Calls
&lt;/h2&gt;

&lt;p&gt;Let's get back to basics: WebSocket is a network protocol in the application layer (in the famous &lt;a href="https://en.wikipedia.org/wiki/OSI_model"&gt;OSImodel&lt;/a&gt;). You don't need to be a networking expert to use it, so everything will happen at the code level. It uses a traditional TCP connection between the client and server and the HTTP architecture.&lt;/p&gt;

&lt;p&gt;To put it simply:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The client will request the initial application resource (HTML + JS) from the server.&lt;/li&gt;
&lt;li&gt; This resource will be responsible for establishing communication with the WebSocket server to receive notifications.&lt;/li&gt;
&lt;li&gt; The WebSocket server will register the client as a recipient and push relevant data to them when necessary.&lt;/li&gt;
&lt;li&gt; The client, which was previously waiting, will receive data streams from the server and can process this data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that in WebSocket, there is &lt;em&gt;Web&lt;/em&gt;: the architecture is no different from traditional web applications. We still use HTTP/TCP, we just use a different application protocol. This means that we will need to use dedicated libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Service That Does "Pong"
&lt;/h2&gt;

&lt;p&gt;There are plenty of libraries that can provide WebSocket support in almost all available web languages. For the purpose of this demonstration&lt;sup id="fnref1"&gt;1&lt;/sup&gt; we will implement a small WebSocket server in Node.js and then in Python. You are free to implement it in PHP, Ruby, Java, Perl, etc.&lt;/p&gt;

&lt;p&gt;Our example is very simple: once connected, our client will be able to send the &lt;code&gt;ping&lt;/code&gt; message to the server, which will then send &lt;code&gt;pong&lt;/code&gt; back to all connected clients one second later. This simple example demonstrates the interactivity between all connected elements (i.e.broadcasting) of our application.&lt;/p&gt;

&lt;h3&gt;
  
  
  With Node.js…
&lt;/h3&gt;

&lt;p&gt;Currently, the most reputable library for creating a WebSocket server application in Node.js is &lt;a href="https://github.com/websockets/ws"&gt;websockets/ws&lt;/a&gt;. Add the &lt;code&gt;ws&lt;/code&gt; package to your project using &lt;code&gt;npm&lt;/code&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt; and create a &lt;code&gt;wss.js&lt;/code&gt; file for your WebSocket server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;WebSocketServer&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;ws&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;wss&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;WebSocketServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&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;IP&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="na"&gt;port&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;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8080&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 attach the WebSocket server to the &lt;code&gt;IP&lt;/code&gt;/&lt;code&gt;PORT&lt;/code&gt; pair exposed in the environment variables for more flexibility, with a fallback to port &lt;code&gt;8080&lt;/code&gt; for ease of development.&lt;/p&gt;

&lt;p&gt;Our server is ready, now it needs to be equipped with its functionalities. First and foremost, it should be able to receive client connections.&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;wss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&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;ws&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;// log as an error any exception&lt;/span&gt;
  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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;error&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;Next, when a client sends the &lt;code&gt;ping&lt;/code&gt; message, the server should respond with &lt;code&gt;pong&lt;/code&gt; to all active clients.&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;wss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&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;ws&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="cm"&gt;/* ... */&lt;/span&gt;
  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;// only react to `ping` message&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;data&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ping&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="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;wss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clients&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;client&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;// send to active clients only&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPEN&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;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pong&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all. To launch the WebSocket server, simply run the &lt;code&gt;wss.js&lt;/code&gt; file with Node.js:&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;node wss.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  … or with Python!
&lt;/h3&gt;

&lt;p&gt;To change things up from the usual examples, let's create the same WebSocket server in Python using &lt;em&gt;asyncio&lt;/em&gt; and &lt;a href="https://pypi.org/project/websockets/"&gt;websockets&lt;/a&gt;. Start by installing the &lt;code&gt;websockets&lt;/code&gt; package using &lt;code&gt;pip&lt;/code&gt; in your &lt;em&gt;venv&lt;/em&gt;, and then create a &lt;code&gt;wss.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;websockets&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'IP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'PORT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Run forever
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's define our functionalities: registering clients, and broadcasting the message when a &lt;code&gt;ping&lt;/code&gt; is received. Add to the &lt;code&gt;handler&lt;/code&gt; method which contains the logic for our server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;connected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'ping'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;websockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'pong'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Web Client Side
&lt;/h3&gt;

&lt;p&gt;Our client will be simple: a web page with JavaScript that connects to the WebSocket server, sends a &lt;code&gt;ping&lt;/code&gt; on connection, and has a method to manually send a new &lt;code&gt;ping&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create an &lt;code&gt;index.html&lt;/code&gt; file for your client:&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;WS_SERVER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;dateFormatter&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;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&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;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;numeric&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;websocket&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;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ws://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;WS_SERVER&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ping&lt;/span&gt;&lt;span class="dl"&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="s2"&gt;Send message&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="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="s2"&gt;Recv message&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="nx"&gt;dateFormatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="nx"&gt;websocket&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;open&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="nx"&gt;ping&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;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Open the developer console and run the &lt;span class="nt"&gt;&amp;lt;code&amp;gt;&lt;/span&gt;ping()&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&lt;/span&gt; function&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start your WebSocket server (&lt;em&gt;Python&lt;/em&gt; or &lt;em&gt;Node.js&lt;/em&gt;), and open this HTML page with the developer tools open. You should see &lt;code&gt;ping&lt;/code&gt;/&lt;code&gt;pong&lt;/code&gt; messages appear in the console. Try manually executing the &lt;code&gt;ping()&lt;/code&gt; function in the developer tools.&lt;/p&gt;

&lt;p&gt;Now open this HTML page again in another tab, with the developer tools open. Execute the &lt;code&gt;ping()&lt;/code&gt; function in either tab. Both will receive the &lt;code&gt;pong&lt;/code&gt; from the server.&lt;/p&gt;

&lt;p&gt;Congratulations, you have achieved a basic level of bidirectional broadcast communication between the client and server in full-duplex via WebSocket!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/psv1zrhPZM6WI/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/psv1zrhPZM6WI/giphy.gif" alt="Noot Noot GIF @Giphy" width="476" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Noot Noot: WebSite or WebService ?
&lt;/h2&gt;

&lt;p&gt;We now need to deploy this WebSocket server and client in a production environment.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.alwaysdata.com"&gt;&lt;strong&gt;alwaysdata&lt;/strong&gt;&lt;/a&gt;, we offer several solutions for deploying tools that need to be executed for long periods of time and accessed by clients in the future: &lt;em&gt;Sites&lt;/em&gt; and &lt;em&gt;Services&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;Sites&lt;/em&gt;, it’s straightforward: deploying a web server (Apache, WSGI, Node, etc.) that will be requested later by a client to obtain various resources via HTTP. This is the traditional historical web.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Services&lt;/em&gt;, on the other hand, are designed to run long processes that can potentially be accessed other than via HTTP, within the environment of your account (such as a Git repository server over SSH with &lt;a href="https://github.com/charmbracelet/soft-serve"&gt;soft-serve&lt;/a&gt;, or a monitoring/filtering system for messaging.)&lt;/p&gt;

&lt;p&gt;So, for a WebSocket server, should you use &lt;em&gt;Sites&lt;/em&gt; or &lt;em&gt;Services&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;While one might imagine that &lt;em&gt;Services&lt;/em&gt; are the right place  — for a long-running process that can be accessed from outside  — I will repeat this here: WebSocket includes &lt;em&gt;Web&lt;/em&gt;! The WebSocket protocol is software that uses HTTP/TCP, just like any &lt;em&gt;Site&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A plan that comes together
&lt;/h3&gt;

&lt;p&gt;Our application consists of two parts: a client and a WebSocket server. The WebSocket server cannot serve as a resource like a traditional web server (that's not its role), so you will need two sites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A &lt;em&gt;Static files&lt;/em&gt; type site that will serve your &lt;code&gt;index.html&lt;/code&gt; client.&lt;/li&gt;
&lt;li&gt; A second site adapted to the language of your WebSocket server to execute it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the WebSocket server, use an address of the type: &lt;code&gt;[my-account].alwaysdata.net/wss&lt;/code&gt;. Your WebSocket client must connect to this address. Make sure the &lt;em&gt;Trim path&lt;/em&gt; option is enabled as the server is hosted behind a &lt;code&gt;pathUrl&lt;/code&gt;. You may need to set the value of the &lt;em&gt;idle time&lt;/em&gt; to &lt;code&gt;0&lt;/code&gt; to ensure that it will never be stopped by the system, and to keep the connections to the clients active, even in case of prolonged inactivity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--usGfJ3w1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/03/wss-server-site-1_en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--usGfJ3w1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/03/wss-server-site-1_en.png" alt="WebSocket Site (Python)" width="880" height="938"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i3FcYoxt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/03/wss-server-site-2_en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i3FcYoxt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/03/wss-server-site-2_en.png" alt="WebSocket site, advanced settings: idle time to 0, and trim path checked" width="880" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;index.html&lt;/code&gt; file to specify the WebSocket server URL in the&lt;code&gt;WS_SERVER&lt;/code&gt; variable. Then create a &lt;em&gt;Static files&lt;/em&gt; site with the address &lt;code&gt;[my-account].alwaysdata.net&lt;/code&gt; to serve this file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zg0LVgJy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/03/wss-static-site_en.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zg0LVgJy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/03/wss-static-site_en.png" alt="Static files Client Site" width="880" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head to this address: your WebSocket communication is up and running!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://i.giphy.com/media/3ohryvw2Kca5ggPAAg/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3ohryvw2Kca5ggPAAg/giphy.gif" alt="A Team GIF @Giphy" width="480" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is, of course, only an introduction to the concepts of WebSocket, but it highlights several fundamental elements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; WebSocket enables bi-directional and simultaneous multi-client communication.&lt;/li&gt;
&lt;li&gt; A WebSocket server runs in parallel with the application's Web server. The latter is responsible for distributing the application to the browser, which will then start it; but it is the former that is responsible for the transit of business data flows.&lt;/li&gt;
&lt;li&gt; Even though it seems to be a different protocol, WebSocket exploits the fundamentals of the Web, its underlying building blocks, and its robust protocols.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need to be a network expert to develop with WebSocket. You will use what you already know well from the Web, with its event model.&lt;/p&gt;

&lt;p&gt;It's up to you to find the right uses that suit your needs; to add data processing; to transit JSON or binary formats; to support authentication… Everything you already know how to do on the Web is applicable.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;and to avoid always doing the same thing ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;or &lt;code&gt;pnpm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>cloud</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Devops React : We can be Heroes, just for one app</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Wed, 01 Feb 2023 14:01:04 +0000</pubDate>
      <link>https://forem.com/m4dz/devops-react-we-can-be-heroes-just-for-one-app-36ab</link>
      <guid>https://forem.com/m4dz/devops-react-we-can-be-heroes-just-for-one-app-36ab</guid>
      <description>&lt;p&gt;In an increasingly advanced front-end ecosystem, with an ever-richer selection of frameworks and increasingly demanding tooling, one can sometimes find oneself in a not so comical situation: how to handle a reliable and effective deployment? A case study with &lt;strong&gt;Create React App&lt;/strong&gt;, let’s go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/3o6MbsFDtuAOwPdBxC/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3o6MbsFDtuAOwPdBxC/giphy.gif" alt="Simpsons GIF, Lisa telling Bart : Bart, with these powers, we can be superheroes. @Giphy" width="480" height="368"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Lexical Note&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This guide will take place in two environments: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Local&lt;/em&gt;: run these commands on your local development environment (likely: your laptop).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Server&lt;/em&gt;: on your hosting account. With &lt;strong&gt;alwaysdata&lt;/strong&gt;, you can connect over &lt;em&gt;SSH&lt;/em&gt; to run these commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an account on &lt;a href="https://www.alwaysdata.com"&gt;&lt;strong&gt;alwaysdata&lt;/strong&gt;&lt;/a&gt;, mentions of &lt;code&gt;&amp;lt;account&amp;gt;&lt;/code&gt; in paths/URLs/etc. refer to your account name (i.e. if your account name is &lt;code&gt;superman&lt;/code&gt;, replace &lt;code&gt;&amp;lt;account&amp;gt;&lt;/code&gt; with &lt;code&gt;superman&lt;/code&gt;).  &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;em&gt;Create React App&lt;/em&gt;, what’s that?
&lt;/h2&gt;

&lt;p&gt;Whether you missed the last few years of front-end development or you’re a bit lost in your first steps with React, a quick reminder: &lt;strong&gt;React&lt;/strong&gt; is a &lt;em&gt;JS/TypeScript&lt;/em&gt; framework that provides a development environment for your websites and applications. I want to emphasize the front-end aspect of this topic: even though it’s possible to use &lt;em&gt;React&lt;/em&gt; with server-side rendering, its primary use case is to generate front-end assets: &lt;em&gt;HTML&lt;/em&gt;, &lt;em&gt;CSS&lt;/em&gt;, and &lt;em&gt;JavaScript&lt;/em&gt;. Nothing more.&lt;/p&gt;

&lt;p&gt;Since a &lt;em&gt;React&lt;/em&gt; app is just a set of static files, there’s no need for a complex back-end architecture to serve your front end. No &lt;em&gt;Node.js&lt;/em&gt; server or anything. A simple HTTP web server is enough. As stated in the &lt;a href="https://create-react-app.dev/docs/deployment/"&gt;Create React App deployment documentation&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Set up your favorite HTTP server so that a visitor to your site is served &lt;code&gt;index.html&lt;/code&gt;, and requests to static paths like &lt;code&gt;/static/js/main.&amp;lt;hash&amp;gt;.js&lt;/code&gt; are served with the contents of the &lt;code&gt;/static/js/main.&amp;lt;hash&amp;gt;.js&lt;/code&gt; file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ll take this opportunity to mention &lt;em&gt;Create React App&lt;/em&gt;. If &lt;em&gt;React&lt;/em&gt; is a framework dedicated to designing Web applications, setting up all of its toolings can be complex. Tools that encapsulate all of this logic have appeared to make things easier for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development vs. Production
&lt;/h2&gt;

&lt;p&gt;Let’s start our tutorial by creating a new React-based application using &lt;em&gt;Create React App&lt;/em&gt;. In your local environment, run the command:&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;npx create-react-app my-app
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! Here is your first functional &lt;em&gt;React&lt;/em&gt; application (without having coded anything!). &lt;em&gt;Create React App&lt;/em&gt; has generated the basic files and installed the various modules necessary for the development and compilation of your project.&lt;/p&gt;

&lt;p&gt;Compilation? Yes! Your front-end application will not be served directly to your clients but will have to be transpiled into &lt;em&gt;JavaScript&lt;/em&gt;. &lt;em&gt;Create React App&lt;/em&gt; has equipped your project with two commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm start&lt;/code&gt; : this is your development mode command; it will launch a web server on your machine to allow you to work on your application comfortably (with hot-reloading, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt; : this is the command dedicated to &lt;em&gt;transpiling&lt;/em&gt;, the one that will produce the static assets that you will then need to deploy on the server side.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the server side now: in your &lt;em&gt;alwaysdata&lt;/em&gt; administration interface, create a new site of type &lt;strong&gt;Static files&lt;/strong&gt;, with the following information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;address : &lt;code&gt;&amp;lt;account&amp;gt;.alwaysdata.net/my-app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;root directory: /&lt;code&gt;www/my-app/build&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://blog.alwaysdata.com/wp-content/uploads/2023/02/en_create-static-files-1024x879.png"&gt;Creating the “Static Files” website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As this directory does not exist yet, create it from your local environment over &lt;em&gt;SSH&lt;/em&gt;:&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;ssh &amp;lt;account&amp;gt;@ssh-&amp;lt;account&amp;gt;.alwaysdata.net &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; www/my-app/build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Manual Deployment: The Simple and Quick Option
&lt;/h2&gt;

&lt;p&gt;Everything is ready on the server side to receive your &lt;em&gt;React&lt;/em&gt; site! Let’s move on to deployment.&lt;/p&gt;

&lt;p&gt;You first need to indicate the final &lt;em&gt;URL&lt;/em&gt; of your project in the &lt;code&gt;package.json&lt;/code&gt; file to allow the build task to generate the assets with the correct file paths. Add the following entry:&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;"homepage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;account&amp;gt;.alwaysdata.net/my-app"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still from your local environment, launch the &lt;em&gt;build&lt;/em&gt; task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a &lt;code&gt;build&lt;/code&gt; directory that contains your static assets. To deploy them, use rsync via &lt;em&gt;SSH&lt;/em&gt;:&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;rsync &lt;span class="nt"&gt;-rz&lt;/span&gt; build &amp;lt;account&amp;gt;@ssh-&amp;lt;account&amp;gt;.alwaysdata.net:www/my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can go to your site’s address and admire the first production release of your application!&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Deployment from Github: Be Professional
&lt;/h2&gt;

&lt;p&gt;This manual deployment works perfectly, but it puts a burden on your shoulders. Each time you want to deploy your project, you will have to launch the &lt;em&gt;build&lt;/em&gt; task and then deploy the files via &lt;em&gt;rsync/SSH&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s move on to the DevOps part by automating the process using &lt;em&gt;GitHub Webhooks&lt;/em&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/xT9DPCU60mRbtGw7Ys/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/xT9DPCU60mRbtGw7Ys/giphy.gif" alt="Sponge Bob Thumbs Up @Giphy" width="540" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Webhooks, the magic bond&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;With &lt;em&gt;Webhooks&lt;/em&gt;, &lt;em&gt;GitHub&lt;/em&gt; will be on duty to notify your site that a new version is available every time you push new commits to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;To do so, you will need to equip your &lt;em&gt;alwaysdata&lt;/em&gt; account with a webhook service that will execute the deployment task for you. The &lt;a href="https://github.com/adnanh/webhook/"&gt;adnanh/webhook&lt;/a&gt; project will do the trick. Connect to your account with &lt;em&gt;SSH&lt;/em&gt; and 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;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;webhook
&lt;span class="nv"&gt;$ &lt;/span&gt;wget &lt;span class="nt"&gt;-O-&lt;/span&gt; https://github.com/adnanh/webhook/releases/download/2.8.0/webhook-linux-amd64.tar.gz &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; &lt;span class="nt"&gt;--strip-components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-C&lt;/span&gt; webhook
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new site in your alwaysdata admin interface of type &lt;strong&gt;User Program&lt;/strong&gt; with the following information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;address: &lt;code&gt;&amp;lt;account&amp;gt;.alwaysdata.net/webhook&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;root directory: &lt;code&gt;webhook&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;command: &lt;code&gt;./webhook -port $PORT -hooks ./hooks.yaml -logfile webhook.log&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;remember to also check the &lt;em&gt;Force HTTPS usage&lt;/em&gt; and &lt;em&gt;Trim path&lt;/em&gt; checkboxes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FZbkY2nq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/02/en_create-user-program-965x1024.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FZbkY2nq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/02/en_create-user-program-965x1024.png" alt="Creating the “User program” site" width="880" height="934"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2JAbtLF_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/02/en_force-ssl-site-1024x186.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2JAbtLF_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/02/en_force-ssl-site-1024x186.png" alt="Forcing SSL/HTTPS" width="880" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ew0_w4Bc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/02/en_trim-path-site-1024x282.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ew0_w4Bc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/02/en_trim-path-site-1024x282.png" alt="Trim Path option" width="880" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;GitHub&lt;/em&gt;, The Central Perk
&lt;/h3&gt;

&lt;p&gt;On the &lt;em&gt;GitHub&lt;/em&gt; side now, we’re going to set up a new repository for our project. &lt;a href="https://github.com/new"&gt;Create it as usual&lt;/a&gt; by giving it the name of your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qd-EF7RG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/01/en_github-create-repo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qd-EF7RG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/01/en_github-create-repo.png" alt='Creating a "my-app" GitHub repository' width="725" height="845"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then go to the &lt;em&gt;Settings&lt;/em&gt; section to add a new &lt;em&gt;Webhook&lt;/em&gt; to your server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payload URL : &lt;code&gt;https://&amp;lt;account&amp;gt;.alwaysdata.net/webhook/hooks/deploy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;secret : &lt;em&gt;a random password&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;content-type : &lt;code&gt;application/json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oRPeVQTA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/01/en_github-add-webhook-1024x701.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oRPeVQTA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.alwaysdata.com/wp-content/uploads/2023/01/en_github-add-webhook-1024x701.png" alt="Adding a Webhook in the repository settings" width="880" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Call me WALL·E
&lt;/h3&gt;

&lt;p&gt;Our &lt;em&gt;GitHub&lt;/em&gt; repository is configured, it will now trigger requests to your webhook server for new events, such as &lt;code&gt;git push&lt;/code&gt;. All that’s left is to automate the deployment.&lt;/p&gt;

&lt;p&gt;Connect with &lt;em&gt;SSH&lt;/em&gt; and create the webhook configuration file by editing the &lt;code&gt;webhook/hooks.yaml&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="pi"&gt;-&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;deploy&lt;/span&gt;
  &lt;span class="na"&gt;execute-command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/&amp;lt;account&amp;gt;/webhook/deploy.sh&lt;/span&gt;
  &lt;span class="na"&gt;command-working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/&amp;lt;account&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;pass-arguments-to-command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payload&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;repository.name&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payload&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;repository.clone_url&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payload&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;head_commit.id&lt;/span&gt;
  &lt;span class="na"&gt;trigger-rule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;and&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&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;payload-hmac-sha1&lt;/span&gt;
          &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;github_webhook_secret&amp;gt;&lt;/span&gt;
          &lt;span class="na"&gt;parameter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;header&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;X-Hub-Signature&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&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;value&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refs/heads/main&lt;/span&gt;
          &lt;span class="na"&gt;parameter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payload&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;ref&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to replace the values of &lt;code&gt;&amp;lt;account&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;github_webhook_secret&amp;gt;&lt;/code&gt; in this configuration, and &lt;a href="https://admin.alwaysdata.com/site/"&gt;restart the webhook site&lt;/a&gt; in the administration interface once done.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;deploy&lt;/code&gt; &lt;em&gt;webhook&lt;/em&gt; defines the action to run when a &lt;em&gt;GitHub&lt;/em&gt; notification is received. It will call the script &lt;code&gt;webhook/deploy.sh&lt;/code&gt; with several parameters, but only when the event is a &lt;em&gt;push&lt;/em&gt; to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;All that remains is the deployment script itself, which will be executed by the &lt;em&gt;webhook&lt;/em&gt; server when a notification is received. It will be responsible of the following tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository in a temporary directory.&lt;/li&gt;
&lt;li&gt;Retrieve the latest version specified by &lt;em&gt;GitHub&lt;/em&gt; in its payload.&lt;/li&gt;
&lt;li&gt;Perform the &lt;em&gt;build&lt;/em&gt; task.&lt;/li&gt;
&lt;li&gt;Clean up after itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Still, using &lt;em&gt;SSH&lt;/em&gt;, add the script &lt;code&gt;webhook/deploy.sh&lt;/code&gt; to your server account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;COMMIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;

&lt;span class="nv"&gt;CLONE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DEST_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/www/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEST_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

git clone &lt;span class="nt"&gt;--bare&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLONE_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
git &lt;span class="nt"&gt;--work-tree&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEST_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--git-dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLONE_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; checkout &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMMIT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
npm &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEST_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nb"&gt;install
&lt;/span&gt;npm &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEST_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; run build
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLONE_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember to make the file executable with the command &lt;code&gt;chmod +x webhook/deploy.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You’re all set! Now just let everything run its course. Go back to your local environment and add the &lt;em&gt;GitHub&lt;/em&gt; repository to your project before pushing the &lt;code&gt;main&lt;/code&gt; branch:&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;git remote add origin https://github.com/&amp;lt;gh_name&amp;gt;/my-app.git
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This push will trigger the notification through the &lt;em&gt;Webhooks&lt;/em&gt;. Once notified, the &lt;em&gt;alwaysdata&lt;/em&gt; side server will run the deployment script, and your site will be updated. You can check the progress by consulting the &lt;code&gt;webhook/webhook.log&lt;/code&gt; file on the server side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/6HOkD1pSCne4E/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/6HOkD1pSCne4E/giphy.gif" alt="Happy End Rant Gif @Giphy" width="400" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make changes to your site, push to &lt;em&gt;GitHub&lt;/em&gt;, reload your site at &lt;code&gt;http://&amp;lt;account&amp;gt;.alwaysdata.net/my-app&lt;/code&gt;, and admire! You no longer have anything to do to manage your deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The best PaaS is the one that suits you
&lt;/h2&gt;

&lt;p&gt;You don’t need solutions like &lt;em&gt;Netlify&lt;/em&gt;, &lt;em&gt;Heroku&lt;/em&gt;, or others to manage the &lt;strong&gt;DevOps&lt;/strong&gt; part of your deployments. Even though these platforms offer many practical tools to make your deployments easier (like atomic deployments, etc.), they’re not mandatory.&lt;/p&gt;

&lt;p&gt;We have set up a simple solution here to automate the deployment of static front-end applications on your &lt;em&gt;alwaysdata&lt;/em&gt; account without using third-party tools. Our deployment tool is independent of the application itself, so you can now use it to deploy other projects: in another &lt;em&gt;GitHub&lt;/em&gt; repository, configure a webhook pointing to the same endpoint with the same &lt;em&gt;secret&lt;/em&gt;, and watch your deployments happening on their own in your &lt;code&gt;www&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;It is now up to you to adapt the solution to more complex cases, integrating migration tasks, updating databases, and atomic builds to prevent service interruptions during redeployment… the list is far from its end. Happy deployment 🤓 !&lt;/p&gt;

</description>
      <category>react</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A story of how we migrated to pnpm</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Wed, 26 Jan 2022 09:13:13 +0000</pubDate>
      <link>https://forem.com/divriots/a-story-of-how-we-migrated-to-pnpm-59b3</link>
      <guid>https://forem.com/divriots/a-story-of-how-we-migrated-to-pnpm-59b3</guid>
      <description>&lt;p&gt;It all started with me trying to improve our Continuous Integration pipeline. I'm a strong believer in having proper CI - the threshold for how much to invest in unit &amp;amp; integration tests is always tricky to set, but to me the bare minimum should be to have linting and type checking run on every commit.&lt;/p&gt;

&lt;p&gt;Now, having that bare minimum is great, but it also needs to be as fast as possible. When you want commits &amp;amp; reviews to be fast, CI cannot be the one thing holding you back.&lt;/p&gt;

&lt;p&gt;Yet... This is what we would see in the best case scenario on that bare minimum linting &amp;amp; type checking job:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzf7b10fa7fbtof5uqf2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgzf7b10fa7fbtof5uqf2.jpg" alt="yarn 2 install with cache taking 1 minute and 11 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1 minute and 11 seconds just to install dependencies. Obviously, the job has to do more afterwards, and that's where I'd prefer it to spend time.&lt;/p&gt;

&lt;p&gt;But wait, there's more. This was the best case scenario. You may know that package managers have caches, and a known trick to speed installs is to save that cache after CI runs, so it can be reused for subsequent runs. An easy way to do that nowadays is to use &lt;a href="https://github.com/actions/setup-node#caching-packages-dependencies" rel="noopener noreferrer"&gt;actions/node-setup's caching capabilities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However the cache cannot always be used. As soon as the lock file changes, typically when adding dependencies, the cache isn't reused &lt;a href="https://github.com/actions/cache" rel="noopener noreferrer"&gt;because the cache's hash is usually computed based on the lock file&lt;/a&gt;. We would then get:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1yka18150aey2uvu0vb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1yka18150aey2uvu0vb.jpg" alt="yarn 2 install without cache taking 6 minutes and 31 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;6 minutes and 31 seconds 🐌.&lt;br&gt;
That's when we really thought we needed to do something.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where we stood with Yarn
&lt;/h2&gt;

&lt;p&gt;We have been using Yarn 2 for quite some time, having originally switched to it for its native workspace support which is great for monorepos as we happen to have one. Because we use a lot of different dev tools (in no particular order - &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, &lt;a href="https://vitepress.vuejs.org/" rel="noopener noreferrer"&gt;Vitepress&lt;/a&gt;, &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt;, &lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;, &lt;a href="https://www.11ty.dev/" rel="noopener noreferrer"&gt;Eleventy&lt;/a&gt;, &lt;a href="https://firebase.google.com/" rel="noopener noreferrer"&gt;Firebase tools&lt;/a&gt;, &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind&lt;/a&gt;...) and many more actual dependencies. It's easy to understand how many dependencies we're bound to have when you see all the frameworks we support, whether &lt;a href="https://webcomponents.dev/docs/technologies" rel="noopener noreferrer"&gt;on WebComponents.dev&lt;/a&gt; or &lt;a href="https://backlight.dev/docs/technologies" rel="noopener noreferrer"&gt;on Backlight&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You may know Yarn 2 for introducing the &lt;a href="https://yarnpkg.com/features/pnp" rel="noopener noreferrer"&gt;Plug'n'Play linker&lt;/a&gt;. To make it short, it completely forfeits the idea of the &lt;code&gt;node_modules&lt;/code&gt; resolution mechanism and tells Node to depend on Yarn for dependency resolution.&lt;br&gt;
It's a really interesting idea but dropping &lt;code&gt;node_modules&lt;/code&gt; is a compatibility challenge which kept us away from trying it. We stuck and are sticking to &lt;code&gt;node_modules&lt;/code&gt; for now.&lt;/p&gt;

&lt;p&gt;Anyway, because &lt;a href="https://dev.to/arcanis/yarn-3-0-performances-esbuild-better-patches-e07"&gt;Yarn 3 had been released for a few months with performances improvements&lt;/a&gt;, we decided to give it try to see if that would speedup our builds.&lt;/p&gt;
&lt;h2&gt;
  
  
  Trying yarn 3
&lt;/h2&gt;

&lt;p&gt;Upgrading to Yarn 3 is fairly simple:&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="o"&gt;&amp;gt;&lt;/span&gt; yarn &lt;span class="nb"&gt;set &lt;/span&gt;version berry

➤ YN0000: Retrieving https://repo.yarnpkg.com/3.1.1/packages/yarnpkg-cli/bin/yarn.js
➤ YN0000: Saving the new release &lt;span class="k"&gt;in&lt;/span&gt; .yarn/releases/yarn-3.1.1.cjs
➤ YN0000: Done &lt;span class="k"&gt;in &lt;/span&gt;0s 758ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we go, we were upgraded to Yarn 3.&lt;/p&gt;

&lt;p&gt;I'll spare you another pair of screenshots, but that got us down a bit, to 4 minutes 50 seconds without cache and 57 seconds with cache.&lt;/p&gt;

&lt;p&gt;I'm sparing you the screenshots for a good reason - I did mention we have been using Yarn 2 in that monorepo for a while. We've also been adding so many packages in different workspaces that we ended up with a lot of duplicated dependencies, ie with multiple versions of the same packages.&lt;/p&gt;

&lt;p&gt;So just for the sake of the comparison and because our original point was to speedup install times, I went ahead and completely removed the &lt;code&gt;yarn.lock&lt;/code&gt; file and tested again.&lt;/p&gt;

&lt;p&gt;With cache, down to 50 seconds:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsaay7z3cgaof6k6sdxp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsaay7z3cgaof6k6sdxp.jpg" alt="yarn 3 install with cache taking 50 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And without cache, we got down to 4 minutes and 1 second:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjalqb19f0yitcpms3xml.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjalqb19f0yitcpms3xml.jpg" alt="yarn 3 install without cache taking 4 minutes and 1 second"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's fair to say we've sped up our builds quite a lot already, but we wanted to go further yet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Now, that's where we decided to try out &lt;em&gt;pnpm&lt;/em&gt;. But &lt;a href="https://twitter.com/larixer/status/1483513893825781766" rel="noopener noreferrer"&gt;as pointed out by @larixer&lt;/a&gt;, there are also options you can set to speedup Yarn. We tried them out to make it a fairer comparison.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/larixer"&gt;@larixer&lt;/a&gt; mentions the 3 following options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nmMode: hardlinks-global
enableGlobalCache: true
compressionLevel: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And they do help a lot, especially without cache where we go down to 1 minute 10 seconds:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6etd9qayk5g0f3ndq4m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6etd9qayk5g0f3ndq4m.jpg" alt="yarn 3 optimized install without cache taking 1 minute and 1O seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is also slightly faster with a cache, yielding 45 seconds:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18a5b4vgp0rc8krhqzdx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18a5b4vgp0rc8krhqzdx.jpg" alt="yarn 3 install with cache taking 45 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So if you are running Yarn, do considering trying them out! Chances are they will greatly improve your install times.&lt;/p&gt;

&lt;p&gt;Anyhow, let's jump into pnpm!&lt;/p&gt;
&lt;h2&gt;
  
  
  Enter pnpm
&lt;/h2&gt;

&lt;p&gt;pnpm stands for &lt;strong&gt;Performant NPM&lt;/strong&gt;. Its adoption has been really steady as its close to the 15k stars at the moment on Github. It also comes with out of the box support for workspaces, making it easier for us to consider.&lt;/p&gt;

&lt;p&gt;As its name indicates, it &lt;a href="https://pnpm.io/motivation" rel="noopener noreferrer"&gt;really emphasizes performances&lt;/a&gt;, both regarding disk space and installation times. In all the figures provided, whether &lt;a href="https://pnpm.io/benchmarks" rel="noopener noreferrer"&gt;from pnpm&lt;/a&gt; or &lt;a href="https://p.datadoghq.eu/sb/d2wdprp9uki7gfks-c562c42f4dfd0ade4885690fa719c818?theme=dark&amp;amp;tpl_var_npm=%2A&amp;amp;tpl_var_yarn-classic=%2A&amp;amp;tpl_var_yarn-modern=%2A&amp;amp;tpl_var_yarn-nm=%2A&amp;amp;tpl_var_yarn-pnpm=no&amp;amp;tv_mode=false" rel="noopener noreferrer"&gt;from Yarn&lt;/a&gt; you can see that pnpm really comes out faster most of the time.&lt;/p&gt;

&lt;p&gt;There seems to be two main reasons for it.&lt;/p&gt;

&lt;p&gt;One, being performance-oriented, its implementation targets speed. You may have seen when installing with &lt;em&gt;yarn&lt;/em&gt; or &lt;em&gt;npm&lt;/em&gt; timings for each of the resolution/fetch/link steps. It appears that &lt;em&gt;pnpm&lt;/em&gt; is not doing those steps sequentially globally, but sequentially for each package in parallel which explains why it's so efficient.&lt;/p&gt;

&lt;p&gt;The other reason is the way it deals with the &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;
&lt;h3&gt;
  
  
  Centralized addressable cache
&lt;/h3&gt;

&lt;p&gt;pnpm calls it a &lt;em&gt;content addressable filestore&lt;/em&gt;, and we know other package managers like &lt;em&gt;yarn&lt;/em&gt; or &lt;em&gt;npm&lt;/em&gt; have caches as well, which allow you not to have to re-download.&lt;/p&gt;

&lt;p&gt;The difference with pnpm's is that this cache is also referenced by your node_modules files, which are effectively hard-links to that cache. A hard-link means your OS will report those files as being actual files - but they're not. So the actual disk usage occurs in pnpm's cache, not in your node_modules folder. You save space, and installation time, because there's way less IO involved in setting up that infamous node_modules folder! 🪄&lt;/p&gt;
&lt;h3&gt;
  
  
  Non-flat node_modules
&lt;/h3&gt;

&lt;p&gt;What's also interesting is the way the node_modules is organized with pnpm. npm and yarn (when using the node_modules linker) tend to do hoisting to save space as they're not using links. Hoisting is the act of installing a dependency in a parent directory rather than where it is depended on. So if you have a dependency that can be resolved to the same version pulled by two other packages, they'll try to hoist that dependency to avoid storing that same dependency twice in your node_modules.&lt;/p&gt;

&lt;p&gt;The behavior of pnpm is different, somewhat more consistent. It's always setting up the node_modules structure the same way. First, it's non-flat. So running &lt;code&gt;pnpm install vite&lt;/code&gt; in an empty folder will result in the following node_modules:&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="o"&gt;&amp;gt;&lt;/span&gt; tree node_modules &lt;span class="nt"&gt;-L&lt;/span&gt; 1
node_modules
└── vite -&amp;gt; .pnpm/vite@2.7.10/node_modules/vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So our node_modules only contains vite and not all of its dependencies. This may seem unusual, but this avoids &lt;strong&gt;phantom dependencies&lt;/strong&gt;. Phantom dependencies are dependencies that you end up being able to use without explicitly depending on them. This is a rather dangerous practice, because you do not control those - you may update the original dependency, just upgrading it to a new patch, but its dependencies may have been upgraded to major versions breaking your own code!&lt;/p&gt;

&lt;p&gt;In our previous example, my source code won't be able to require any other dependency but &lt;code&gt;vite&lt;/code&gt; as it's the only one which was effectively installed to the top of my node_modules.&lt;/p&gt;

&lt;p&gt;Now we can see that this folder is actually linking to another folder in &lt;code&gt;node_modules​/.pnpm&lt;/code&gt;: this is pnpm's &lt;em&gt;Virtual Store&lt;/em&gt; where you will find all the packages installed in your project.&lt;/p&gt;

&lt;p&gt;If we take a peek at this folder:&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="o"&gt;&amp;gt;&lt;/span&gt; tree node_modules/.pnpm/vite@2.7.10 &lt;span class="nt"&gt;-L&lt;/span&gt; 2
node_modules/.pnpm/vite@2.7.10
└── node_modules
    ├── esbuild -&amp;gt; ../../esbuild@0.13.15/node_modules/esbuild
    ├── postcss -&amp;gt; ../../postcss@8.4.5/node_modules/postcss
    ├── resolve -&amp;gt; ../../resolve@1.21.0/node_modules/resolve
    ├── rollup -&amp;gt; ../../rollup@2.63.0/node_modules/rollup
    └── vite
        ├── bin
        ├── CHANGELOG.md
        ├── client.d.ts
        ├── dist
        ├── LICENSE.md
        ├── node_modules
        ├── package.json
        ├── README.md
        ├── src
        └── types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, vite itself and its dependencies were installed to &lt;code&gt;node_modules/​.pnpm/​vite@2.7.10/​node_modules&lt;/code&gt;.&lt;br&gt;
The magic that makes it all work is that Node, when resolving packages, considers the target of the symlink instead of using the symlink's path itself. So when I do &lt;code&gt;require('vite')&lt;/code&gt; from an &lt;code&gt;src/​index.js&lt;/code&gt; file, Node finds the &lt;code&gt;node_modules/​vite&lt;/code&gt; file by iterating on parent directories looking for a &lt;code&gt;node_modules&lt;/code&gt; folder containing &lt;code&gt;vite&lt;/code&gt; but actually resolves it to the source of the symlink:&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="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"console.log(require.resolve('vite'))
/tmp/foobar/node_modules/.pnpm/vite@2.7.10/node_modules/vite/dist/node/index.js
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means that any further package resolutions needed will effectively be done from this folder - so if that &lt;code&gt;/tmp/​foobar/​node_modules/​.pnpm/​vite@2.7.10/​node_modules/​vite/​dist/​node/​index.js&lt;/code&gt; file requires &lt;code&gt;esbuild&lt;/code&gt; it will find it in &lt;code&gt;node_modules/​.pnpm/​vite@2.7.10/​node_modules/​esbuild&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;This is also why some dependencies don't play nice with pnpm: because they don't resolve symlink targets. But we'll get to that later.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: peer dependencies have a special handling in pnpm, if you're interested about it I suggest &lt;a href="https://pnpm.io/how-peers-are-resolved" rel="noopener noreferrer"&gt;this read&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have a rough understanding of how pnpm works, let's try using it! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating to pnpm
&lt;/h2&gt;

&lt;h3&gt;
  
  
  pnpm import
&lt;/h3&gt;

&lt;p&gt;pnpm comes with a command to import yarn's locked dependencies:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pnpm.io/cli/import" rel="noopener noreferrer"&gt;https://pnpm.io/cli/import&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's just one gotcha when you're using it in a monorepo: &lt;strong&gt;the workspaces have to be declared&lt;/strong&gt; in your &lt;a href="https://pnpm.io/pnpm-workspace_yaml" rel="noopener noreferrer"&gt;pnpm-workspace.yaml&lt;/a&gt; first. If you don't, then at best &lt;code&gt;pnpm import&lt;/code&gt; will only import the dependencies declared in your root file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependencies which have undeclared dependencies
&lt;/h3&gt;

&lt;p&gt;Another kind of problem we ran into is some dependencies having undeclared dependencies. When using &lt;em&gt;yarn&lt;/em&gt; it was not a problem because those undeclared dependencies are sometimes very used. For example, after the migration we realized &lt;code&gt;mdjs-core&lt;/code&gt; &lt;a href="https://github.com/modernweb-dev/rocket/pull/278" rel="noopener noreferrer"&gt;had not declared its dependency on &lt;code&gt;slash&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A simple way to fix this is again through the &lt;a href="https://pnpm.io/pnpmfile#hooksreadpackagepkg-context-pkg--promisepkg" rel="noopener noreferrer"&gt;readPackage hook&lt;/a&gt; we mentioned in the previous section. There, you can simply declare the dependency explicitly for &lt;code&gt;mdjs-core&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@mdjs/core&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;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;slash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;^3.0.0&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;h3&gt;
  
  
  shamefully-hoist when tools don't play along
&lt;/h3&gt;

&lt;p&gt;We talked about the non-flat node-modules earlier. This structure is unfortunately not compatible with every Node tool.&lt;/p&gt;

&lt;p&gt;An example of this is Astro which &lt;a href="https://github.com/withastro/astro/blob/74372a7d269882fe8bbcbf02a85a79296d58668f/examples/minimal/.npmrc#L2" rel="noopener noreferrer"&gt;at the moment recommends using &lt;code&gt;shamefully-hoist&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
Kind of a funny name, meant to dissuade you from using it :-)&lt;/p&gt;

&lt;p&gt;As the name implies, this one will hoist all your dependencies in your root node_modules, fixing any incompatibility you may have with dev tools not playing along with the nested node_modules. This typically happens because they don't resolve symlinks to their target.&lt;/p&gt;

&lt;p&gt;At the time of this writing, Astro requiring it, if you're not using it will fail at loading its dependencies, with a&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error: The following dependencies are imported but could not be resolved:

  react &lt;span class="o"&gt;(&lt;/span&gt;imported by /not-relevant/testimonial-card/src/index.tsx&lt;span class="o"&gt;)&lt;/span&gt;
  svelte/internal &lt;span class="o"&gt;(&lt;/span&gt;imported by /not-relevant/double-cta/dist/DoubleCta.svelte.js&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of going this way, I preferred adding manually the missing dependencies to the workspace using Astro. It's a hack, but one I prefer living with than using &lt;code&gt;shamefully-hoist&lt;/code&gt; globally as it would cancel out the advantages of the non-flat node-modules.&lt;/p&gt;

&lt;h3&gt;
  
  
  How fast is it
&lt;/h3&gt;

&lt;p&gt;I know, that was the whole point of us trying out pnpm - let's see how fast it is!&lt;/p&gt;

&lt;p&gt;So, when the cache is hit, we get down to 24 seconds:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2rxad0aml5srrr0b7wuk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2rxad0aml5srrr0b7wuk.jpg" alt="pnpm install with cache taking 24 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And when the cache cannot be used, we get down to a whopping 53 seconds:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xz7kp490dp1vffyxa8w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xz7kp490dp1vffyxa8w.jpg" alt="pnpm install without cache taking 53 seconds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Summarizing the results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Without cache&lt;/th&gt;
&lt;th&gt;With cache&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;yarn 2 (without dedupe)&lt;/td&gt;
&lt;td&gt;6min 31s&lt;/td&gt;
&lt;td&gt;1min 11s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yarn 3 (without dedupe)&lt;/td&gt;
&lt;td&gt;4min 50s&lt;/td&gt;
&lt;td&gt;57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yarn 3&lt;/td&gt;
&lt;td&gt;4min 1s&lt;/td&gt;
&lt;td&gt;50s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yarn 3 (optimized)&lt;/td&gt;
&lt;td&gt;1min 10&lt;/td&gt;
&lt;td&gt;45s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pnpm&lt;/td&gt;
&lt;td&gt;58s&lt;/td&gt;
&lt;td&gt;24s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Honestly, I'm particularly impressed about the results when there's no cache.&lt;br&gt;
I would have expected network to be the bottleneck for both yarn or pnpm in that case, but somehow pnpm still really shines there, while also being faster (at least for us) when the cache is used too!&lt;/p&gt;

&lt;p&gt;Now I'm happy - the CI is snappy, at least way snappier than it was, and our local install times also benefitted from it. Thank you pnpm!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tooling</category>
      <category>npm</category>
    </item>
    <item>
      <title>Vite in the browser</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Wed, 19 Jan 2022 17:39:06 +0000</pubDate>
      <link>https://forem.com/divriots/vite-in-the-browser-354p</link>
      <guid>https://forem.com/divriots/vite-in-the-browser-354p</guid>
      <description>&lt;p&gt;
  TL;DR
  &lt;p&gt;We made &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; - a patched version of &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; running in the browser with Workers.&lt;/p&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works - in a nutshell
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API"&gt;Service Worker&lt;/a&gt;: replaces Vite's HTTP server. Capturing the HTTP calls of an embedded iframe from example.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers"&gt;Web Worker&lt;/a&gt;: Run &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; to process off the main thread.&lt;/li&gt;
&lt;li&gt;Calls to the file system are replaced by an in-memory file system.&lt;/li&gt;
&lt;li&gt;Import of files with special extensions (&lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt;, &lt;code&gt;.scss&lt;/code&gt;...) are transformed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  No real File System
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; does a lot with files. The files of the project but also config files, watchers, and globs. These are difficult to implement in the browser with a shimmed in-memory FS. We removed watchers, globs, and config file calls to limit the complexity and the surface API.&lt;/p&gt;

&lt;p&gt;The project files stay in the in-memory FS that &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; and vite plugins can access normally.&lt;/p&gt;

&lt;h3&gt;
  
  
  No "node_modules"
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; relies on the presence of &lt;code&gt;node_modules&lt;/code&gt; to resolve dependencies. And it bundles them in a &lt;a href="https://vitejs.dev/guide/dep-pre-bundling.html"&gt;Dependencing Pre-Bundling&lt;/a&gt; optimization at startup.&lt;/p&gt;

&lt;p&gt;We didn't want to run a &lt;code&gt;node_modules&lt;/code&gt; folder in the browser's memory because we think it's just too much data to download and store into the browser's memory. So we carefully stripped out node resolvers and &lt;a href="https://vitejs.dev/guide/dep-pre-bundling.html"&gt;Dependencing Pre-Bundling&lt;/a&gt; from &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Users of &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; have to create a &lt;a href="https://vitejs.dev/guide/api-plugin.html"&gt;Vite plugin&lt;/a&gt; to resolve bare module imports.&lt;/p&gt;

&lt;p&gt;Our products: &lt;a href="https://backlight.dev"&gt;Backlight.dev&lt;/a&gt;, &lt;a href="https://components.studio"&gt;Components.studio&lt;/a&gt; and &lt;a href="https://webcomponents.dev"&gt;WebComponents.dev&lt;/a&gt;, are running a server-side bundler optimizer for the past 2 years now. We created a Vite plugin for &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; to resolve node dependencies automatically. As of the date of this post, this server-side bundler is not open-sourced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regex "lookbehind"
&lt;/h3&gt;

&lt;p&gt;Some regexs in &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; are using &lt;a href="https://www.regular-expressions.info/lookaround.html"&gt;lookbehind&lt;/a&gt;. This works great locally when executed by &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt;, but &lt;a href="https://caniuse.com/js-regexp-lookbehind"&gt;it's not supported in Safari&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So we rewrote the regexs for more browser compatibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot Module Reload (HMR)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; uses &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;WebSockets&lt;/a&gt; to communicate code changes from the server (node) to the client (browser).&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;, the server is the ServiceWorker + Vite worker and the client is the iframe. So we changed the communication from &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;WebSockets&lt;/a&gt; to a post message to the iframe.&lt;/p&gt;

&lt;p&gt;For this, the client side code of Vite in iframe has been replaced by &lt;a href="https://github.com/divriots/browser-vite/blob/browser-vite/packages/vite/src/client/browser.ts"&gt;a special browser version&lt;/a&gt; handling messages outside of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;WebSockets&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use it
&lt;/h2&gt;

&lt;p&gt;As of the time of this writing, it's not a plug and play process. There is a lot to figure out by reading Vite's internal processing in order to use &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note: This post may become obsolete over time, so make sure you check&lt;br&gt;
&lt;a href="https://github.com/divriots/browser-vite/blob/browser-vite/README.md#usage"&gt;browser-vite's README&lt;/a&gt; for always up to date information on browser-vite's usage.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Install the &lt;a href="https://npmjs.org/browser-vite"&gt;browser-vite npm package&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; browser-vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; vite@npm:browser-vite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To channel "vite" imports to "browser-vite".&lt;/p&gt;

&lt;h3&gt;
  
  
  iframe - window to browser-vite
&lt;/h3&gt;

&lt;p&gt;You need an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"&gt;iframe&lt;/a&gt; that will show the pages served internally by &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Worker - the in-browser web server
&lt;/h3&gt;

&lt;p&gt;The Service Worker will capture certain URLs requests coming from the iframe.&lt;/p&gt;

&lt;p&gt;Here is an example using &lt;a href="https://developers.google.com/web/tools/workbox"&gt;workbox&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;workbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="sr"&gt;/^https&lt;/span&gt;&lt;span class="se"&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;HOST/&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="err"&gt;\&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="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workbox-routing/types/RouteHandler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;RouteHandlerCallbackContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;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;req&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="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&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;pathname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="c1"&gt;// send the request to vite worker&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;postToViteWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathname&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="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mostly posting a message to the "Vite Worker" using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage"&gt;postMessage&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API"&gt;broadcast-channel&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vite Worker - processing request
&lt;/h3&gt;

&lt;p&gt;The Vite Worker is a Web Worker that will process requests captured by the Service Worker.&lt;/p&gt;

&lt;p&gt;Example of creating a Vite Server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;transformWithEsbuild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ModuleGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createPluginContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;createDevHtmlTransformFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;resolveConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;generateCodeFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ssrTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ssrLoadModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;ViteDevServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;PluginOption&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;browser-vite&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createServer&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;config&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;resolveConfig&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="c1"&gt;// virtual plugin to provide vite client/env special entries (see below)&lt;/span&gt;
        &lt;span class="nx"&gt;viteClientPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// virtual plugin to resolve NPM dependencies, e.g. using unpkg, skypack or another provider (browser-vite only handles project files)&lt;/span&gt;
        &lt;span class="nx"&gt;nodeResolvePlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// add vite plugins you need here (e.g. vue, react, astro ...)&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;base&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="c1"&gt;// as hooked in service worker&lt;/span&gt;
      &lt;span class="c1"&gt;// not really used, but needs to be defined to enable dep optimizations&lt;/span&gt;
      &lt;span class="na"&gt;cacheDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VFS_ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;// any other configuration (e.g. resolve alias)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serve&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;plugins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&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;pluginContainer&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;createPluginContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&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;moduleGraph&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;ModuleGraph&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;pluginContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolveId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;watcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;what&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&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;watcher&lt;/span&gt;&lt;span class="p"&gt;;&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="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;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ViteDevServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pluginContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;moduleGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;transformWithEsbuild&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="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;return&lt;/span&gt; &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&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="nx"&gt;ssrTransform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;printUrls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;_globImporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;send&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="c1"&gt;// send HMR data to vite client in iframe however you want (post/broadcast-channel ...)&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;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
      &lt;span class="nx"&gt;off&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;watcher&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;ssrLoadModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ssrLoadModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loadModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;ssrFixStacktrace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="na"&gt;_optimizeDepsMetadata&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="na"&gt;_isRunningOptimizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;_ssrExternals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;_restartPromise&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="na"&gt;_forceOptimizeOnRestart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;_pendingRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transformIndexHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createDevHtmlTransformFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// apply server configuration hooks from plugins&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postHooks&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="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="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;plugin&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;postHooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&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="c1"&gt;// run post config hooks&lt;/span&gt;
  &lt;span class="c1"&gt;// This is applied before the html middleware so that user middleware can&lt;/span&gt;
  &lt;span class="c1"&gt;// serve custom content instead of index.html.&lt;/span&gt;
  &lt;span class="nx"&gt;postHooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;fn&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;pluginContainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buildStart&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;runOptimize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&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;server&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;Pseudo code to process requests via &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;transformRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isCSSRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;isDirectCSSRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;injectQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;removeImportQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;unwrapId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handleFileAddUnlink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;handleHMRUpdate&lt;/span&gt;&lt;span class="p"&gt;,&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;vite/dist/browser&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;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accept&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;accept&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// strip ?import&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;removeImportQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Strip valid id prefix. This is prepended to resolved Ids that are&lt;/span&gt;
  &lt;span class="c1"&gt;// not valid browser import specifiers by the importAnalysis plugin.&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unwrapId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// for CSS, we need to differentiate between normal CSS requests and&lt;/span&gt;
  &lt;span class="c1"&gt;// imports&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;isCSSRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;accept&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/css&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;injectQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;direct&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;let&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&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;code&lt;/span&gt;&lt;span class="p"&gt;;&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;url&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="mi"&gt;1&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;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;code&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;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transformIndexHtml&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&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;else&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;ret&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;transformRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Return code reponse&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Return error 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;Check &lt;a href="https://github.com/vitejs/vite/tree/main/packages/vite/src/node/server/middlewares"&gt;Vite's internal middlewares&lt;/a&gt; for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it compare to Stackblitz WebContainers
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"&lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt;:&lt;br&gt;
Run Node.js natively in your browser"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://stackblitz.com/"&gt;Stackblitz&lt;/a&gt;'s &lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; can also run &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; in the browser. You can elegantly go to &lt;a href="https://vite.new"&gt;vite.new&lt;/a&gt; to have a working environment.&lt;/p&gt;

&lt;p&gt;We are not experts in WebContainers but, in a nutshell, where &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; shims the FS and the HTTPS server at the Vite level, WebContainers shims the FS and a lot of other things at the &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt; level, and Vite runs on it with a few additional changes.&lt;/p&gt;

&lt;p&gt;It goes as far as storing a &lt;code&gt;node_modules&lt;/code&gt; in the WebContainer, in the browser. But it doesn't run &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; directly because it would take too much space (I guess). They aliased these commands to &lt;a href="https://developer.stackblitz.com/docs/platform/turbo"&gt;Turbo&lt;/a&gt; - their package manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; can run other frameworks too, like &lt;a href="https://blog.stackblitz.com/posts/remix-runs-on-webcontainers/"&gt;Remix&lt;/a&gt;, &lt;a href="https://blog.stackblitz.com/posts/sveltekit-supported-in-webcontainers/"&gt;SvelteKit&lt;/a&gt; or &lt;a href="https://blog.stackblitz.com/posts/astro-support/"&gt;Astro&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's magical ✨ It's mind-blowing 🤯 We have massive respect for what the Stackblitz team has built here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One downside of &lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; is that it can &lt;a href="https://developer.stackblitz.com/docs/platform/browser-support"&gt;only run on Chrome today&lt;/a&gt; but will probably &lt;a href="https://developer.stackblitz.com/docs/platform/browser-support#testing-on-firefox"&gt;run on Firefox soon&lt;/a&gt;. &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; works on Chrome, Firefox and Safari today.&lt;/p&gt;

&lt;p&gt;In a nutshell, &lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/"&gt;WebContainers&lt;/a&gt; operates at a lower level of abstraction to run &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; in the browser. &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; operates at a higher level of abstraction, very close to &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt; itself.&lt;/p&gt;

&lt;p&gt;Metaphorically, for the retro-gamers out there, &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; is a little bit like &lt;a href="https://en.wikipedia.org/wiki/UltraHLE"&gt;UltraHLE&lt;/a&gt; 🕹️😊&lt;/p&gt;

&lt;p&gt;(*) &lt;a href="https://emulation.gametechwiki.com/index.php/High/Low_level_emulation"&gt;gametechwiki.com: High/Low level emulation&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; is at the heart of our solutions. We are progressively rolling it out to all our products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://backlight.dev"&gt;Backlight.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://components.studio"&gt;Components.studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webcomponents.dev"&gt;WebComponents.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://replic.dev"&gt;Replic.dev&lt;/a&gt; (New app coming up very soon!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Going forward, we will continue to invest in &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt; and report back upstream. Last month, we also announced that &lt;a href="https://dev.to/blog/supporting-vitejs"&gt;we sponsored Vite via Evan You and Patak&lt;/a&gt; to support this wonderful project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Want to know more?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Repository: &lt;a href="https://github.com/divriots/browser-vite"&gt;browser-vite&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Join our &lt;a href="https://discord.gg/XkQxSU9"&gt;Discord server&lt;/a&gt;, we have a #browser-vite channel going on 🤗&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tooling</category>
      <category>vite</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How ESLint Can Enforce Your Design System Best Practices</title>
      <dc:creator>🎙 m4dz</dc:creator>
      <pubDate>Wed, 19 Jan 2022 17:34:31 +0000</pubDate>
      <link>https://forem.com/divriots/how-eslint-can-enforce-your-design-system-best-practices-37ad</link>
      <guid>https://forem.com/divriots/how-eslint-can-enforce-your-design-system-best-practices-37ad</guid>
      <description>&lt;p&gt;If you're creating a design system component library for your company or the open-source community, there's a good chance that you have strong opinions on how end-users should consume your design system.&lt;/p&gt;

&lt;p&gt;To ensure that your design system is used in its intended way, and to reduce the number of possible bugs, you might want your users to adhere to your best practices. The following are two examples of possible best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoiding inline styles in your elements&lt;/li&gt;
&lt;li&gt;Ensuring that tooltips don't contain interactive content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're the only person designing, developing, and consuming your design system, then you can sleep comfortably knowing that your design system is being used exactly as intended.&lt;/p&gt;

&lt;p&gt;Chances are you're not the only person developing the design system and you'll certainly not be present when someone consumes it. How can you feel sure that everyone abides by your design system's best practices? You could cross your fingers and trust that your end-users read the documentation, heed your warnings, and never fail to abide by your rules.&lt;/p&gt;

&lt;p&gt;Unfortunately, this is often not the case and it's very easy to miss warnings or misunderstand how to properly use a tool. I've been there!&lt;/p&gt;

&lt;p&gt;Fortunately, a great way to encourage your consumers to follow your best practices is through the use of &lt;a href="https://eslint.org/"&gt;ESLint&lt;/a&gt;, a static analysis tool to find problems in your code.&lt;/p&gt;

&lt;p&gt;By default, ESLint ships with a handful of general best practices, called &lt;em&gt;rules&lt;/em&gt; and will display red squigglys in your IDE if the rules have been violated. Some of these rules include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No duplicate keys in objects&lt;/li&gt;
&lt;li&gt;No unreachable code&lt;/li&gt;
&lt;li&gt;No unused variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, the rules you enable in your project don't need to come directly from ESLint. Popular libraries like &lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt;, &lt;a href="https://lodash.com/"&gt;Lodash&lt;/a&gt;, and &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt; have ESLint configurations that anyone can use in their own projects to ensure users adhere to best practices. If you're an intrepid explorer of the JavaScript language you can go a step further and create custom rules specific to your design system which you can export for other people to use in their projects. That's exactly what we'll do in these articles.&lt;/p&gt;




&lt;p&gt;In this article, we'll spend a bit of time understanding how tools like ESLint parse JavaScript down into a data structure called an &lt;em&gt;abstract syntax tree&lt;/em&gt; (AST). We'll then touch on how ESLint rules work and how to parse our Lit templates into HTML. Finally we'll start creating our rules. We'll even use ESLint's built-in testing tool to make sure our rules work under a variety of conditions.&lt;/p&gt;

&lt;p&gt;The pre-requisite for this article is some JavaScript + HTML knowledge. A little experience using ESLint and Lit may come in handy but isn't necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s an Abstract Syntax Tree?
&lt;/h2&gt;

&lt;p&gt;For those, like me, who haven't gotten their hands dirty with compilers before, conceptualising how the human-readable language we write in our IDE gets understood (and transformed) by tools like &lt;a href="https://webpack.js.org/"&gt;Webpack&lt;/a&gt;, &lt;a href="https://prettier.io/"&gt;Prettier&lt;/a&gt;, and &lt;a href="https://babeljs.io/"&gt;Babel&lt;/a&gt; can feel like magic.&lt;/p&gt;

&lt;p&gt;Under the hood, when a tool like ESLint wants to start performing actions against your JavaScript it &lt;em&gt;parses&lt;/em&gt; your code. Parsing is the process of taking the JavaScript you've written and turning it into a tree representation of the code, an &lt;em&gt;abstract syntax tree&lt;/em&gt; (AST).&lt;/p&gt;

&lt;p&gt;This process of parsing is split into two parts, &lt;em&gt;tokenization&lt;/em&gt; and &lt;em&gt;tree construction&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Tokenization takes the code and splits it into things called tokens which describe isolated parts of the syntax.&lt;/p&gt;

&lt;p&gt;Tokens for a JavaScript program like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello world&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;will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IdentifierName&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;const&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WhiteSpace&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IdentifierName&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;helloWorld&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WhiteSpace&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Punctuator&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;WhiteSpace&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;StringLiteral&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'hello world'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;closed&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;
  JS Tokens
  &lt;p&gt;I used &lt;a href="https://github.com/lydell/js-tokens"&gt;js-tokens&lt;/a&gt; as a quick way of tokenizing my JS for this example but we won't be dealing with tokenization directly ourselves in this article.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;The second step in the parsing process is &lt;em&gt;tree construction&lt;/em&gt;, which reformats the tokens into an AST. The AST describes each part of the syntax and its relationship to the others.&lt;/p&gt;

&lt;p&gt;We can visualise this relationship by parsing the following JavaScript statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Creating custom ESLint rules&amp;lt;/h1&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would get transformed into an AST, with the following structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/blog/best-practices-w-eslint/parser.drawio.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/blog/best-practices-w-eslint/parser.drawio.png" alt="AST representation of the previous code sample"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tools like Babel and Prettier turn your written JavaScript into an AST to analyse and transform the code we've written. Babel uses the AST to transpile our code into a browser-friendly version of JavaScript, while Prettier uses the AST to reformat your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Curious with the AST Explorer
&lt;/h2&gt;

&lt;p&gt;To really explore what an AST looks like, have a play with the &lt;a href="https://astexplorer.net/"&gt;AST explorer&lt;/a&gt;. Get familiar with the AST explorer as we're gonna be using it loads later in the article.&lt;/p&gt;

&lt;p&gt;Write a simple statement, like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello world&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;You'll see that the top level of the tree describes the entire program and we can look into the &lt;em&gt;body&lt;/em&gt; array to see the individual constituents of our above statement represented in the AST.&lt;/p&gt;

&lt;p&gt;If you hover over the &lt;code&gt;VariableDeclaration&lt;/code&gt; you can see that the entire statement on the left gets highlighted. If we go a level deeper into the &lt;code&gt;declarations&lt;/code&gt; array you'll see an additional node &lt;code&gt;VariableDeclarator&lt;/code&gt;. If we keep going we'll eventually reach rock bottom. In the case of our hello world statement, it's with the variable's &lt;code&gt;Identifier&lt;/code&gt; and the variable's &lt;code&gt;Literal&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;Let's revisit our component from earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;h1&amp;gt;Creating custom ESLint rules&amp;lt;/h1&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you step through the tree in the AST explorer you can see that the structure matches our image from earlier. Pay particular attention to the &lt;code&gt;TaggedTemplateExpression&lt;/code&gt; node and the &lt;code&gt;TemplateLiteral&lt;/code&gt; node. These are the ones that will come in handy when we write our ESLint rules.&lt;/p&gt;

&lt;p&gt;Our call to the &lt;code&gt;html&lt;/code&gt; function is an expression, but it looks a little different from other function definitions. Let's see how the AST differs with an expression like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;heyThere&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="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="nx"&gt;heyThere&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 hover over the &lt;code&gt;heyThere()&lt;/code&gt; &lt;code&gt;ExpressionStatement&lt;/code&gt;, we see that the properties match our &lt;code&gt;html&lt;/code&gt; ExpressionStatement. The main difference is that the value in the &lt;code&gt;expression&lt;/code&gt; property looks different. The expression this time is a &lt;code&gt;CallExpression&lt;/code&gt;, which has a set of properties different to that of our &lt;code&gt;TaggedTemplateExpression&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we look back at our &lt;code&gt;TaggedTemplateExpression&lt;/code&gt;, we can see that we have properties like tag and quasi.&lt;/p&gt;

&lt;p&gt;The tag gives us some details about the function name. Which in this case is &lt;code&gt;html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means when writing our ESlint rule we'll be able to do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Some ESLint psuedo-code&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createRule&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;TaggedTemplateExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLitExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLitExpression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// rest of the rule&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// do nothing&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;Finally, if you look into the &lt;code&gt;TaggedTemplateExpression&lt;/code&gt; object, you'll see a property named &lt;code&gt;quasi&lt;/code&gt;. This property contains our two noteworthy properties &lt;code&gt;expressions&lt;/code&gt; and &lt;code&gt;quasis&lt;/code&gt;. Take the following expression:&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/blog/best-practices-w-eslint/taggedtemplateexpression.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/blog/best-practices-w-eslint/taggedtemplateexpression.png" alt="Tagged Template Expression Highlights"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The blue underlines, the first and third respectively, will live in the &lt;code&gt;quasis&lt;/code&gt; array and they'll be in the order that they're written in your template literal.&lt;/p&gt;

&lt;p&gt;The green underline, the second one, will live in the &lt;code&gt;expressions&lt;/code&gt; array, and provides a reference to the name of the variable. Like the &lt;code&gt;quasis&lt;/code&gt;, the items in the array are in the order that they're defined. This makes it very easy to reconcile your template literal later on.&lt;/p&gt;

&lt;p&gt;
  Quasis
  &lt;p&gt;When accessing the value in your quasis, you'll see the string available as either &lt;em&gt;raw&lt;/em&gt; or &lt;em&gt;cooked&lt;/em&gt;. These values determine whether escape sequences are ignored or interpreted. Axel Rauschmayer covers this in a little more detail in &lt;a href="https://2ality.com/2016/09/template-literal-revision.html"&gt;this article&lt;/a&gt;.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;Here's a question for you, what happens if the first character of our template literal is an expression? How is this represented in our AST? Try the following snippet in the AST explorer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;helloWorld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, how you doin'?`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take a little more time exploring quasis and expressions if they still feel unfamiliar to you.&lt;/p&gt;

&lt;p&gt;Fortunately, we won't need to directly deal with the parsing process when writing our ESLint rules. We've covered a lot of ground because having a high-level understanding of how the tooling works, makes for a much more intuitive development experience later on.&lt;/p&gt;

&lt;p&gt;
  Super Tiny Compiler
  &lt;p&gt;If you're interested in learning a little more about the whole compilation process, the &lt;a href="https://github.com/jamiebuilds/the-super-tiny-compiler/blob/master/the-super-tiny-compiler.js"&gt;Super Tiny Compiler&lt;/a&gt; is a really fun way of building your own JavaScript compiler using only a couple of hundred lines of code.&lt;/p&gt;



&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do Eslint Rules Work?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Visitor Pattern
&lt;/h3&gt;

&lt;p&gt;Fortunately, we don't need to make any transformation when writing ESLint rules and instead we write our checks against specific node types in our code. These nodes are slices from our code's AST.&lt;/p&gt;

&lt;p&gt;Once ESLint has parsed your code into an AST, it then traverses your tree, &lt;em&gt;visiting&lt;/em&gt; each node along the way. For those familiar with programming design patterns, you might recognise this pattern as the &lt;em&gt;visitor&lt;/em&gt; pattern.&lt;/p&gt;

&lt;p&gt;The visitor pattern is a way of running some new logic against an object without modifying the object. ESLint uses the visitor pattern to separate the code used to run checks against your code from the AST.&lt;/p&gt;

&lt;p&gt;Let's take a look at the &lt;a href="https://codesandbox.io/s/mystifying-morning-lk3xh?file=/src/index.js"&gt;visitor pattern in action&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can see, that I've implemented the visitor using 3 blocks of code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ast.js&lt;/code&gt;: The AST for &lt;code&gt;const name = 'andrico'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;traverser.js&lt;/code&gt;: An algorithm that traverses the nodes of our AST.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;visitors.js&lt;/code&gt;: An object of methods where a given method fires once the traverser reaches its corresponding node. In our case, when the traverser reaches a &lt;code&gt;VariableDeclarator&lt;/code&gt; node, it fires off our visitor function.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's break down the &lt;code&gt;traverser&lt;/code&gt; a little more:&lt;/p&gt;

&lt;p&gt;We begin in &lt;code&gt;index.js&lt;/code&gt; by creating an instance of our &lt;code&gt;Traverser&lt;/code&gt; class and passing through our AST and our visitors. Under the hood, our &lt;code&gt;Traverser&lt;/code&gt; class stores our AST and visitors as instance variables for us to use later.&lt;/p&gt;

&lt;p&gt;We then invoke the instance's &lt;code&gt;traverse&lt;/code&gt; method. If you move to the &lt;code&gt;traverser.js&lt;/code&gt; file, you can see that when we invoke &lt;code&gt;traverse&lt;/code&gt; 5 things can happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The node is &lt;code&gt;null&lt;/code&gt;, which will happen when we manually invoke the &lt;code&gt;traverse&lt;/code&gt; method without any arguments. When this happens, we kick off the traversal function using the AST we stored during the class's initialisation.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;node&lt;/em&gt; has a type of &lt;code&gt;Program&lt;/code&gt;, which will happen for the top-level nodes in our AST. When this happens we recursively call the traversal method on the child nodes.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;node&lt;/em&gt; has a type that matches a visitor function. When this happens, we fire our visitor function and pass through the node as an argument.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;node&lt;/em&gt; has additional declarations, so we continue calling our traversal function on those child declarations.&lt;/li&gt;
&lt;li&gt;Our &lt;em&gt;node&lt;/em&gt; satisfies none of these conditions, which will cause our traversal method to exit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the context of our &lt;code&gt;const name = 'andrico'&lt;/code&gt; example, our traversal function will continue making its way through the AST until it reaches the &lt;code&gt;VariableDeclarator&lt;/code&gt;, where it will invoke the visitor we defined in &lt;code&gt;visitors.js&lt;/code&gt;. In this visitor we check to see if the value is &lt;code&gt;Andrico&lt;/code&gt; and if it is, we log a message saying that it's an invalid name (though I kinda like it).&lt;/p&gt;

&lt;p&gt;Open the console in the CodeSandbox and see what it outputs. Try changing the check in your visitor and see what happens, if anything.&lt;/p&gt;

&lt;p&gt;The good news is that ESLint handles the traversal logic for our JavaScript. The other good news is that we'll need to implement the traversal logic for our parsed HTML. 😄&lt;/p&gt;

&lt;p&gt;
  Open WC's eslint-plugin-lit-a11y
  &lt;p&gt;This section was heavily informed by my recent involvement with Open WC's &lt;a href="https://github.com/open-wc/open-wc/tree/master/packages/eslint-plugin-lit-a11y"&gt;eslint-plugin-lit-a11y&lt;/a&gt; and &lt;a href="https://github.com/43081j/eslint-plugin-lit"&gt;eslint-plugin-lint&lt;/a&gt;. If you'd like to learn more about (or try a handful of) ESLint rules focused on web components, these are your go-to repos.&lt;/p&gt;



&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does an Eslint Rule Look Like?
&lt;/h3&gt;

&lt;p&gt;Writing an ESLint rule doesn't require anything fancy, it's just a plain ol' JavaScript object. The object's top-level can receive two properties: &lt;code&gt;meta&lt;/code&gt; and &lt;code&gt;create&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;meta&lt;/code&gt; provides the metadata for the rule.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;create&lt;/code&gt; property is a function that returns an object of visitors that ESLint calls when it visits each node. This follows the same principle as the snippets in the codesandbox. And much like the demo in our codesandbox, the name of each visitor function is the name of the node that we want to visit.&lt;/p&gt;

&lt;p&gt;In fact, we can even repurpose the pseudo-code from earlier, and decorate it with the ESLint specific boilerplate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="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;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;create&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;TaggedTemplateExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isLitExpression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLitExpression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// rest of the rule&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// do nothing&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;The &lt;code&gt;create&lt;/code&gt; function also provides a context object, which provides some additional helpers and information about the current rule. The helper we're most concerned with right now is the &lt;code&gt;report()&lt;/code&gt; method. We can call &lt;code&gt;report&lt;/code&gt; whenever we want an ESLint error to display in the console or the IDE.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://Context.report"&gt;Context.report&lt;/a&gt; takes an object with a handful of properties, but we're most interested in the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;message: the description of the problem&lt;/li&gt;
&lt;li&gt;node: the AST node related to the problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  Additional information
  &lt;p&gt;We can pass through additional information, like the line of code we want to report the error on, but that's outside the scope of this tutorial.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;Before continuing, why not think about adjusting the pseudocode above to display an ESLint error when a tagged template is invoked, and the template literal has no content, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;``&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a basic understanding of JavaScript's AST, the visitor pattern, and the anatomy of an ESLint rule, the only thing left to cover is how to parse our template string into HTML before we can start creating our rules.&lt;/p&gt;

&lt;p&gt;For a more in-depth read into the anatomy of an ESLint rule, there's no better place to look than the &lt;a href="https://eslint.org/docs/developer-guide/working-with-rules"&gt;official docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Can We Transform Our Templates into HTML?
&lt;/h2&gt;

&lt;p&gt;When using ESLint, we have the luxury of ESLint providing us with our parsed JavaScript AST. And while ESLint can't parse our HTML, we can use a library like &lt;code&gt;[parse5](https://github.com/inikulin/parse5)&lt;/code&gt; to parse a valid HTML string into a data structure, not unlike our JavaScript AST.&lt;/p&gt;

&lt;p&gt;The AST explorer we've spent so much time exploring even has settings for displaying &lt;a href="https://astexplorer.net/#/1CHlCXc4n4"&gt;HTML ASTs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since one of our rules is going to prevent us from passing through inline styles, let's see how the following gets represented as an AST:&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;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:inline;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Main content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we dive into the AST and look for our div, we can see we're presented with some useful information. The most notable are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tagName&lt;/strong&gt;: Which is the name of the html element. (in this case &lt;code&gt;div&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;attrs&lt;/strong&gt;: Which is an array of attributes, represented as a key-value pair. Our div's &lt;code&gt;attrs&lt;/code&gt; property holds a single item. The item has a &lt;code&gt;name&lt;/code&gt; of &lt;code&gt;style&lt;/code&gt; and a &lt;code&gt;value&lt;/code&gt; of &lt;code&gt;display:inline;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using this information we can already start seeing how to piece together everything we've learned to create our first lint rule.&lt;/p&gt;

&lt;p&gt;Here's how we can parse our JavaScript templates using the &lt;code&gt;parse5&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;parse5&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;parse5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// We're defining out HTML templates&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;div style="display:inline;"&amp;gt;Main content&amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// We're passing through an HTML snippet to parseFragment, which returns our HTML AST&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedFragment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parse5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseFragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// We access the first child because the top-level contains metadata we don't need right now.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parsedFragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;childNodes&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;// We check to see if there are any style attributes in our div&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasStyleAttr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;style&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If there are, we report an error&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;hasStyleAttr&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;FAIL&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;Thanks to tools like parse 5 and ESLint, we can offload a lot of the complex processing, and focus on writing the code for our specific rules. This is what we'll start doing from the next article onwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe Check
&lt;/h2&gt;

&lt;p&gt;
  Further Reading
  &lt;ul&gt;
&lt;li&gt;&lt;a href="https://babeljs.io/docs/en/babel-parser"&gt;Babel Parser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/lydell/js-tokens"&gt;js-tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://astexplorer.net/"&gt;AST Explorer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://egghead.io/lessons/javascript-introduction-to-abstract-syntax-trees"&gt;Abstract Syntax Trees Kent Dodds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.sessionstack.com/how-javascript-works-parsing-abstract-syntax-trees-asts-5-tips-on-how-to-minimize-parse-time-abfcf7e8a0c8"&gt;Parsing and ASTs in JS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jamiebuilds/the-super-tiny-compiler/blob/master/the-super-tiny-compiler.js"&gt;Super Tiny Compiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/get-started/ch1.md"&gt;You Don't Know JS Yet&lt;/a&gt; - Chapter 1&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint.org/docs/developer-guide/working-with-rules"&gt;Working with rules - ESLint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eslint.org/docs/developer-guide/nodejs-api#ruletester"&gt;ESLint RuleTester&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#interactive_content"&gt;Interactive content&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md"&gt;Babel plugin handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/open-wc/open-wc/tree/master/packages/eslint-plugin-lit-a11y"&gt;ESLint Plugin Lit A11y&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/43081j/eslint-plugin-lit"&gt;ESLint Plugin Lit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;

&lt;p&gt;We've covered a lot of theory so far, and a lot of separate ideas. We'll bring everything together in the next article.&lt;/p&gt;

&lt;p&gt;Let's have a vibe check, if something doesn't quite make sense at this point, it's worth giving it a quick re-review. And if things still aren't clear, that's probably on me, so feel free to &lt;a href="https://twitter.com/andricokaroulla?lang=en"&gt;reach out and let me know how I can make things even clearer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before we move on, let's go through the key points one last time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The JavaScript we write gets parsed into an AST, which tools can use to validate or transform our code&lt;/li&gt;
&lt;li&gt;Each part of our JavaScript code is represented as a node, as seen in the AST explorer.&lt;/li&gt;
&lt;li&gt;ESLint then traverses our AST, and invokes our visitor functions whenever it &lt;em&gt;visits&lt;/em&gt; a node we're interested in.&lt;/li&gt;
&lt;li&gt;Once ESLint invokes our visitor function, we can start running checks against the node.&lt;/li&gt;
&lt;li&gt;We can then check to see if the node that gets passed to our function is a &lt;code&gt;lit&lt;/code&gt; &lt;em&gt;TaggedTemplateExpression&lt;/em&gt;, and if it is, we can grab its HTML fragment, which we can build by consolidating the expressions and quasis.&lt;/li&gt;
&lt;li&gt;We'll use &lt;code&gt;parse5&lt;/code&gt; to parse the fragment and give us our HTML's AST.&lt;/li&gt;
&lt;li&gt;We now have everything we need to run our checks, like seeing if a certain attribute is present when it shouldn't be.&lt;/li&gt;
&lt;li&gt;We can then invoke ESLint's report function if the rule has been violated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We've learned a lot of theory, and the best thing to do with all that theory is to put it into practice. In the next two articles, we're going to be creating a couple of ESLint rules and taking everything we've learned into something you can use for your own design systems.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
