<?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: colin-williams-dev</title>
    <description>The latest articles on Forem by colin-williams-dev (@colin-williams-dev).</description>
    <link>https://forem.com/colin-williams-dev</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%2F940011%2F5e7e3659-60bd-4f63-b99a-a34229aa5bfc.jpeg</url>
      <title>Forem: colin-williams-dev</title>
      <link>https://forem.com/colin-williams-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/colin-williams-dev"/>
    <language>en</language>
    <item>
      <title>How to Reclaim Memory from Docker WSL</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Tue, 09 Dec 2025 08:05:20 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/how-to-reclaim-memory-from-docker-wsl-2lkf</link>
      <guid>https://forem.com/colin-williams-dev/how-to-reclaim-memory-from-docker-wsl-2lkf</guid>
      <description>&lt;p&gt;&lt;em&gt;If you've ever had the misfortune of working with Docker on a Windows machine you are in the right place.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Also, if you've arrived here via a google search on "how to get memory back from #!$@%^ing DOCKER" here is the cheat sheet so you can be on your merry way 🎅&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;# documentation: https://learn.microsoft.com/en-us/windows/wsl/wsl-config#wslconfig&lt;/span&gt;
&lt;span class="c"&gt;# actual contents of .wslconfig @ %UserProfile%&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;wsl2]
&lt;span class="nv"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2123366400
&lt;span class="nv"&gt;swap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1073741824
&lt;span class="o"&gt;[&lt;/span&gt;experimental]
&lt;span class="nv"&gt;autoMemoryReclaim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gradual

&lt;span class="c"&gt;# the WSL documentation suggests finding the GUI and configuring there:&lt;/span&gt;
&lt;span class="c"&gt;# Start &amp;gt; WSL Settings&lt;/span&gt;

&lt;span class="c"&gt;# the default is half of your RAM (reduce this further)&lt;/span&gt;
&lt;span class="c"&gt;# I think the default is all of the processors (e.g. 20) (I reduced this to 10 or 5)&lt;/span&gt;

&lt;span class="c"&gt;# comprehensive blog with example .wslconfig&lt;/span&gt;
&lt;span class="c"&gt;# https://www.valens.dev/blog/Take-control-of-your-WSL-resources-for-smooth-development&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Now for my thoughts...&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Warning!
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This blog is going to piss a lot of people off because...&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Everyone LOVES &lt;em&gt;Docker&lt;/em&gt; (I don't)&lt;/li&gt;
&lt;li&gt;I curse a lot in this blog... (It's censored)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Docker is an @$#hole on Windows🙂
&lt;/h2&gt;

&lt;p&gt;As a native Windows developer who works almost exclusively in the Microsoft tech stack (TypeScript, C#/.NET, MS SQL Server, Azure) I have no reason to go near Windows Subsystem for Linux... that is... until &lt;em&gt;Docker&lt;/em&gt; enters the picture... 🙄&lt;/p&gt;

&lt;p&gt;I have worked with Docker for two years (feels like devs don't often get a choice in the matter) and only last week did I discover the existence (the necessity...) of the &lt;code&gt;.wslconfig&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;For some reason, WSL2 comes ready to steal massive amounts of your machine's processing power out of the box. Docker on Windows "requires" WSL for its backend (presumably for the container/VM orchestration). The defaults for what your VMs can partition from your hardware are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;100%&lt;/code&gt; of your CPU cores&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;50%&lt;/code&gt; of your RAM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n%&lt;/code&gt; of your hard disk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This feels problematic in general but allow me the pleasure of listing explicit reasons why: (&lt;em&gt;why you might want to relegate only the bare minimum processes to your WSL VMs and keep the vast majority of your processing power on the Windows partition&lt;/em&gt;)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want access to a desktop GUI and native apps*&lt;/li&gt;
&lt;li&gt;You want access to your primary file system&lt;/li&gt;
&lt;li&gt;You want to limit the amount of latency you have to deal with&lt;/li&gt;
&lt;li&gt;You work in enterprise which runs anti-malware and other spyware on your machine which will also hog resources...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Yes, I know, &lt;a href="https://github.com/microsoft/wslg" rel="noopener noreferrer"&gt;WSLG&lt;/a&gt; exists and is actually goated...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR - You want to preserve as much processing power as possible on your primary partition for a litany of valid reasons.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, why does WSL think you want to surrender ONE-HUNDRED-PERCENT OF YOUR CPU CORES AND FIFTY PERCENT OF YOUR RAM??? 🤔🤔🤔&lt;/p&gt;

&lt;p&gt;Idk...&lt;/p&gt;

&lt;p&gt;But anyway, here is how to fix it: Bottleneck Hardware Thresholds in the &lt;code&gt;wslconfig&lt;/code&gt; File.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Create a &lt;code&gt;.wslconfig&lt;/code&gt; File
&lt;/h2&gt;

&lt;p&gt;The official Microsoft Documentation strongly suggests you do this through the Windows UI so I'll list those steps. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;(see the top "cheatsheet" if you want to raw-dog it and just &lt;code&gt;cd ~&lt;/code&gt; (your &lt;code&gt;%USERPROFILE%&lt;/code&gt; most likely) &lt;code&gt;ni .wslconfig&lt;/code&gt; etc etc etc)&lt;br&gt;
Legend: &lt;code&gt;Blah Blah Setting: My - Choice (suggested percent hardware capacity)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to: Start &amp;gt; WSL Settings&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2 - Bottleneck Memory and Processor
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Adjust settings in "Memory and processor"

&lt;ul&gt;
&lt;li&gt;Processor Count: 10 - 15 (50-75%)&lt;/li&gt;
&lt;li&gt;Memory Size: 2000MB - 8000MB (6.25% - 25%)&lt;/li&gt;
&lt;li&gt;(2000MB is the minimum for SQL Server)&lt;/li&gt;
&lt;li&gt;Swap Size: 1024MB (0.002%+)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3 - Reclaim Memory During Runtime
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;By default, Windows will only reclaim memory from the partitions automatically on cache drop, but with an experimental feature we can reclaim unused resources while the VMs are running.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adjust settings in "Optional Features"

&lt;ul&gt;
&lt;li&gt;Auto memory reclaim: Gradual&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4 - Restart WSL
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Either close Docker Desktop or whatever or run the following commands&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# see if WSL is running anything&lt;/span&gt;
wsl &lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="nt"&gt;--running&lt;/span&gt;
&lt;span class="c"&gt;# if it is, teriminate it&lt;/span&gt;
wsl &lt;span class="nt"&gt;--shutdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations! 🎉🎈You just reclaimed tons of processing power that Docker and WSL stole without your permission!🥳&lt;/p&gt;

&lt;h2&gt;
  
  
  Addendum
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This config will apply thresholds/bottlenecks to the entire WSL orchestrator, meaning, if you need more resources than a single SQL Server Instance and some Functions, these ratios will not work for you.&lt;/li&gt;
&lt;li&gt;"Swap Size" creates a file on disk which will use native I/O computational processing power for the spills when your VM needs to exceed the RAM/memory bottleneck. This can have adverse effects on the health of your physical disk. &lt;strong&gt;DO NOT USE SWAP SIZE/FILE IF YOU HAVE AN HDD&lt;/strong&gt; (each HDD has a finite number of READS/WRITES in its lifetime)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;#docker #wsl #vm #local #performance #bottleneck #ram #memory #cpu #disk&lt;/p&gt;

</description>
      <category>docker</category>
      <category>microsoft</category>
      <category>virtualmachine</category>
      <category>performance</category>
    </item>
    <item>
      <title>What is the git index?</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Mon, 27 May 2024 19:57:30 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/what-is-the-git-index-2485</link>
      <guid>https://forem.com/colin-williams-dev/what-is-the-git-index-2485</guid>
      <description>&lt;h1&gt;
  
  
  🗃 💻 🌊
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Ever so often, I like to "plunge" (more on that later..) into the depths of Git deeper to strengthen my understanding of this tool I leverage every single day.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently, I wanted to &lt;strong&gt;untrack files that had previously been tracked and ultimately were pushed to my remote repository&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I added the directory for said files to my .gitignore and believed my next step would be to just delete them off the remote directly on GitHub... But, it occurred to me there were probably better ways to achieve this same outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  (scroll down to the📌for tl;dr)
&lt;/h2&gt;




&lt;p&gt;My investigating lead me to a few places: &lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;a href="https://superuser.com/a/1442728" rel="noopener noreferrer"&gt;Super-User Forum Comment on --cached&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Here is where I learned about &lt;code&gt;git rm --cached...&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;What &lt;code&gt;--cached&lt;/code&gt; does is: &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;"Use this option to unstage and remove paths only from the INDEX. Working tree files, whether modified or not, will be left alone." &lt;br&gt;
[emphasis mine] [source below]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. &lt;a href="https://git-scm.com/docs/git-rm" rel="noopener noreferrer"&gt;Git Documentation on git rm&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;This lead me further into this "pipe" (more still to come...) with asking myself "what is the git index?"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;a href="https://stackoverflow.com/a/3690796" rel="noopener noreferrer"&gt;SO forum comment on the actual /.git/index&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Where I learned that:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;"The index is a single, large, binary file in /.git/index, which lists all files in the current branch, their sha1 checksums, time stamps and the file name -- it is not another directory with a copy of files in it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Moreover, the git "index" is where your changes go when you run &lt;code&gt;git add ...&lt;/code&gt; and directly into that above mentioned binary (&lt;code&gt;/.git/index&lt;/code&gt;) which acts as your "staging area" in between your workspace (working directory) and your local repository (where your changes go to your current branch when you run &lt;code&gt;git commit ...&lt;/code&gt;).&lt;/p&gt;

&lt;h1&gt;
  
  
  📌
&lt;/h1&gt;

&lt;p&gt;To summarize, when you run &lt;code&gt;git rm --cached folder/\*&lt;/code&gt; git will delete the (history?) data represented by this directory in your /.git/index binary/staging area. Next, you push these changes to your remote to have them scrubbed out, LEAVING your directory locally. If this directory has been added to your .gitignore, it will no longer be tracked as well.&lt;/p&gt;

&lt;h1&gt;
  
  
  🚽 🌊 🏄‍♂️
&lt;/h1&gt;

&lt;p&gt;Now for the conceit that git uses regarding the words "plunge", "pipe" and these 🌊 emojis I've been cheekily trying to breadcrumb into this post...&lt;/p&gt;

&lt;p&gt;Through my investigation I learned of the "high level" and "low level" conceit that git adopts: "Porcelain" (high) and "Plumbing" (low).&lt;/p&gt;

&lt;h3&gt;
  
  
  4.a. &lt;a href="https://git-scm.com/docs/git#_high_level_commands_porcelain" rel="noopener noreferrer"&gt;high_level_commands_porcelain&lt;/a&gt;
&lt;/h3&gt;

&lt;h3&gt;
  
  
  4.b. &lt;a href="https://git-scm.com/docs/git#_low_level_commands_plumbing" rel="noopener noreferrer"&gt;low_level_commands_plumbing&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Anyway, just thought that was funny. Git uses a toilet metaphor in their documentation&lt;/em&gt;... 😋 &lt;/p&gt;

&lt;h3&gt;
  
  
  Honorable mention: &lt;a href="https://think-like-a-git.net/sections/git-makes-more-sense-when-you-understand-x/example-4-lsd-and-chainsaws.html" rel="noopener noreferrer"&gt;Think like (a) Git: LSD &amp;amp; Chainsaws&lt;/a&gt;
&lt;/h3&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Simple Production Error-Logger w/ Node and S3</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Sun, 14 Apr 2024 05:48:09 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/building-a-simple-production-logger-w-node-and-s3-2076</link>
      <guid>https://forem.com/colin-williams-dev/building-a-simple-production-logger-w-node-and-s3-2076</guid>
      <description>&lt;h2&gt;
  
  
  🤔Problem:
&lt;/h2&gt;

&lt;p&gt;You want to record error logs asynchronously for an app in production, but you don't want to eat up limited disk space. You also want your dev team to easily be able to access the logs as soon as they can. You also don't want to pay for things you don't have to...😋🏴&lt;/p&gt;

&lt;h2&gt;
  
  
  💡Solution:
&lt;/h2&gt;

&lt;p&gt;Hook into the processes you are already listening for and logging to &lt;code&gt;stderr/stdout&lt;/code&gt; but also write them to a file which you can then ship to a hosted service and delete the logs from disk.&lt;/p&gt;




&lt;h4&gt;
  
  
  There were a few things I wanted my logger to satisfy:
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Catch and log errors that have not been handled in the code.&lt;/li&gt;
&lt;li&gt;Persist the logs so that the dev team has time to find them (the app should not be crashing or terminating).&lt;/li&gt;
&lt;li&gt;Don't take up precious disk-space on the deployment.&lt;/li&gt;
&lt;li&gt;Render the data in a human readable format with useful information.&lt;/li&gt;
&lt;li&gt;Make the data as available as possible (without being completely public*).&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;*&lt;em&gt;AWS IAM users pending still..&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  📌Node.js Process and FS
&lt;/h2&gt;

&lt;p&gt;I was working on my application's execution context architecture, setting up lots of try/catches with re-throws when the above "💡&lt;strong&gt;Solution&lt;/strong&gt;" thought occurred to me. I had just written my top level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;/////////////////////////////////////////////////////////&lt;/span&gt;
&lt;span class="c1"&gt;///// #region top-level node critical-failure catch /////&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;uncaughtExceptionMonitor&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;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;origin&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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Critical failure, propagated to top-level from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, error: `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="cm"&gt;/* TODO: create custom monitor class here that will handle application recover or restart from unhandled critical failure... */&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="nf"&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;unhandledRejection&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;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promise&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="nf"&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;Unhandled Rejection at:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reason:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="cm"&gt;/* TODO: application logging, throwing an error, or other logic here for uncaught promises */&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="nf"&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;uncaughtException&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;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Caught exception: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="s2"&gt;`Exception origin: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n`&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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Critical Error -- app is about to explode... &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; performing synchronous cleanup.. &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; writing crash state to terminal..&lt;/span&gt;&lt;span class="dl"&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="nf"&gt;writeSync&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;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fd&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="cm"&gt;/* TODO: needs to be pruned on CRON job, otherwise infinitely expands for logs */&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;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;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error.log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;`Logging to ./error.log app critical failure:\n
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;\n
      &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n --- \n
      \n`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;goodbye.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;/////////////////////////////////////////////////////////&lt;/span&gt;
&lt;span class="c1"&gt;// #endregion ///////////////////////////////////////////&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this implementation I was writing to a file in the root called "error.log" (creating--if not exists--to the local disk). &lt;/p&gt;

&lt;p&gt;With a node backend, I could log what I needed (on node's &lt;code&gt;processes&lt;/code&gt; events) in the typical &lt;code&gt;stderr/stdout&lt;/code&gt; but I also wanted to make it persist. I could have just stuffed it into a database somewhere... but, I didn't want to pollute the database and I also wanted the logs to be more readily available. I knew I could use node's &lt;code&gt;fs&lt;/code&gt; module to write to a file but I was concerned about eating my production instance's disk space. 😓&lt;/p&gt;

&lt;p&gt;I wanted to leverage more of what I already had available to me as well as limit my costs as much as possible. I thought about where else I could put these logs with low overhead while provisioning as few resources as possible. I started with technologies I already had implemented for my project: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub (where the development git repo lives) 

&lt;ul&gt;
&lt;li&gt;Pages (free)❌&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;AWS (the EC2 where the production deployment will &lt;em&gt;eventually&lt;/em&gt; live)

&lt;ul&gt;
&lt;li&gt;S3 (free)✅&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  📌AWS S3 HTML/JS
&lt;/h2&gt;

&lt;p&gt;I knew I could quickly provision an &lt;code&gt;index.html&lt;/code&gt; and &lt;em&gt;probably&lt;/em&gt; throw in a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; where I could leverage the JS Fetch API to send an HTTP request to my node server. I could also easily use the &lt;code&gt;dom&lt;/code&gt; with vanilla JS and HTML to display my logs in a nice, human-readable format:&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;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;GH-SP-Logger&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;GH-Scrum-Poker-Error-Logger:&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ol&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error-logs-list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchErrorLogs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://tricky-monkeys-sin.loca.lt/error-logger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// TODO: replace with static origin-url of hosted node web-server&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bypass-tunnel-reminder&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// Set the bypass-tunnel-reminder header TODO: remove this&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&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="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&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;=&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;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&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="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Next Log Entry -- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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;errorLogsElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error-logs-list&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;li&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;li&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;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pre&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;space&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;space&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;errorLogsElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

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

    &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetchErrorLogs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// fires every 12 hours TODO: set this to something?&lt;/span&gt;

    &lt;span class="cm"&gt;/* Set interval to purge &amp;lt;li&amp;gt; items from &amp;lt;ol&amp;gt; every 2 weeks */&lt;/span&gt;
    &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorLogsElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error-logs-list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;errorLogsElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&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="c1"&gt;// Clear all &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&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;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I utilized JS native &lt;code&gt;setInterval&lt;/code&gt; to constrain how frequently the requests were being sent (once every 12 hours) and purge stale error logs (once every two weeks). I opted for a long time between purges since the ordered list HTML element will keep the sequence of logs relevant to their time of occurrence (and I also have an ISO string Date prepended to each item). The free hosting space for a static site with S3 should suffice and this way I don't need to leverage any object delete operations which could incur extra costs.&lt;/p&gt;

&lt;p&gt;This seemed all fine and dandy... but while setting up the static site on S3 I noticed something... &lt;em&gt;CORS&lt;/em&gt;... (ughhhh)&lt;/p&gt;

&lt;p&gt;I threw up a wild card policy (S3) in &lt;code&gt;json&lt;/code&gt; for testing/development:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AllowedHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AllowedMethods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"HEAD"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AllowedOrigins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ExposeHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MaxAgeSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As soon as the webserver is deployed (dev and test on localhost for now) the &lt;strong&gt;allowed origins will be swapped out&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  📌Tunneling/Proxy:
&lt;/h2&gt;

&lt;p&gt;During this process it occurred to me that the deployed S3 would not be able to communicate with my locally running node server... I had used tunneling before with &lt;code&gt;ngrok&lt;/code&gt; but I wanted something lighter and simpler... &lt;em&gt;enter&lt;/em&gt; &lt;a href="https://theboroer.github.io/localtunnel-www/" rel="noopener noreferrer"&gt;localtunnel&lt;/a&gt;. With a simple CLI command: &lt;code&gt;lt --port 3000&lt;/code&gt; I could tunnel my locally-running-node-process port a randomly generated, live HTTPS URL. &lt;/p&gt;

&lt;p&gt;You may have already noticed this URL in the S3 &lt;code&gt;fetch&lt;/code&gt; call: &lt;code&gt;fetch("https://tricky-monkeys-sin.loca.lt/error-logger" ...)&lt;/code&gt;. This, as well as all wildcard origin-access will all be swapped out for the safeguarded origin of the production server.&lt;/p&gt;

&lt;h2&gt;
  
  
  📌Node Endpoint and Middleware
&lt;/h2&gt;

&lt;p&gt;Next, I had to set up an endpoint that would receive the request from my S3's JS Fetch request. I knew I could read the data (the logs) from the file on disk and send them somewhere over HTTP with node by creating an endpoint and simple middleware. &lt;/p&gt;

&lt;p&gt;First, I designed the middleware function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathToFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error.log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cm"&gt;/* TODO: This reads from local error.log on disk... needs to be pruned incrementally? */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorLogger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&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;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&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="k"&gt;try&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="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Reading from Error Log file on Disk.. -- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pathToFile&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="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathToFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&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;err&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`404 root error.log could not be read.. &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cm"&gt;/* TODO: SWAP OUT THIS "*" WITH ACTUAL ORIGIN (replace lt proxy) */&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="cm"&gt;/* TODO: swap out origin in S3 permissions: CORS when static origin created (when above swap happens) (also noted in src/index) */&lt;/span&gt;
    &lt;span class="p"&gt;});&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="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`500 Failure reading error.log from disk.. &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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and hooked it up to an endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configureServer&lt;/span&gt; &lt;span class="o"&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;Application&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;/* Global Middleware */&lt;/span&gt;
    &lt;span class="nx"&gt;server&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;extended&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="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/error-logger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errorLogger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📌CRON
&lt;/h2&gt;

&lt;p&gt;The last step was to ensure that disk space was not being eaten up by the node FS write operations in the "production" environment. I elected to purge the file on disk, once a day, since it would be sending all log data every 12 hours to the S3 bucket. To achieve this I implemented a simple CRON Job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Path to the local error log file defined earlier&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathToFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error.log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Define a cron job that runs every day at midnight&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&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;cron&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CronJob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// Truncate the error log file to empty it&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathToFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error truncating error.log file:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Disk error.log file has been cleaned out.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start the cron job&lt;/span&gt;
&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully you found this interesting and/or informative! Leave a comment if you have any suggestions 😋&lt;/p&gt;

&lt;h2&gt;
  
  
  P.S.
&lt;/h2&gt;

&lt;p&gt;Depending on the success of this application I may migrate to a paid service such as &lt;a href="https://sentry.io/welcome/#errormonitoring" rel="noopener noreferrer"&gt;Sentry.io&lt;/a&gt; for a much more elegant error monitoring process. (But, I think this workflow has a pretty decent DevX (since the team only needs to visit a URL to see a styled HTML render))&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>aws</category>
      <category>programming</category>
    </item>
    <item>
      <title>Transferring Files of Any Size with Powershell</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Fri, 08 Mar 2024 03:43:49 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/transferring-files-of-any-size-with-powershell-3783</link>
      <guid>https://forem.com/colin-williams-dev/transferring-files-of-any-size-with-powershell-3783</guid>
      <description>&lt;p&gt;So, you have a directory that you want to share with your team. It's a big one. You think: "I'll compress this to a zip and email it" to avoid laborious hosting steps. You go to email your project when you are greeted with this:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  😑😑😑
&lt;/h2&gt;

&lt;p&gt;I got tired of seeing this so I wrote a couple Powershell scripts to break the files down into arbitrarily-sized buffers, from which we can transfer in however many emails required. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This workflow requires the SENDER and RECIPIENT to each run one of the scripts... (you'll need the directory you clone these scripts to be in your PATH if you don't want to reference them with their full paths...)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The steps are simple:
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Sender - &lt;code&gt;split-file.ps1&lt;/code&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sender Runs&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;split-file.ps1 -inFile "C:\path\to\your\file.zip" -buffSize 4MB&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the out files will be named "1", "2", "3", ... to the current working directory&lt;/li&gt;
&lt;li&gt;you can pass whatever &lt;code&gt;-buffSize&lt;/code&gt; you need for your file (e.g. &lt;code&gt;-buffSize 100KB&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Recipient - &lt;code&gt;converge-files.ps1&lt;/code&gt;
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recipient Runs&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.\converge-files.ps1 -outFile "reassembled_file.zip" -buffSize 4MB&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;this will search the current working directory, matching filenames that are incrementing digits&lt;/li&gt;
&lt;li&gt;you should pass the same &lt;code&gt;-buffSize&lt;/code&gt; argument as you did to &lt;code&gt;split-file&lt;/code&gt; for best results (resembling original file)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Linked below is the repository that holds the two scripts and a helpful README with more thorough explanation. Open to cloning if you think this will be helpful for you! Feel free to make an issue or a PR if you think of ways to improve the scripts (I am a beginner with Powershell).&lt;/p&gt;

&lt;p&gt;In case you don't want to download anything, here are the contents of the two scripts:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;code&gt;split-file.ps1&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$inFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$buffSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$inFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$buffSize&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;OpenRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$inFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$chunkNum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$barr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$buffSize&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="kr"&gt;while&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$barr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$buffsize&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$outFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$chunkNum&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$outStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;OpenWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$outFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$outStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$barr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$outStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wrote chunk &lt;/span&gt;&lt;span class="nv"&gt;$outFile&lt;/span&gt;&lt;span class="s2"&gt; to file"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$chunkNum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;PSBoundParameters&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. &lt;code&gt;converge-files.ps1&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$outFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$buffSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;converge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$outfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$buffSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'^\d+$'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nv"&gt;$outStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;OpenWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$outFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nv"&gt;$inStream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.IO.File&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;OpenRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nv"&gt;$buffer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$buffSize&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$inStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$buffSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nv"&gt;$outStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nv"&gt;$inStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nv"&gt;$outStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Re-assembled file written to &lt;/span&gt;&lt;span class="nv"&gt;$outFile&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;converge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;PSBoundParameters&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;"Splatting", or using the &lt;code&gt;@&lt;/code&gt; before the &lt;code&gt;PSBoundParameters&lt;/code&gt; keyword will pass all the parameter arguments to the fn invocation in the script from how you invoked it in the CLI&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  How it works...
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;split&lt;/strong&gt; (first) script, opens a Read stream by &lt;em&gt;reading&lt;/em&gt; the &lt;code&gt;-inFile&lt;/code&gt; argument and breaks it into chunks of bytes. The bytes will be stored in a byte array (&lt;code&gt;$barr&lt;/code&gt;) and will grow (Write) incrementally in chunks of bites determined by the &lt;code&gt;-buffSize&lt;/code&gt; argument until the &lt;code&gt;.Read&lt;/code&gt; of the stream returns zero which will terminate the &lt;code&gt;while&lt;/code&gt; loop. The loop will output files named by the incrementing integer &lt;code&gt;$chunkNum&lt;/code&gt; starting with 1 (which will later be captured as a &lt;code&gt;digit&lt;/code&gt; file name in sequence).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;converge&lt;/strong&gt; script, searches the CWD (where the &lt;code&gt;split&lt;/code&gt; function outputs...) for filenames that are digits and sorts them numerically (to persist original order of byte chunks). It then uses a Write stream and a byte array to re-combine the chunks of bytes back into a single output file, resembling the original.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Voila! Your file is now duplicated and reconstructed. You will not even notice the difference. The purpose is the intermediary stage between the two scripts, where you can now send your data chunk files below any file transfer size limit and reconstruct them ("converge") when they arrive at their destination. 🏴🏴🏴&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://github.com/colinwilliams91/split-converge-scripts" rel="noopener noreferrer"&gt;My public repository for cloning the scripts&lt;/a&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Throw me a react on this blog and a star on the repo if this was helpful for you! &amp;lt;3&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>programming</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Bridge-Branch Strategy with GitHub Actions</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Mon, 05 Feb 2024 06:08:07 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/bridge-branch-strategy-with-github-actions-31ao</link>
      <guid>https://forem.com/colin-williams-dev/bridge-branch-strategy-with-github-actions-31ao</guid>
      <description>&lt;h2&gt;
  
  
  Problem:
&lt;/h2&gt;

&lt;p&gt;As a developer, you want to merge small, file imports into your remote repository's development branch to share with your team incrementally, but, your workflow is bottlenecked by opening and closing PRs every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution:
&lt;/h2&gt;

&lt;p&gt;Create a "Bridge-Branch" in parallel to &lt;code&gt;main&lt;/code&gt; or your &lt;em&gt;development&lt;/em&gt; branch that has Actions watching it and will automatically merge changes into &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/colinwilliams91/helpful-gh-automations" rel="noopener noreferrer"&gt;Bridge-Branch Template Repo with PR and Issue Templates&lt;/a&gt; I created to solve this problem. Feel free to Fork ( and Star 🌟😉) if you find yourself in a similar scenario.&lt;/p&gt;

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

&lt;p&gt;Typically, Rules and Rulesets on GH exist for a reason, and it is not best practice to build ways to circumvent them. However, when you find a reasonable case to do so, you MUST ENSURE:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small Incremental Changes (Easy to revert without loss)&lt;/li&gt;
&lt;li&gt;Consistently syncing the source branch with the target branch to avoid conflicts&lt;/li&gt;
&lt;li&gt;Good communication and notifications for your team, as changes will be going into your remote on a branch that other developers depend on MORE FREQUENTLY.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thoughts:
&lt;/h2&gt;

&lt;p&gt;There are other ways of handling this but this is a strategy that I developed while working on a collaborative Unity project. In our case, we wanted to be able to incrementally import Sprites into our Unity code base and check them into our remote repository so the entire team could pull them down and review them in an Editor or Image Viewer tool so that they can make accurate suggestions early in their design process.&lt;/p&gt;

&lt;p&gt;The nature of Sprites in a Unity project are that they will sit inside a specific directory (away from any scripts/code) until they are plugged in as components to Game Objects. For this reason, upon their creation they can be merged into the development branch of our remote repo without any risk of merge conflicts (for the most part).&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Bridge-Branch" Strategy
&lt;/h2&gt;

&lt;p&gt;This may or may not exist elsewhere, and may arguably be an anti-pattern in some git workflows, but for ours I theorized and executed a strategy. I configured our repository's permissions and runner, then designed two Actions: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To watch the "Bridge-Branch" and merge changes to &lt;code&gt;main&lt;/code&gt; automatically on push, &lt;strong&gt;removing the bottleneck.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;To watch "Main" and ensure all the code in the "Bridge-Branch" stays in sync with it, &lt;strong&gt;preventing any merge conflicts during automation&lt;/strong&gt;.
Both listen for pushes and then checkout and merge the desired changes to the specified target.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;🔁&lt;code&gt;auto_merge_bridge_to_main.yaml&lt;/code&gt; 1&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto Merge Bridge Branch to Main&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;your-bridge-branch&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;merge-to-main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="c1"&gt;# this ensures all histories are fetched for runner, avoiding `unrelated histories` merge error&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Merge to main&lt;/span&gt;
      &lt;span class="c1"&gt;# GH actions/checkout requires git identity to be configured for security, top two commands handle that&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;git config user.name "${{ github.actor }}"&lt;/span&gt;
        &lt;span class="s"&gt;git config user.email "${{ github.actor }}@users.noreply.github.com"&lt;/span&gt;

        &lt;span class="s"&gt;git fetch origin&lt;/span&gt;
        &lt;span class="s"&gt;git checkout main&lt;/span&gt;
        &lt;span class="s"&gt;git merge --no-ff -m "auto merge bridge changes to main" ${{ github.ref }}&lt;/span&gt;
        &lt;span class="s"&gt;git push origin main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🔁&lt;code&gt;auto_sync_main_to_bridge.yaml&lt;/code&gt; 2&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Auto Sync bridge-branch with main&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sync-with-main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="c1"&gt;# this ensures all histories are fetched for runner, avoiding `unrelated histories` merge error&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Merge changes into sprite-branch&lt;/span&gt;
      &lt;span class="c1"&gt;# GH actions/checkout requires git identity to be configured for security, top two commands handle that&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;git config user.name "${{ github.actor }}"&lt;/span&gt;
        &lt;span class="s"&gt;git config user.email "${{ github.actor }}@users.noreply.github.com"&lt;/span&gt;
        &lt;span class="s"&gt;git config pull.rebase false&lt;/span&gt;

        &lt;span class="s"&gt;git fetch origin&lt;/span&gt;
        &lt;span class="s"&gt;git checkout your-bridge-branch&lt;/span&gt;
        &lt;span class="s"&gt;git pull origin main&lt;/span&gt;
        &lt;span class="s"&gt;git merge --no-ff -m "auto sync main changes to bridge branch" main&lt;/span&gt;

        &lt;span class="s"&gt;git push origin your-bridge-branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The purpose of this "Bridge-Branch" is to act as an intermediary between feature development and &lt;code&gt;main&lt;/code&gt;. When I was first drafting up how to achieve this I was thinking: "I want a connection from my local feature-branch to main like a web socket, something that stays open so I can push in changes any time" 🤔🤔🤔&lt;/p&gt;

&lt;p&gt;And, because Unity converts Sprites to YAML if these changes go into our remote repository they would require a PR to be opened, reviewed, approved, merged and then closed. A lot of extra work and bottlenecking for a change that will have no significant impact on the code. All of the review of the actual design occurs in the Unity Editor where the Sprite can actually be viewed (as a &lt;code&gt;.png&lt;/code&gt; or checked out inside our Team's sprite editor Aseprite as a &lt;code&gt;.ase&lt;/code&gt; or &lt;code&gt;.aseprite&lt;/code&gt; per our workflow for art prototypes)&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bootstrap a repository from &lt;a href="https://github.com/colinwilliams91/helpful-gh-automations" rel="noopener noreferrer"&gt;my template&lt;/a&gt; OR create a &lt;code&gt;.github/workflows&lt;/code&gt; directory in your project root and copy/paste the two files I list below into it.&lt;/li&gt;
&lt;li&gt;Navigate to your projects "Settings" view on Github&lt;/li&gt;
&lt;li&gt;Click the "Actions" dropdown-section in the left-side toolbar&lt;/li&gt;
&lt;li&gt;Scroll to "Workflow permissions" and toggle "Read and write permissions" and "Allow GitHub Actions to create and approve pull requests"

&lt;ul&gt;
&lt;li&gt;You may need to perform this in the "Organization Settings" level above your repository if it is inside of one to establish default permissions across the group's repositories first&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Create a branch parallel to &lt;code&gt;main&lt;/code&gt; (like a &lt;code&gt;development&lt;/code&gt; or &lt;code&gt;staging&lt;/code&gt; branch) (this will be your "Bridge-Branch") on the remote&lt;/li&gt;

&lt;li&gt;Plug the name of that branch into the places where &lt;code&gt;your-bridge-branch&lt;/code&gt; exists in the &lt;a href="https://github.com/colinwilliams91/helpful-gh-automations/blob/main/.github/workflows/auto_merge_bridge_to_main.yaml" rel="noopener noreferrer"&gt;auto_merge_bridge_to_main.yaml&lt;/a&gt; file&lt;/li&gt;

&lt;li&gt;Plug the name of that branch into the places where &lt;code&gt;your-bridge-branch&lt;/code&gt; exists in the &lt;a href="https://github.com/colinwilliams91/helpful-gh-automations/blob/main/.github/workflows/auto_sync_main_to_bridge.yaml" rel="noopener noreferrer"&gt;auto_sync_main_to_bridge.yaml&lt;/a&gt; file&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Create a new feature branch

&lt;ul&gt;
&lt;li&gt;Make a small change like a "hello world" &lt;code&gt;.md&lt;/code&gt;, &lt;code&gt;.sh&lt;/code&gt; or any extremely simple file&lt;/li&gt;
&lt;li&gt;Push it to the remote&lt;/li&gt;
&lt;li&gt;Merge it into &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Checkout the "Bridge-Branch" you created to ensure those new changes are reflected on this branch as well&lt;/li&gt;

&lt;li&gt;While checked out on the "Bridge-Branch"

&lt;ul&gt;
&lt;li&gt;Make another small change&lt;/li&gt;
&lt;li&gt;Push that change to the remote "Bridge-Branch"&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Checkout &lt;code&gt;main&lt;/code&gt; to ensure those changes are reflected here&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;You should also be able to view the Actions working here: &lt;a href="https://github.com/your-organization?/your-repo/actions" rel="noopener noreferrer"&gt;https://github.com/your-organization?/your-repo/actions&lt;/a&gt; or by clicking the Actions tab on the view for your repo on GH.&lt;/p&gt;

</description>
      <category>github</category>
      <category>githubactions</category>
      <category>agile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Set up: Git and GH for Unity</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Mon, 29 Jan 2024 03:31:34 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/how-to-set-up-git-and-gh-for-unity-2198</link>
      <guid>https://forem.com/colin-williams-dev/how-to-set-up-git-and-gh-for-unity-2198</guid>
      <description>&lt;p&gt;With the provided Unity &lt;code&gt;.gitignore&lt;/code&gt; &lt;a href="https://github.com/github/gitignore/blob/main/Unity.gitignore" rel="noopener noreferrer"&gt;file available through GitHub&lt;/a&gt; you can easily collaborate with your favorite version control system (Git) and remote repositories on your game development projects. Following these easy steps you can set up a new Unity project and connect to a new remote repository on GitHub or even jump into a preexisting one quickly.&lt;/p&gt;

&lt;p&gt;Doing this will allow you to confidently branch your development and iteratively make changes like you would through the SDLC of any other application. The Unity &lt;code&gt;.gitignore&lt;/code&gt; preset includes all necessary files and excludes the large ones (would need Git LFS otherwise) that will be built by the Unity editor when you open the project anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to hook up Unity to Git/GH
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Make a Repository on GitHub (GH)

&lt;ul&gt;
&lt;li&gt;If you aren't using a template, make sure you select "Unity" &lt;code&gt;.gitignore&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Make a new project from Unity Hub (UH)

&lt;ul&gt;
&lt;li&gt;Make the name of the project the SAME as what the cloned down repo will be(should just be the name of the repo (&lt;em&gt;sans-"main"&lt;/em&gt;))&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Create a script in your new Unity project (within Unity) to open it with VS by double clicking it&lt;/li&gt;

&lt;li&gt;Open a terminal in the root folder in VS (your unity proj)&lt;/li&gt;

&lt;li&gt;Run &lt;code&gt;git init&lt;/code&gt; to initialize a git repo inside of it&lt;/li&gt;

&lt;li&gt;Create a &lt;code&gt;main&lt;/code&gt; branch if it isn't autogenerated from &lt;code&gt;git init&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;Run &lt;code&gt;git remote -v&lt;/code&gt; to see if &lt;code&gt;origin&lt;/code&gt; has been created automatically or not...&lt;/li&gt;

&lt;li&gt;If it HAS:

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;git remote set-url origin https://gihub.com/your/repo.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;to set the remote URL for origin to your clone URL for the GH repository&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Perform ONE of the FOLLOWING (&lt;strong&gt;A&lt;/strong&gt; or &lt;strong&gt;B&lt;/strong&gt;):&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;A:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;git pull origin main&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;B.1:&lt;/strong&gt; (&lt;em&gt;if you have anything in your Unity git repo that you need merged with the remote repo&lt;/em&gt;)

&lt;ul&gt;
&lt;li&gt;In the terminal where you ran &lt;code&gt;git init&lt;/code&gt; (the root of your Unity project) run &lt;code&gt;git checkout main&lt;/code&gt; (&lt;em&gt;if you are not already in main&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git branch -m main-holder&lt;/code&gt; to rename this branch temporarily&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git fetch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git checkout main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git pull origin main&lt;/code&gt; this will create a local main that matches the remote main and sync&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git merge main-holder --allow-unrelated-histories&lt;/code&gt; to merge anything you need from your local Unity project&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;If any of the above steps &lt;strong&gt;fail&lt;/strong&gt; and you don't want to debug... (&lt;strong&gt;experimental&lt;/strong&gt;)&lt;/em&gt; &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;B.2:&lt;/strong&gt; (only if &lt;strong&gt;B.1:&lt;/strong&gt; has failed)

&lt;ul&gt;
&lt;li&gt;In a new terminal or system explorer Navigate to the parent folder where your Unity project sits.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git clone https://gihub.com/your/repo.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the terminal where you ran &lt;code&gt;git init&lt;/code&gt; (the root of your Unity project) run &lt;code&gt;git checkout main&lt;/code&gt; (&lt;em&gt;if you are not already in main&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;git merge ../your-cloned-gh-repo/main --allow-unrelated-histories&lt;/code&gt; to merge the main from your local version of the remote GH repo into your local Unity project git repo you created&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Handle the merge into your Unity Project's git repository where the C# scripts will now be added by default from within the Unity Editor&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;If you found this helpful, check out/fork/clone the &lt;a href="https://github.com/colinwilliams91/unity-game-template" rel="noopener noreferrer"&gt;Unity Template Repository&lt;/a&gt; I made on GitHub and feel free to give it a star or like/share this blog :)&lt;/p&gt;

&lt;p&gt;#git #github #unity #setup #howto #how #to #tutorial #devops #agile #team #collab #collaborate #game #gamedev #development&lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>git</category>
      <category>github</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Catalogue of CLI commands and IDE keyboard shortcuts for a growing Developer</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Thu, 28 Dec 2023 02:34:31 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/catalogue-of-cli-commands-and-ide-keyboard-shortcuts-for-a-growing-developer-1c9d</link>
      <guid>https://forem.com/colin-williams-dev/catalogue-of-cli-commands-and-ide-keyboard-shortcuts-for-a-growing-developer-1c9d</guid>
      <description>&lt;p&gt;📓 📓 📓 &lt;/p&gt;

&lt;p&gt;So, I keep a running catalog of nearly every useful &lt;strong&gt;CLI command and IDE keyboard shortcut&lt;/strong&gt; I discover in a markdown file and it has not only optimized my development speed but saved me countless repeated stack overflow queries. (currently at 156 entries)&lt;/p&gt;

&lt;p&gt;I decided to host the file as a simple GitHub Pages site here: &lt;br&gt;
&lt;a href="https://colinwilliams91.github.io/exh-cli-cmd-md/" rel="noopener noreferrer"&gt;https://colinwilliams91.github.io/exh-cli-cmd-md/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did a little formatting and added a legend at the top for anyone who thinks this might help them, but it is still very much a rough "note" page*...&lt;/p&gt;

&lt;p&gt;*My secret is that I actually spent way too much time exploring (and failing...) at hosting/rendering a markdown file:&lt;/p&gt;

&lt;p&gt;**But, it was an opportunity I used to explore new things too.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;First time using .Net and React together to leverage a C# Library called &lt;code&gt;Markdig&lt;/code&gt; and &lt;code&gt;Markdig.ColorCode&lt;/code&gt; for making a Markdown Pipeline Builder to send a &lt;code&gt;.md&lt;/code&gt; file through &lt;code&gt;System.IO.File&lt;/code&gt; as a property of a Class and render to page. (abandoned with GitHub Pages Jekyll build failure...) ❌&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using Next.js and their MDX package (&lt;code&gt;.mdx&lt;/code&gt;) to embed markdown syntax alongside JSX and style with TailwindCSS (abandoned with un-ending &lt;code&gt;could not parse expression with acorn&lt;/code&gt; JS expression render errors...) ❌ &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using vanilla React and the React-Markdown library which is just a react component where the dev can wrap raw markdown syntax inside of JSX (same issue as above with Next.js. Basically, the React markdown processing gets messed up when there are too many curly braces for it to parse) ❌ &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hosting a static page with Amazon S3 (was hoping I could host a markdown file as an object in the bucket but turns out &lt;code&gt;.md&lt;/code&gt; files are significantly larger than .html files...) ❌ &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Hosted a public repo with a single file (my markdown notes file) and renamed the file to README so GitHub Pages would host it as the front page of the static site ✅😅&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>softwaredeveloper</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Powershell: Git &amp; .Net Tab Autocomplete Custom CLI</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Sun, 24 Dec 2023 23:09:54 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/powershell-git-net-tab-autocomplete-custom-cli-46pn</link>
      <guid>https://forem.com/colin-williams-dev/powershell-git-net-tab-autocomplete-custom-cli-46pn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Coming from WSL and Bash to a full Microsoft stack has had it's ups and downs. One of the more demoralizing moments was losing my really cute Oh My Zsh configured terminal migrating to *&lt;em&gt;Powershell *&lt;/em&gt;:'( &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;(tl;dr - &lt;a href="https://ohmyposh.dev/" rel="noopener noreferrer"&gt;prompt theme engine&lt;/a&gt; for Powershell or &lt;a href="https://github.com/colinwilliams91" rel="noopener noreferrer"&gt;my custom profile templates&lt;/a&gt; for more granular customization with less bloat)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On &lt;em&gt;top of that&lt;/em&gt; neither Git nor .Net have tab-key autocomplete built into their CLIs...&lt;/p&gt;

&lt;h4&gt;
  
  
  BUT THERE IS A SOLUTION!
&lt;/h4&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Enter: &lt;code&gt;$PROFILE&lt;/code&gt;&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;By default, your system will not have the file that is linked to this variable. If the Profile doesn't exist then create an empty text file named &lt;code&gt;Microsoft.PowerShell_profile.ps1&lt;/code&gt; (note: file extension should be &lt;code&gt;.ps1&lt;/code&gt;) at your PowerShell installation location e.g. &lt;code&gt;C:\Program Files\PowerShell\&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I suggest running this command inside Powershell so that your customization will persist wherever you want to use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$PROFILE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AllUsersAllHosts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;use your desired text editor (e.g. &lt;code&gt;notepad&lt;/code&gt; if you're a psychopath)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are different profiles for different scopes in PowerShell, and they are executed in a specific order. The common profiles include:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$PROFILE.AllUsersAllHosts&lt;/code&gt;: Applies to all users and all hosts (global profile, console, ISE, etc).&lt;br&gt;
&lt;code&gt;$PROFILE.AllUsersCurrentHost&lt;/code&gt;: Applies to all users but only for the current host.&lt;br&gt;
&lt;code&gt;$PROFILE.CurrentUserAllHosts&lt;/code&gt;: Applies to the current user but for all hosts.&lt;br&gt;
&lt;code&gt;$PROFILE&lt;/code&gt;: Applies only to the current user and the current host.&lt;/p&gt;

&lt;p&gt;Inside one of these profile scripts is where you can overwrite your default Powershell &lt;code&gt;prompt&lt;/code&gt; function. Without any modification it behaves like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt; "&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example of a very simple &lt;code&gt;prompt&lt;/code&gt; function I wrote. With this very bare bones set up you can customize the colors of the command line as well as the ASCII characters that are used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Random&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nx"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"::"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"\:: ~&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-NoNewLine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Color&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;the only available colors come from the .Net &lt;code&gt;ConsoleColor&lt;/code&gt; enumeration that I will list below&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0:  Black
1:  DarkBlue
2:  DarkGreen
3:  DarkCyan
4:  DarkRed
5:  DarkMagenta
6:  DarkYellow
7:  Gray
8:  DarkGray
9:  Blue
10: Green
11: Cyan
12: Red
13: Magenta
14: Yellow
15: White
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the &lt;code&gt;profile.ps1&lt;/code&gt; script that you create you can also &lt;code&gt;Import-Module posh-git&lt;/code&gt; to leverage their really intuitive Git status for the command line. See the Repo Docs/API link below in the References section for full description of how to install and customize. (I also include a step by step walkthrough in my &lt;a href="https://github.com/colinwilliams91/profiles" rel="noopener noreferrer"&gt;Github repository&lt;/a&gt; for PS profile templates)&lt;/p&gt;

&lt;p&gt;If you wish to explore this further, I made a public repo with some examples, comments, and links to further API and Documentation regarding everything custom PS Profile Scripts! 🐱‍🏍&lt;/p&gt;




&lt;h2&gt;
  
  
  References:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Git-Posh Module &lt;a href="https://github.com/dahlbyk/posh-git" rel="noopener noreferrer"&gt;Repo Docs/API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Oh My Posh &lt;a href="https://ohmyposh.dev/docs/" rel="noopener noreferrer"&gt;Prompt Theme Engine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>powershell</category>
      <category>windows</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Purposeful Programming Languages: Non-English Syntaxes</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Thu, 13 Apr 2023 14:58:50 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/purposeful-programming-languages-non-english-syntaxes-3ib1</link>
      <guid>https://forem.com/colin-williams-dev/purposeful-programming-languages-non-english-syntaxes-3ib1</guid>
      <description>&lt;p&gt;As a native English speaker I am very privileged to be able to jump into almost any programming language and have some basic understanding of the "human-readable" parts. There are a some exceptions in languages like Web Assembly where the constructions and operations can become quite esoteric and verbose where even a native English speaker might have no idea what they imply. With that being said, these ideas present the question of: how do non-native English speakers learn to code?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All modern programming tools are based on the ASCII character set, which encodes Latin Characters and was originally based on the English Language. As a result, programming has become tied to a single written culture. It carries with it a cultural bias that favors those who grew up reading and writing in that culture.&lt;br&gt;
&lt;a href="https://nas.sr/%D9%82%D9%84%D8%A8/" rel="noopener noreferrer"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Well, the first obvious answer is: they learn English as they learn the new programming language. In many parts of the world students train intensely to learn English throughout their grade-schools. Kudos to all of the international students who work hard and navigate this obstacle on top of the others they may face (us Americans are a pretty privileged lot when it comes to things like this...). &lt;/p&gt;

&lt;p&gt;Despite languages like &lt;em&gt;Python&lt;/em&gt; (syntax English, created in Holland), &lt;em&gt;Ruby&lt;/em&gt; (syntax English, created in Japan) and &lt;em&gt;Ruby on Rails&lt;/em&gt; (syntax English, created in Denmark) that were created in countries where English is &lt;em&gt;not&lt;/em&gt; the dominant language, the developers still elected to use English as their base syntax. As many of you are aware, English is the &lt;em&gt;de facto lingua franca&lt;/em&gt; of the world (actually makes me feel embarrassed at my own privilege once again...). With this in mind, programmers figured that due in part to the international level of digital era it would be best practice to use such a language as their model. As you could imagine, it would be quite difficult to debug your code base if your repository has chunks of it in different languages, potentially that you can't read or pronounce. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;BUT&lt;/strong&gt;&lt;/em&gt;,&lt;br&gt;
This still disadvantages people who do not speak English or are learning it as a second language. Because of this, there have been a variety of tools to help with this and I will demonstrate a few of them in the following:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;a href="https://citrine-lang.org/" rel="noopener noreferrer"&gt;Citrine&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In an attempt to "democratize software" a flexible, general purpose, scripting language was created. A team of three developers (de Mooji from Holland, Jilani from India, and Litwinow from Russia) released their free and &lt;a href="https://github.com/gabordemooij/citrine" rel="noopener noreferrer"&gt;open source&lt;/a&gt; language &lt;a href="https://citrine-lang.org/" rel="noopener noreferrer"&gt;Citrine&lt;/a&gt;.&lt;/p&gt;

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

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

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

&lt;p&gt;According to their website: "Citrine can be used as a stand-alone programming language, an embedded scripting language, DSL or as a transpiler". It seems to have similarities to JavaScript for prototypal inheritance and object orientation. They also claim to be like BASIC and LISP as well. But, coolest of all is Citrine's automatic translation, allowing any member of your team to write in their native language: &lt;a href="https://citrine-lang.org/docs/en/27c4553ad78ede1de736f6aa4b3bbf6e0dd05c24.html?v2023" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach seemed to be the most-encompassing but there are also other languages I have found that provide non-English syntax. Some of them are:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://nas.sr/%D9%82%D9%84%D8%A8/" rel="noopener noreferrer"&gt;Qalb&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Qalb is the Arabic word for heart and is also the only Arabic programming language: &lt;a href="https://nas.sr/%D9%82%D9%84%D8%A8/" rel="noopener noreferrer"&gt;nas.sr/قلب/&lt;/a&gt; Created by developer Ramsey Nasser with the intention of creating an art-forward, functional language: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Arabic people have a very intense relationship to text, and a lot of the culture is predicated on language itself. So I wanted to bring as much of the Arab tradition of calligraphy into the computer science tradition of text and source code.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.arabamerica.com/qalb-the-only-arabic-programming-language/" rel="noopener noreferrer"&gt;Interview with Nasser&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Qalb is an attempt at making computer programming more approachable in the Arab world. Nasser hopes that Qalb's popularity will spread within the academic scope and make it one step easier for native Arabic speakers to see if they like writing code as well as to hold onto their culture as they enter the professional space.&lt;/p&gt;

&lt;p&gt;Qalb is said to have similarities to LISP, C++ and JavaScript. As you can see from the banner image above and below, the code &lt;em&gt;really&lt;/em&gt; looks beautiful.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  &lt;a href="https://wy-lang.org/" rel="noopener noreferrer"&gt;Wenyan-lang&lt;/a&gt;: 文言
&lt;/h2&gt;

&lt;p&gt;Another case for non-English programming languages is: &lt;a href="https://spectrum.ieee.org/classical-chinese" rel="noopener noreferrer"&gt;wenyan-lang&lt;/a&gt;: "an esoteric programming language that closely follows the grammar and tone of classical Chinese literature." After reading into its creator, Lingdong Huang, I would say this language appeals to the opposite approach of what I have mentioned above. That being, a syntax that is less familiar to all, even to contemporary Chinese/Mandarin speakers, in an effort to celebrate the history and artistry of a traditional form instead. In a way, using an esoteric language achieves a similar effect, that is; the crossover of ancient Chinese speakers and software engineers is &lt;em&gt;so&lt;/em&gt; small that it evens the playing field much to the same effect as using a syntax that everyone knows. No one knowing a language is the logical inverse of everyone knowing one. &lt;/p&gt;

&lt;p&gt;Wenyan-lang is a difficult language to make comparisons to because traditional Chinese does not use spaces to divide sentences into words. Therefore, "how to split a sentence is the task of the reader... not the writer." This resulted in Huang making the wenyan-lang interpreter go from the longest keywords to the shortest keywords to determine what the programmer wants, "and it works", as he claimed in this &lt;a href="https://spectrum.ieee.org/classical-chinese" rel="noopener noreferrer"&gt;interview&lt;/a&gt;. &lt;br&gt;
Here is a link to its online IDE: &lt;a href="https://ide.wy-lang.org/" rel="noopener noreferrer"&gt;wenyan-lang&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5bl3iiykkrdpju10vps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5bl3iiykkrdpju10vps.png" alt="chineseHW" width="800" height="177"&gt;&lt;/a&gt;&lt;br&gt;
Above is an example of using &lt;code&gt;.wy&lt;/code&gt; to write "Hello World" and below is how it currently compiles to JavaScript, Python or Ruby.&lt;/p&gt;

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

&lt;p&gt;Furthermore, an example of the programming window using the Sieve of Eratosthenese in the language's traditional Chinese Characters and classical Chinese grammar. The language's developer Huang is fascinated by the algorithms of ancient Chinese math and philosophy books and wanted to create a language that maintained the affects of those works. &lt;/p&gt;

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

&lt;p&gt;The world of non-English software development is expanding and it is a profound and democratic evolution. If you are interested in more, please follow the links I have provided in the Works Cited section to contribute, learn and share. &lt;/p&gt;

&lt;h2&gt;
  
  
  Works Cited
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Citrine&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://citrine-lang.org/" rel="noopener noreferrer"&gt;language&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/gabordemooij/citrine" rel="noopener noreferrer"&gt;contributions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Qalb&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://nas.sr/%D9%82%D9%84%D8%A8/" rel="noopener noreferrer"&gt;language&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.arabamerica.com/qalb-the-only-arabic-programming-language/" rel="noopener noreferrer"&gt;interview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wenyan-lang&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://wy-lang.org/" rel="noopener noreferrer"&gt;language&lt;/a&gt;&lt;br&gt;
&lt;a href="https://spectrum.ieee.org/classical-chinese" rel="noopener noreferrer"&gt;interview&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Testing w/ Vitest w/o Vite</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Mon, 13 Mar 2023 02:18:55 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/testing-w-vitest-wo-vite-5820</link>
      <guid>https://forem.com/colin-williams-dev/testing-w-vitest-wo-vite-5820</guid>
      <description>&lt;p&gt;Vitest is an ultra fast and lightweight Unit Test Framework that extends the Jest API. Because of this, it is has a super intuitive set up and familiar syntax. In the following blog I will show you how to get it up and running in a project that isn't even using the Vite bundler! I know a lot of us know and love webpack, so if you're like me (and have already dedicated a hundred hours to learning how to use webpack...) and you don't necessarily want to jump ships to a different bundler, but, you &lt;em&gt;do&lt;/em&gt; want to use something more modern than Jest for your testing; than this blog should be helpful in explaining how to do so. Let's get started:&lt;/p&gt;

&lt;p&gt;Along with the qualities I mentioned above, the fast and lightweight aspects of Vitest, there are other reasons we will enjoy using Vitest. A couple of these reasons are the following: The first, and my favorite, is its out-of-the-box compatibility with ES6, JSX and TypeScript; and it's easily configurable reporting and watching. I will touch on the latter of those two a bit later.&lt;/p&gt;

&lt;h2&gt;
  
  
  SETUP
&lt;/h2&gt;

&lt;p&gt;If you &lt;em&gt;are&lt;/em&gt; using Vite, this part will truly be a breeze. If you are using &lt;code&gt;npm&lt;/code&gt; simply run this command in your terminal:&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;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;D&lt;/span&gt; &lt;span class="nx"&gt;vitest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using Vite then you will already have a &lt;code&gt;vite.config.ts&lt;/code&gt; file and Vitest will use that configuration file to execute alias configuration, as well as, whatever plugins are needed. This will happen automatically with installation. However, and this is what we will mostly focus on, if you are using a different bundler than you will want to create a &lt;code&gt;vitest.config.ts&lt;/code&gt; file that we will configure if we need specific changes to be made to our testing framework. If you happen to be using Vite and you create a &lt;code&gt;vitest.config.ts&lt;/code&gt; file it will take priority over the &lt;code&gt;vite.config.ts&lt;/code&gt; file. Either way, for developers using webpack or rollup or something other than Vite we will be creating a file that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vitest.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&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;vitest/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;globals&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="c1"&gt;// &amp;lt;-- **&lt;/span&gt;
    &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;reporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;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;// &amp;lt;-- ***&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;** --&amp;gt; This line will allow us to use the methods from the Vitest library without specifically importing which ones we need (for those who are familiar with Jest if will behave similarly this way).&lt;br&gt;
*** --&amp;gt; This &lt;code&gt;coverage&lt;/code&gt; object and the &lt;code&gt;reporter&lt;/code&gt; array is where we can pass values to influence how the tests display their responses. In this case, I have set the reporter to return and &lt;code&gt;html&lt;/code&gt; file which we can access with our method of choice in our browser. This will make a very human readably interface that is good for teams or collaborator who want to share testing.&lt;/p&gt;

&lt;p&gt;Next, we head over to our &lt;code&gt;package.json&lt;/code&gt; to add our script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// package.json&lt;/span&gt;
&lt;span class="cm"&gt;/*...*/&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scripts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt;
   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vitest --coverage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- **&lt;/span&gt;
   &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="cm"&gt;/*...*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;** --&amp;gt; This &lt;code&gt;--coverage&lt;/code&gt; flag enables our modifications from our config file to be applied.&lt;/p&gt;

&lt;p&gt;With that script created we can simply enter &lt;code&gt;npm run test&lt;/code&gt; to fire our tests. The &lt;code&gt;--watch&lt;/code&gt; flag you may be familiar with is applied to Vitest by default so we don't have to include it in our script. &lt;/p&gt;

&lt;h2&gt;
  
  
  TESTING
&lt;/h2&gt;

&lt;p&gt;With that simple set up complete we can now write and execute some tests! I will include a couple example tests and you will notice how familiar they look. But, first, we need some code to test. Here is an example file called: &lt;code&gt;sum.ts&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="c1"&gt;// sum.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&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;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cur&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;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a extremely simple function that is just a reducer. It will work for our purposes as I can pass 1 in instead of 0 to make the test behave differently than our tests would expect if I want to see a what a failing test looks like.&lt;/p&gt;

&lt;p&gt;Now that we have some code, let's write some tests!&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;sum&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;./sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&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;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- **&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#sum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 0 with no numbers input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns same number with one number input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns sum with nultiple numbers input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="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;** --&amp;gt; we do not need this line if we pass the &lt;code&gt;globals: true&lt;/code&gt; property to our &lt;code&gt;vitest.config.ts&lt;/code&gt; file like we did above, but this is the best practice as it exposes intention and explicit usage.&lt;/p&gt;

&lt;p&gt;You probably recognize this syntax as it is largely the same as Jest and designed to be human-readable. The test-naming convention is to put a hasthag &lt;code&gt;#&lt;/code&gt; in front of the string that represents the name of the function we are testing, in this case: &lt;code&gt;'#sum'&lt;/code&gt;. Following that, we pass a callback as our second argument. This callback will contain the &lt;code&gt;it&lt;/code&gt; invocation which takes a string which describes what we will expect and another callback. Inside the &lt;code&gt;it&lt;/code&gt; callback we invoke our imported &lt;code&gt;expect&lt;/code&gt; method and the &lt;code&gt;toBe&lt;/code&gt; method which is available without explicit importing. &lt;code&gt;expect&lt;/code&gt; takes what we want to test as an argument, in this case an invocation of our &lt;code&gt;sum()&lt;/code&gt; function with a given argument. Then, &lt;code&gt;toBe()&lt;/code&gt; gets invoked and is passed what we want to see as the result of our tested function. We can then run our test script and see if our function behaves appropriately! &lt;/p&gt;

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

&lt;p&gt;If you don't already have &lt;code&gt;c8&lt;/code&gt; installed running our Vitest script with the &lt;code&gt;coverage&lt;/code&gt; options we added will prompt you to do so. Once you have it installed we will see these results (the bottom added by the "text" value added and &lt;code&gt;coverage&lt;/code&gt; directory added to our root containing the aforementioned &lt;code&gt;index.html&lt;/code&gt; coverage file for serving. &lt;/p&gt;

&lt;p&gt;As I mentioned above, if I change the 0 to a 1 in my &lt;code&gt;sum&lt;/code&gt; function my tests will fail and this is what it will look like:&lt;/p&gt;

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

&lt;p&gt;As you can see, the results are clear and easy to comprehend and with a blistering fast response of 21ms and 30ms it is really a clear successor to its ancient testing framework associates. So, with this information under your belt, go give Vitest a try! I have found it very intuitive and rewarding to integrate into my projects. Happy coding!&lt;/p&gt;

</description>
      <category>vite</category>
      <category>testing</category>
    </item>
    <item>
      <title>Adding Sound to JS</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Sun, 26 Feb 2023 23:49:33 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/adding-sound-to-js-b3i</link>
      <guid>https://forem.com/colin-williams-dev/adding-sound-to-js-b3i</guid>
      <description>&lt;p&gt;I am a lover of video games. I jump at every chance I get to incorporate video game compatibility to what I am learning in web development. Video games are complex and what separates the good from the bad is manifold. During my research I have encountered terms like "juice" which are "simple visual enhancements that make the game 'feel' good." (see my other blog &lt;a href="https://dev.to/colinwilliams91/gamify-js-resources-for-easy-browser-roguelike-creation-2b16"&gt;here:&lt;/a&gt;). So, with that in mind, we need to consider the user experience while building our games. This experience stretches beyond the pillars of plot or gameplay mechanics and fractals out into minutia. Those minute details are often sensory, visually; like with "juice", or auditory; as we will explore below. For today's blog, I want to explore the latter and how the developer can intuitively create and add sounds to any JavaScript project using two libraries: &lt;a href="https://sfxr.me/" rel="noopener noreferrer"&gt;JSFXR&lt;/a&gt; and &lt;a href="https://howlerjs.com/" rel="noopener noreferrer"&gt;Howler&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSFXR
&lt;/h2&gt;

&lt;p&gt;JSFXR is an online 8 bit sound maker and sfx generator. It features a super simple GUI for users to customize and create any lofi video game sound they could need: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jbql42f02y5r3ju7flr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jbql42f02y5r3ju7flr.png" alt="Image of JSFXR Graphical Interface" width="698" height="735"&gt;&lt;/a&gt;&lt;br&gt;
With this free-to-use tool the developer can generate wave (.WAV) files that they can then host in their project environment or externally with a resource such as &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;cloudinary&lt;/a&gt;. The sliders that JSFXR provides are very intuitive and robust, providing an interface for the user to create and manipulate detailed 8-bit sounds. JSFXR is maintained by a single developer named Chris McCormick. He is super informative and often posts youtube videos teaching developers how they can create games (often roguelikes...) with JavaScript or Python. I had the pleasure of speaking with him via Github Issues where he helped walk me through a struggle I was having. He responded within &lt;em&gt;two minutes!&lt;/em&gt; of my late-night post! Incredible! Kudos to you Chris! The JSFXR team also thanks "Dr. Petter for inventing sfxr" and "Eric Fredricksen for creating [the sfxr port to JS]".&lt;/p&gt;
&lt;h2&gt;
  
  
  HOWLER
&lt;/h2&gt;

&lt;p&gt;Once we have all of the sounds we want created we can then move back into our code editor. We will install Howler via npm: &lt;code&gt;npm i howler&lt;/code&gt; and obtain the super lightweight library: "7KB gzipped and is 100% JavaScript with no outside dependencies or plugins". The Howler team also boasts their library will work anywhere as it "...defaults to Web Audio and falls back to HTML5 Audio to provide full coverage across all browsers and platforms". This is great because between HTML5 and the Web Audio API most web-based audio options are covered. Using this library will allow the user a more intuitive process to curating where and how they want their sounds to be executed. Without this library, the developer will need to learn the syntax for Web Audio API and HTML5 with the DOM. It is wise to go down this path and learn the ropes, but once we are working with a front end library such as React that abstracts away the DOM for their own virtual DOM we want to avoid using the &lt;code&gt;document&lt;/code&gt; where we can to avoid availability and consistency problems. Furthermore, using a library like Howler that abstracts the process of connecting all the wires from our .wav files to the DOM makes working with sound files in JS a much more intuitive experience. &lt;/p&gt;
&lt;h2&gt;
  
  
  EXAMPLES
&lt;/h2&gt;

&lt;p&gt;The following will be my most recent use of JSFXR and HOWLER in a project: &lt;br&gt;
&lt;code&gt;src/client/utility/sounds.ts&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Howl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Howler&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;howler&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;We import &lt;code&gt;Howl&lt;/code&gt; which will be our sound constructor and &lt;code&gt;Howler&lt;/code&gt; as the library/namespace with methods for the sound execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dodgeUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://res.cloudinary.com/version/path/to/file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We grab our hosted .wav file from cloudinary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dodge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Howl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;dodgeUrl&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we create our sound by linking the URL to cloudinary to the src of our new sound. We pass the URL inside an array because we can actually have multiple sound files inside the src. This allows for optimization depending on if a browser needs certain audio file types: (.mp3 vs .wav etc). There are even more ways to customize EACH sound we make with Howler like the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Howl&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sound.webm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sound.mp3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sound.wav&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;autoplay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Finished!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, we can effect the master volume of all of our sounds directly on the Howler object like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Howler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine how easily you can program a slider on the user side with this method!&lt;/p&gt;

&lt;p&gt;Next is where and how we want our sounds to fire! For my project, I needed a sound to play when a user clicked a button. This button lived in my front end React component: GameView.tsx.&lt;br&gt;
&lt;code&gt;src/client/components/gameView/GameView.tsx&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="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dodge&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;../../utility/sounds&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;GameView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HudButton&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;dodge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&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;Continue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/HudButton&lt;/span&gt;&lt;span class="err"&gt;&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;And that's it! We simply import the sound and call &lt;code&gt;.play()&lt;/code&gt; on it inside a react onClick attribute! There are other methods we can use as well such as &lt;code&gt;.fade()&lt;/code&gt; or &lt;code&gt;.rate()&lt;/code&gt; to further process or manipulate not only each sound but each instance a sound is fired!&lt;br&gt;
Their docs are super user friendly and provide further insight into each use case: &lt;a href="https://github.com/goldfire/howler.js#documentation" rel="noopener noreferrer"&gt;here.&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="c1"&gt;// Play returns a unique Sound ID that can be passed&lt;/span&gt;
&lt;span class="c1"&gt;// into any method on Howl to control that specific sound.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dodge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&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;id2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dodge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Fade out the first sound and speed up the second.&lt;/span&gt;
&lt;span class="nx"&gt;dodge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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="nx"&gt;id1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;dodge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Howler is so intuitive and flexible! From this point we can designate the execution of our sounds anywhere in our application. Anywhere we can invoke a method we can now execute a sound. Even as asynchronous code is being processed we could execute any number of sounds in an order we design. &lt;/p&gt;

&lt;p&gt;In conjunction with JSFXR and Howler the developer can create an aural masterpiece with precision and ease. &lt;/p&gt;

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

&lt;h2&gt;
  
  
  WORKS CITED
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://howlerjs.com/" rel="noopener noreferrer"&gt;https://howlerjs.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/goldfire/howler.js#documentation" rel="noopener noreferrer"&gt;https://github.com/goldfire/howler.js#documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://sfxr.me/" rel="noopener noreferrer"&gt;https://sfxr.me/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chr15m/jsfxr#use" rel="noopener noreferrer"&gt;https://github.com/chr15m/jsfxr#use&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>puzzlegames</category>
    </item>
    <item>
      <title>Tailwind CSS: How To...</title>
      <dc:creator>colin-williams-dev</dc:creator>
      <pubDate>Mon, 30 Jan 2023 04:22:03 +0000</pubDate>
      <link>https://forem.com/colin-williams-dev/tailwind-css-how-to-22o1</link>
      <guid>https://forem.com/colin-williams-dev/tailwind-css-how-to-22o1</guid>
      <description>&lt;h2&gt;
  
  
  What is Tailwind CSS?
&lt;/h2&gt;

&lt;p&gt;Tailwind helps the developer by using in-line styling that acts like classes. I know personally that I loathe needing to jump around into different files to modify the style of components or elements I am working on in my JSX inside component files. So, let's get started on how to set it all up! Below is a detailed, step-by-step guide on how to get it all configured and up and running quickly! I will also include a couple examples of what you can achieve with Tailwind instead of the archaic, dusty, old CSS you know and... &lt;em&gt;love&lt;/em&gt;...&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;npm&lt;/span&gt; &lt;span class="nt"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;tailwindcss&lt;/span&gt;
&lt;span class="nt"&gt;npx&lt;/span&gt; &lt;span class="nt"&gt;tailwindcss&lt;/span&gt; &lt;span class="nt"&gt;init&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;will&lt;/span&gt; &lt;span class="nt"&gt;initialize&lt;/span&gt; &lt;span class="nt"&gt;tailwind&lt;/span&gt;&lt;span class="nc"&gt;.config.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This last CLI command will generate a file (tailwind.config.js) to configure how Tailwind will be used in our project! The documentation describes this as setting up your "template paths".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- default empty object&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;   
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(this config file is actually 'optional' and will fallback to the tailwind default config if not initialized:) &lt;a href="https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js" rel="noopener noreferrer"&gt;https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For users who want to scaffold or add code into this default file use the command: &lt;code&gt;npx tailwindcss init --full&lt;/code&gt;&lt;br&gt;
For simplicity's sake I like to just &lt;code&gt;init&lt;/code&gt; the file and keep it small and specific to what I want to customize as to avoid any potential issues since our project will reference our config file and the Tailwind full config file upon necessary lookup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- files that contain Tailwind Class Names&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/**/*.{html,js}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/**/*.{html,js}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;content&lt;/code&gt; array is where we will want to use Tailwind.&lt;/p&gt;

&lt;p&gt;Below, we will create the styling we want applied through Tailwind.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// &amp;lt;-- where we will define our default styles&lt;/span&gt;
    &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1fb6ff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;purple&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#7e5bef&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ff49db&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;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;sans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Graphik&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sans-serif&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;serif&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Merriweather&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;serif&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;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;8xl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;96rem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;9xl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;128rem&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;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;4xl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2rem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a useful trick for if you intend to use Tailwind on top of basic CSS as well is to create a &lt;code&gt;prefix&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tw-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case Tailwind will automatically apply &lt;code&gt;tw-&lt;/code&gt; to all of its classes to avoid any conflicts if your CSS accidentally share the same names!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.tw-text-left&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tw-text-center&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.tw-text-right&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;/* etc. */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Once you have finished your tailwind.config.js file...&lt;/strong&gt;&lt;br&gt;
You can navigate to your main CSS file and add the &lt;code&gt;@tailwind&lt;/code&gt; directives to make sure the Tailwind classes and utilities will work in your project!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@tailwind&lt;/span&gt; &lt;span class="n"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much like how we use Babel with React and ES6 we should use the Tailwind build process to transpile our Tailwind utility classes into standard CSS styles that the browser will more easily understand. To do this, we run this CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;npx&lt;/span&gt; &lt;span class="nt"&gt;tailwindcss&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="nc"&gt;.css&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="o"&gt;./&lt;/span&gt;&lt;span class="nt"&gt;dist&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;output&lt;/span&gt;&lt;span class="nc"&gt;.css&lt;/span&gt; &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now will have a processed and transformed output.css file that will come from whatever we use in our input directory with all the fun Tailwind stuff that the browser will have no problem reading. The &lt;code&gt;--watch&lt;/code&gt; flag ensures that each time we make a change to our input file it will transpile and update our output file.&lt;/p&gt;

&lt;p&gt;We should also grab Tailwind's handy VSCode extension which will make our lives SO much easier with auto-complete IntelliSense for our new utilities and classes (just like with CSS). It will help when writing out directives as well as Syntax Highlighting and Linting. &lt;/p&gt;

&lt;p&gt;Search for Tailwind CSS IntelliSense inside your VSCode and install the one with 2.4M+ downloads from Tailwind Labs!&lt;/p&gt;

&lt;p&gt;You may need to go to your VSCode settings ( &lt;code&gt;ctrl ,&lt;/code&gt;) type in &lt;code&gt;editor.quickSuggestions&lt;/code&gt; and open the JSON file. You will see a JSON object that you can edit. Add the following for string suggestions: (and the last bit if you wish to use Tailwind by default...)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;editor.quickSuggestions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;other&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;on&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;comments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;off&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;--&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;files.associations&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tailwindcss&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- to set Tailwind to default&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, we just &lt;code&gt;link&lt;/code&gt; our Tailwind output file in the head of our HTML file and BOOM! (I'll include a basic example of an in-line styled DOM element as well)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/dist/output.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; // &lt;span class="nt"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="na"&gt;output&lt;/span&gt;
&lt;span class="err"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="na"&gt;head&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-3xl font-bold underline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; // &lt;span class="nt"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;b&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;u&amp;gt;&amp;lt;/u&amp;gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
    Hello world!
  &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;Look at how easily we can style an element right in our markup!&lt;br&gt;
Below we create a class, give it a color and an aspect (shape and position) all in-line!!&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bg-sky-50 aspect-square&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSS equivalent would like something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.sky-50&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#E1F8F9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="err"&gt;Sky&lt;/span&gt; &lt;span class="err"&gt;50&lt;/span&gt; 
    &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="err"&gt;aspect-square&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="err"&gt;aspect-square&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yuck!&lt;/p&gt;

&lt;p&gt;If you're like me and are sick and tired of dealing with how verbose CSS directives feel, or how annoying it is to navigate to different style-sheet files in other directories, I hope you all give Tailwind a shot. It is super user friendly and provides such an easier and quicker time to customize your elements right inside your HTML or JSX! No more esoteric &lt;code&gt;#E1F8F9&lt;/code&gt; and hideous CSS properties you have to look up on W3 schools! Yay!&lt;/p&gt;

&lt;p&gt;Tailwind is also mobile first! So, it makes building for phone-apps easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Works Cited:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;https://tailwindcss.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/developedbyed/react-portofolio-with-tailwind" rel="noopener noreferrer"&gt;https://github.com/developedbyed/react-portofolio-with-tailwind&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>tailwindcss</category>
    </item>
  </channel>
</rss>
