DEV Community: Ramu Narasinga The latest articles on DEV Community by Ramu Narasinga (@ramunarasinga). https://dev.to/ramunarasinga 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%2F1119879%2F345b9f00-b4b0-4e4c-9377-d1f265bad0e4.jpeg DEV Community: Ramu Narasinga https://dev.to/ramunarasinga en Use degit to download a template in your CLI tool. Ramu Narasinga Fri, 16 Aug 2024 20:05:33 +0000 https://dev.to/ramunarasinga/use-degit-to-download-a-template-in-your-cli-tool-6hg https://dev.to/ramunarasinga/use-degit-to-download-a-template-in-your-cli-tool-6hg <p>I found a file named "degit" in the <a href="proxy.php?url=https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/degit.ts" rel="noopener noreferrer">Remotion source code</a>. <br> <a href="proxy.php?url=https://www.remotion.dev/" rel="noopener noreferrer">Remotion</a> helps you make videos programatically.</p> <p>In this article, we will look at the following concepts:</p> <ol> <li>What is Degit?</li> <li>Build a simple degit function inspired by <a href="proxy.php?url=https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/degit.ts" rel="noopener noreferrer">Remotion's degit file</a> </li> </ol> <h1> What is Degit? </h1> <p>I do remember seeing "degit" mentioned in one of the Readmes in the open source, but I could not recall which repository it was so I googled what a degit means and found this <a href="proxy.php?url=https://github.com/Rich-Harris/degit" rel="noopener noreferrer">degit npm package</a>.</p> <p>In simple terms, You can use degit to quickly make a copy of a Github repository by only downloading the latest commit <br> instead of the entire git history.</p> <p>Visit the official <a href="proxy.php?url=https://github.com/Rich-Harris/degit" rel="noopener noreferrer">npm package for degit</a> to read more about this package.</p> <p>You can use this degit package to download repos from Gitlab or Bitbucket as well so its not just limited to Github repositories.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code><span class="c"># download from GitLab</span> degit gitlab:user/repo <span class="c"># download from BitBucket</span> degit bitbucket:user/repo degit user/repo <span class="c"># these commands are equivalent</span> degit github:user/repo </code></pre> </div> <p>Here's a sample usage in Javascript:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>const degit <span class="o">=</span> require<span class="o">(</span><span class="s1">'degit'</span><span class="o">)</span><span class="p">;</span> const emitter <span class="o">=</span> degit<span class="o">(</span><span class="s1">'user/repo'</span>, <span class="o">{</span> cache: <span class="nb">true</span>, force: <span class="nb">true</span>, verbose: <span class="nb">true</span>, <span class="o">})</span><span class="p">;</span> emitter.on<span class="o">(</span><span class="s1">'info'</span>, info <span class="o">=&gt;</span> <span class="o">{</span> console.log<span class="o">(</span>info.message<span class="o">)</span><span class="p">;</span> <span class="o">})</span><span class="p">;</span> emitter.clone<span class="o">(</span><span class="s1">'path/to/dest'</span><span class="o">)</span>.then<span class="o">(()</span> <span class="o">=&gt;</span> <span class="o">{</span> console.log<span class="o">(</span><span class="s1">'done'</span><span class="o">)</span><span class="p">;</span> <span class="o">})</span><span class="p">;</span> </code></pre> </div> <h2> Build a simple degit function inspired by Remotion's degit file </h2> <p>To understand how to build a simple degit function, let’s break down the code from Remotion’s degit.ts file. This file implements a basic version of what the degit npm package does: fetching a GitHub repository’s latest state without downloading the full history.</p> <h3> 1. Imports used </h3> <div class="highlight js-code-highlight"> <pre class="highlight plaintext"><code>import https from 'https'; import fs from 'node:fs'; import {tmpdir} from 'node:os'; import path from 'node:path'; import tar from 'tar'; import {mkdirp} from './mkdirp'; </code></pre> </div> <ul> <li>https: Used to make a network request to fetch the repository.</li> <li>fs: Interacts with the file system, such as writing the downloaded files.</li> <li>tmpdir: Provides the system's temporary directory path.</li> <li>path: Handles and transforms file paths.</li> <li>tar: Extracts the contents of the tarball (compressed file).</li> <li>mkdirp: A helper function to create directories recursively, provided in a separate file.</li> </ul> <h3> 2: Fetching the Repository </h3> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">export</span> <span class="kd">function</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">dest</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="k">void</span><span class="o">&gt;</span><span class="p">((</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">https</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">code</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nx">statusCode</span> <span class="k">as</span> <span class="nx">number</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="nx">code</span> <span class="o">&gt;=</span> <span class="mi">400</span><span class="p">)</span> <span class="p">{</span> <span class="nf">reject</span><span class="p">(</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span> <span class="s2">`Network request to </span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2"> failed with code </span><span class="p">${</span><span class="nx">code</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">response</span><span class="p">.</span><span class="nx">statusMessage</span><span class="p">}</span><span class="s2">)`</span><span class="p">,</span> <span class="p">),</span> <span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">code</span> <span class="o">&gt;=</span> <span class="mi">300</span><span class="p">)</span> <span class="p">{</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">location</span> <span class="k">as</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">dest</span><span class="p">)</span> <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">resolve</span><span class="p">)</span> <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">reject</span><span class="p">);</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="nx">response</span> <span class="p">.</span><span class="nf">pipe</span><span class="p">(</span><span class="nx">fs</span><span class="p">.</span><span class="nf">createWriteStream</span><span class="p">(</span><span class="nx">dest</span><span class="p">))</span> <span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">finish</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="nf">resolve</span><span class="p">())</span> <span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">reject</span><span class="p">);</span> <span class="p">}</span> <span class="p">}).</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">reject</span><span class="p">);</span> <span class="p">});</span> <span class="p">}</span> </code></pre> </div> <ul> <li>URL Handling: The function checks if the request is successful (status codes below 300). If it’s a redirect (codes between 300 and 399), it follows the new URL. If it’s an error (codes 400+), it rejects the promise.</li> <li>File Saving: The repository is downloaded and saved to the dest path using fs.createWriteStream.</li> </ul> <h3> 3: Extracting the Repository </h3> <p>After downloading the repository, it’s necessary to extract the contents of the tarball:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">untar</span><span class="p">(</span><span class="nx">file</span><span class="p">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">dest</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">tar</span><span class="p">.</span><span class="nf">extract</span><span class="p">(</span> <span class="p">{</span> <span class="nx">file</span><span class="p">,</span> <span class="na">strip</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="na">C</span><span class="p">:</span> <span class="nx">dest</span><span class="p">,</span> <span class="p">},</span> <span class="p">[],</span> <span class="p">);</span> <span class="p">}</span> </code></pre> </div> <ul> <li>Tar Extraction: This function extracts the contents of the .tar.gz file into the specified destination directory.</li> </ul> <h3> 4: Putting It All Together </h3> <p>The main degit function ties everything together, handling directory creation, fetching, and extracting the repository:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">degit</span> <span class="o">=</span> <span class="k">async </span><span class="p">({</span> <span class="nx">repoOrg</span><span class="p">,</span> <span class="nx">repoName</span><span class="p">,</span> <span class="nx">dest</span><span class="p">,</span> <span class="p">}:</span> <span class="p">{</span> <span class="nl">repoOrg</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="nl">repoName</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="nl">dest</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">base</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="nf">tmpdir</span><span class="p">(),</span> <span class="dl">'</span><span class="s1">.degit</span><span class="dl">'</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">dir</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="nx">base</span><span class="p">,</span> <span class="nx">repoOrg</span><span class="p">,</span> <span class="nx">repoName</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">file</span> <span class="o">=</span> <span class="s2">`</span><span class="p">${</span><span class="nx">dir</span><span class="p">}</span><span class="s2">/HEAD.tar.gz`</span><span class="p">;</span> <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="s2">`https://github.com/</span><span class="p">${</span><span class="nx">repoOrg</span><span class="p">}</span><span class="s2">/</span><span class="p">${</span><span class="nx">repoName</span><span class="p">}</span><span class="s2">/archive/HEAD.tar.gz`</span><span class="p">;</span> <span class="nf">mkdirp</span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nf">dirname</span><span class="p">(</span><span class="nx">file</span><span class="p">));</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">file</span><span class="p">);</span> <span class="nf">mkdirp</span><span class="p">(</span><span class="nx">dest</span><span class="p">);</span> <span class="k">await</span> <span class="nf">untar</span><span class="p">(</span><span class="nx">file</span><span class="p">,</span> <span class="nx">dest</span><span class="p">);</span> <span class="nx">fs</span><span class="p">.</span><span class="nf">unlinkSync</span><span class="p">(</span><span class="nx">file</span><span class="p">);</span> <span class="p">};</span> </code></pre> </div> <p><a href="proxy.php?url=https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/mkdirp.ts" rel="noopener noreferrer">mkdirp</a> is used to create<br> a directories recursively. </p> <h2> Conclusion: </h2> <p>I found that remotion uses degit to download templates when you run their installation commmand:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight shell"><code>npx create-video@latest </code></pre> </div> <p>This command asks you to choose a template, this is where degit comes into action to download<br> the latest commit of the selected template</p> <p>You can check this code from <a href="proxy.php?url=https://github.com/remotion-dev/remotion/blob/c535e676badd055187d1ea8007f9ac76ab0ad315/packages/create-video/src/init.ts#L109" rel="noopener noreferrer">create-video package</a> for proof.</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li><a href="proxy.php?url=https://github.com/Rich-Harris/degit" rel="noopener noreferrer">https://github.com/Rich-Harris/degit</a></li> <li><a href="proxy.php?url=https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/degit.ts" rel="noopener noreferrer">https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/degit.ts</a></li> <li><a href="proxy.php?url=https://github.com/remotion-dev/remotion/blob/c535e676badd055187d1ea8007f9ac76ab0ad315/packages/create-video/src/init.ts#L109" rel="noopener noreferrer">https://github.com/remotion-dev/remotion/blob/c535e676badd055187d1ea8007f9ac76ab0ad315/packages/create-video/src/init.ts#L109</a></li> <li><a href="proxy.php?url=https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/mkdirp.ts" rel="noopener noreferrer">https://github.com/remotion-dev/remotion/blob/main/packages/create-video/src/mkdirp.ts</a></li> </ol> javascript opensource git tarball Supabase uses Valtio for its state management Ramu Narasinga Fri, 09 Aug 2024 22:32:08 +0000 https://dev.to/ramunarasinga/supabase-uses-valtio-for-its-state-management-cpf https://dev.to/ramunarasinga/supabase-uses-valtio-for-its-state-management-cpf <p>As I was reading the <a href="proxy.php?url=https://github.com/search?q=repo%3Asupabase%2Fsupabase+valtio&amp;type=code" rel="noopener noreferrer">Supabase source code</a> for “fun”, I came across a package named <a href="proxy.php?url=https://github.com/pmndrs/valtio" rel="noopener noreferrer">Valtio</a>.</p> <p>I visited Valtio repository and found this in the description:</p> <blockquote> <p><em>💊 Valtio makes proxy-state simple for React and Vanilla</em></p> </blockquote> <p>In this article, we will look at:</p> <ol> <li> Proxy object.</li> <li> Valtio usage with an example.</li> <li> An example use case found in Supabase source code.</li> </ol> <h2> Proxy object </h2> <p>Wait, what’s a Proxy? The Proxy object enables you to create a proxy for another object, which can intercept and redefine fundamental operations for that object. (<a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer">From the MDN docs</a>). Valtio internally uses <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer">Proxy</a> mechanism.</p> <p>The below example is picked from MDN docs:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Example 1:</span> <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="p">{</span> <span class="na">message1</span><span class="p">:</span> <span class="dl">"</span><span class="s2">hello</span><span class="dl">"</span><span class="p">,</span> <span class="na">message2</span><span class="p">:</span> <span class="dl">"</span><span class="s2">everyone</span><span class="dl">"</span><span class="p">,</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">handler1</span> <span class="o">=</span> <span class="p">{};</span> <span class="kd">const</span> <span class="nx">proxy1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Proxy</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">handler1</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">proxy1</span><span class="p">.</span><span class="nx">message1</span><span class="p">);</span> <span class="c1">// hello</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">proxy1</span><span class="p">.</span><span class="nx">message2</span><span class="p">);</span> <span class="c1">// everyone</span> <span class="c1">// Example 2:</span> <span class="kd">const</span> <span class="nx">target</span> <span class="o">=</span> <span class="p">{</span> <span class="na">message1</span><span class="p">:</span> <span class="dl">"</span><span class="s2">hello</span><span class="dl">"</span><span class="p">,</span> <span class="na">message2</span><span class="p">:</span> <span class="dl">"</span><span class="s2">everyone</span><span class="dl">"</span><span class="p">,</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">handler2</span> <span class="o">=</span> <span class="p">{</span> <span class="nf">get</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">prop</span><span class="p">,</span> <span class="nx">receiver</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="dl">"</span><span class="s2">world</span><span class="dl">"</span><span class="p">;</span> <span class="p">},</span> <span class="p">};</span> <span class="kd">const</span> <span class="nx">proxy2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Proxy</span><span class="p">(</span><span class="nx">target</span><span class="p">,</span> <span class="nx">handler2</span><span class="p">);</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">proxy2</span><span class="p">.</span><span class="nx">message1</span><span class="p">);</span> <span class="c1">// world</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">proxy2</span><span class="p">.</span><span class="nx">message2</span><span class="p">);</span> <span class="c1">// world</span> </code></pre> </div> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <h2> Valtio usage with an example. </h2> <p>The following demonstration is picked from the docs.</p> <h3> Install </h3> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nx">npm</span> <span class="nx">i</span> <span class="nx">valtio</span> </code></pre> </div> <h3> Wrap your state object </h3> <p>Valtio turns the object you pass it into a self-aware proxy.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">proxy</span><span class="p">,</span> <span class="nx">useSnapshot</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">valtio</span><span class="dl">'</span> <span class="kd">const</span> <span class="nx">state</span> <span class="o">=</span> <span class="nf">proxy</span><span class="p">({</span> <span class="na">count</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="na">text</span><span class="p">:</span> <span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span> <span class="p">})</span> </code></pre> </div> <h3> Mutate from anywhere </h3> <p>You can make changes to it in the same way you would to a normal js-object.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="nf">setInterval</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="o">++</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span> <span class="p">},</span> <span class="mi">1000</span><span class="p">)</span> </code></pre> </div> <h3> React via useSnapshot </h3> <p>Create a local snapshot that catches changes. Rule of thumb: read from snapshots in render function, otherwise use the source. The component will only re-render when the parts of the state you access have changed, it is render-optimized.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// This will re-render on \`state.count\` change but not on \`state.text\` change</span> <span class="kd">function</span> <span class="nf">Counter</span><span class="p">()</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">snap</span> <span class="o">=</span> <span class="nf">useSnapshot</span><span class="p">(</span><span class="nx">state</span><span class="p">)</span> <span class="k">return </span><span class="p">(</span> <span class="o">&lt;</span><span class="nx">div</span><span class="o">&gt;</span> <span class="p">{</span><span class="nx">snap</span><span class="p">.</span><span class="nx">count</span><span class="p">}</span> <span class="o">&lt;</span><span class="nx">button</span> <span class="nx">onClick</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="o">++</span><span class="nx">state</span><span class="p">.</span><span class="nx">count</span><span class="p">}</span><span class="o">&gt;+</span><span class="mi">1</span><span class="o">&lt;</span><span class="sr">/button</span><span class="err">&gt; </span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; </span> <span class="p">)</span> <span class="p">}</span> </code></pre> </div> <h2> An example use case found in Supabase source code </h2> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFbK-AGUq5QQQ%2Farticle-inline_image-shrink_1000_1488%2F0%2F1723241642296%3Fe%3D1728518400%26v%3Dbeta%26t%3D950CNIIGzbF26K0rTWjIQM6KwKoqztoUce8vuKU9fsQ" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFbK-AGUq5QQQ%2Farticle-inline_image-shrink_1000_1488%2F0%2F1723241642296%3Fe%3D1728518400%26v%3Dbeta%26t%3D950CNIIGzbF26K0rTWjIQM6KwKoqztoUce8vuKU9fsQ"></a></p> <p>I searched long and hard to find a simple, easy to understand example use case in <a href="proxy.php?url=https://github.com/search?q=repo%3Asupabase%2Fsupabase+valtio&amp;type=code" rel="noopener noreferrer">Supabase source code</a>.</p> <p>The example I picked is from <a href="proxy.php?url=https://github.com/supabase/supabase/blob/00385657e8da32535916969036bb4e76befc5a44/apps/studio/state/app-state.ts#L6" rel="noopener noreferrer">/apps/studio/state/app-state.ts</a></p> <p>app-state.ts has about 95 lines of code at the time of writing this article.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// pulled from https://github.com/supabase/supabase/blob/00385657e8da32535916969036bb4e76befc5a44/apps/studio/state/app-state.ts#L57-L60</span> <span class="nx">showAiSettingsModal</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">setShowAiSettingsModal</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">boolean</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nx">appState</span><span class="p">.</span><span class="nx">showAiSettingsModal</span> <span class="o">=</span> <span class="nx">value</span> <span class="p">},</span> </code></pre> </div> <p>showAiSettingsModal is found to be used in <a href="proxy.php?url=https://github.com/supabase/supabase/blob/00385657e8da32535916969036bb4e76befc5a44/apps/studio/components/ui/AISettingsModal.tsx#L20" rel="noopener noreferrer">/apps/studio/components/ui/AISettingsModal.tsx</a></p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQFfKGqlzUhyjA%2Farticle-inline_image-shrink_1000_1488%2Farticle-inline_image-shrink_1000_1488%2F0%2F1723241641336%3Fe%3D1728518400%26v%3Dbeta%26t%3DvEHsFlFQ_0opSO3cqR1dfsP0_Z2oedrmZmy3rgT9exI" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2Fv2%2FD4E12AQFfKGqlzUhyjA%2Farticle-inline_image-shrink_1000_1488%2Farticle-inline_image-shrink_1000_1488%2F0%2F1723241641336%3Fe%3D1728518400%26v%3Dbeta%26t%3DvEHsFlFQ_0opSO3cqR1dfsP0_Z2oedrmZmy3rgT9exI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/search?q=repo%3Asupabase%2Fsupabase+valtio&amp;type=code" rel="noopener noreferrer">https://github.com/search?q=repo%3Asupabase%2Fsupabase+valtio&amp;type=code</a> </li> <li> <a href="proxy.php?url=https://github.com/supabase/supabase/blob/00385657e8da32535916969036bb4e76befc5a44/apps/studio/state/app-state.ts#L6" rel="noopener noreferrer">https://github.com/supabase/supabase/blob/00385657e8da32535916969036bb4e76befc5a44/apps/studio/state/app-state.ts#L6</a> </li> <li> <a href="proxy.php?url=https://github.com/search?q=repo%3Asupabase/supabase%20appState&amp;type=code" rel="noopener noreferrer">https://github.com/search?q=repo%3Asupabase/supabase%20appState&amp;type=code</a> </li> <li> <a href="proxy.php?url=https://github.com/supabase/supabase/blob/00385657e8da32535916969036bb4e76befc5a44/apps/studio/components/ui/AISettingsModal.tsx#L20" rel="noopener noreferrer">https://github.com/supabase/supabase/blob/00385657e8da32535916969036bb4e76befc5a44/apps/studio/components/ui/AISettingsModal.tsx#L20</a> </li> <li> <a href="proxy.php?url=https://github.com/pmndrs/valtio" rel="noopener noreferrer">https://github.com/pmndrs/valtio</a> </li> <li> <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="noopener noreferrer">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy</a> </li> </ol> opensource statemanagement supabase valtio Comparing the copyToClipboard implementations in Shadcn-ui/ui and Codehike. Ramu Narasinga Wed, 07 Aug 2024 21:48:41 +0000 https://dev.to/ramunarasinga/comparing-the-copytoclipboard-implementations-in-shadcn-uiui-and-codehike-4kog https://dev.to/ramunarasinga/comparing-the-copytoclipboard-implementations-in-shadcn-uiui-and-codehike-4kog <p>In this article, we will compare the Copy button code between <a href="proxy.php?url=https://github.com/code-hike/codehike/blob/next/packages/mdx/src/smooth-code/copy-button.tsx#L3" rel="noopener noreferrer">Shadcn-ui/ui</a> and <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/components/copy-button.tsx#L31" rel="noopener noreferrer">Codehike</a>.</p> <h2> copyToClipboard in Shadcn-ui/ui </h2> <p>The code snippet below is picked from <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/components/copy-button.tsx#L24" rel="noopener noreferrer">shadcn-ui source code</a>.</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">copyToClipboardWithMeta</span><span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">event</span><span class="p">?:</span> <span class="nx">Event</span><span class="p">)</span> <span class="p">{</span> <span class="nb">navigator</span><span class="p">.</span><span class="nx">clipboard</span><span class="p">.</span><span class="nf">writeText</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="k">if </span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span> <span class="nf">trackEvent</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>I think ‘withMeta’ in the function name copyToClipboardWithMeta refers to the analytics recorded in the <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/events.ts#L27" rel="noopener noreferrer">trackEvent function</a>.</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="k">import</span> <span class="nx">va</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@vercel/analytics</span><span class="dl">"</span> <span class="k">export</span> <span class="kd">function</span> <span class="nf">trackEvent</span><span class="p">(</span><span class="nx">input</span><span class="p">:</span> <span class="nx">Event</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">event</span> <span class="o">=</span> <span class="nx">eventSchema</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">input</span><span class="p">)</span> <span class="k">if </span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span> <span class="nx">va</span><span class="p">.</span><span class="nf">track</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">properties</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre> </div> <h2> copyToClipboard in Codehike </h2> <p>The code snippet below is picked from <a href="proxy.php?url=https://github.com/code-hike/codehike/blob/next/packages/mdx/src/smooth-code/copy-button.tsx#L54" rel="noopener noreferrer">codehike source code</a>.</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="kd">function</span> <span class="nf">copyToClipboard</span><span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nb">navigator</span><span class="p">.</span><span class="nx">clipboard</span><span class="p">)</span> <span class="p">{</span> <span class="nf">fallbackCopyTextToClipboard</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="k">return</span> <span class="p">}</span> <span class="nb">navigator</span><span class="p">.</span><span class="nx">clipboard</span><span class="p">.</span><span class="nf">writeText</span><span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">}</span> </code></pre> </div> <p>Codehike implements the copyToClipboard differently.</p> <ol> <li> There is no analytics recorded when the copyToClipboard function is called like we saw this happening in Shadcn-ui/ui.</li> <li> There is a fallback method in case the navigation.clipboard API is not available.</li> </ol> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGMAJHHr6876Q%2Farticle-inline_image-shrink_1000_1488%2F0%2F1723066613850%3Fe%3D1728518400%26v%3Dbeta%26t%3DlmUDm82Txht8TLWn1GhAllRlKIuX_wC1RjPhc0fLH-E" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGMAJHHr6876Q%2Farticle-inline_image-shrink_1000_1488%2F0%2F1723066613850%3Fe%3D1728518400%26v%3Dbeta%26t%3DlmUDm82Txht8TLWn1GhAllRlKIuX_wC1RjPhc0fLH-E"></a></p> <h3> fallbackCopyTextToClipboard: </h3> <p>This below code snippet is picked from <a href="proxy.php?url=https://github.com/code-hike/codehike/blob/next/packages/mdx/src/smooth-code/copy-button.tsx#L62C1-L84C2" rel="noopener noreferrer">Codehike source code</a>. This function is just under the copyToClipboard.</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <p><span class="kd">function</span> <span class="nf">fallbackCopyTextToClipboard</span><span class="p">(</span><span class="nx">text</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span><br> <span class="kd">var</span> <span class="nx">textArea</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">createElement</span><span class="p">(</span><span class="dl">"</span><span class="s2">textarea</span><span class="dl">"</span><span class="p">)</span><br> <span class="nx">textArea</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">text</span></p> <p><span class="c1">// Avoid scrolling to bottom</span><br> <span class="nx">textArea</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">top</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><br> <span class="nx">textArea</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">left</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><br> <span class="nx">textArea</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">position</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">fixed</span><span class="dl">"</span></p> <p><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">textArea</span><span class="p">)</span><br> <span class="nx">textArea</span><span class="p">.</span><span class="nf">focus</span><span class="p">()</span><br> <span class="nx">textArea</span><span class="p">.</span><span class="nf">select</span><span class="p">()</span></p> <p><span class="k">try</span> <span class="p">{</span><br> <span class="kd">var</span> <span class="nx">successful</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">execCommand</span><span class="p">(</span><span class="dl">"</span><span class="s2">copy</span><span class="dl">"</span><span class="p">)</span><br> <span class="c1">// var msg = successful ? "successful" : "unsuccessful"</span><br> <span class="c1">// console.log("Fallback: Copying text command was " + msg)</span><br> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span><br> <span class="c1">// console.error("Fallback: Oops, unable to copy", err)</span><br> <span class="p">}</span></p> <p><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">removeChild</span><span class="p">(</span><span class="nx">textArea</span><span class="p">)</span><br> <span class="p">}</span></p> </code></pre> </div> <h2> <br> <br> <br> Conclusion:<br> </h2> <p>If I were to implement a copyToClipboard functionality, I would also add a fallback in case the navigator.clipboard is not available in a given browser like in Codehike and if you also use Vercel analytics in your application, you might as well record your analytics like in shadcn-ui/ui.</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Build open source projects like Shadcn-ui/ui from scratch</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/code-hike/codehike/blob/next/packages/mdx/src/mini-editor/editor-tween.tsx" rel="noopener noreferrer">https://github.com/code-hike/codehike/blob/next/packages/mdx/src/mini-editor/editor-tween.tsx</a> </li> <li> <a href="proxy.php?url=https://github.com/code-hike/codehike/blob/next/packages/mdx/src/smooth-code/copy-button.tsx#L3" rel="noopener noreferrer">https://github.com/code-hike/codehike/blob/next/packages/mdx/src/smooth-code/copy-button.tsx#L3</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/components/copy-button.tsx#L31" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/apps/www/components/copy-button.tsx#L31</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/components/copy-button.tsx#L24" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/apps/www/components/copy-button.tsx#L24</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/events.ts#L27" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/apps/www/lib/events.ts#L27</a> </li> </ol> codehike shadcnui typescript opensource Shimmer effect in Card when you load Supabase dashboard. Ramu Narasinga Mon, 05 Aug 2024 20:54:03 +0000 https://dev.to/ramunarasinga/shimmer-effect-in-card-when-you-load-supabase-dashboard-7cn https://dev.to/ramunarasinga/shimmer-effect-in-card-when-you-load-supabase-dashboard-7cn <p>When loading a dashboard, especially one as feature-rich as Supabase’s, it’s essential to provide visual feedback to users indicating that content is being loaded. A popular and visually appealing way to achieve this is by using a shimmer effect. This effect simulates a loading state and keeps users engaged while the actual content is being fetched.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQEsuWACCZJZCw%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722890745011%3Fe%3D1728518400%26v%3Dbeta%26t%3DG1BG0pK7mIAxYFMn82lNKDC-jrQK2ioGfzG5c4gDGVU" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQEsuWACCZJZCw%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722890745011%3Fe%3D1728518400%26v%3Dbeta%26t%3DG1BG0pK7mIAxYFMn82lNKDC-jrQK2ioGfzG5c4gDGVU"></a></p> <p>Let’s explore how Supabase implements a shimmer effect in their dashboard using the animate property and some creative CSS.</p> <h2> Shimmer Effect Using the Animate Property </h2> <p>The ShimmeringCard component in Supabase's source code showcases how to create a shimmering loading effect using a combination of React and CSS animations.</p> <p>Here’s the implementation of the ShimmeringCard component:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Pulled from </span> <span class="c1">// https://github.com/supabase/supabase/blob/master/apps/studio/components/interfaces/Home/ProjectList/ShimmeringCard.tsx#L3</span> <span class="k">import</span> <span class="nx">CardButton</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">components/ui/CardButton</span><span class="dl">'</span> <span class="kd">const</span> <span class="nx">ShimmeringCard</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return </span><span class="p">(</span> <span class="o">&lt;</span><span class="nx">CardButton</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">h-44 !px-0 pt-5 pb-0</span><span class="dl">"</span> <span class="nx">title</span><span class="o">=</span><span class="p">{</span> <span class="o">&lt;</span><span class="nx">div</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">w-full justify-between space-y-1.5 px-5</span><span class="dl">"</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="nx">p</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">flex-shrink truncate text-sm pr-4 shimmering-loader h-5 w-20</span><span class="dl">"</span> <span class="o">/&gt;</span> <span class="o">&lt;</span><span class="nx">p</span> <span class="nx">className</span><span class="o">=</span><span class="dl">"</span><span class="s2">text-sm lowercase text-foreground-light h-4 w-40 shimmering-loader</span><span class="dl">"</span> <span class="o">/&gt;</span> <span class="o">&lt;</span><span class="sr">/div</span><span class="err">&gt; </span> <span class="p">}</span> <span class="sr">/</span><span class="err">&gt; </span> <span class="p">)</span> <span class="p">}</span> <span class="k">export</span> <span class="k">default</span> <span class="nx">ShimmeringCard</span> </code></pre> </div> <p>In this component, CardButton is used to create the card structure, and the shimmer effect is applied to the text elements within the card using the shimmering-loader class.</p> <h2> The shimmering-loader CSS Class </h2> <p>The shimmering-loader class is defined in the main.scss file and leverages the animate property to create the shimmer effect:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="p">.</span><span class="nx">shimmering</span><span class="o">-</span><span class="nx">loader</span> <span class="p">{</span> <span class="nl">animation</span><span class="p">:</span> <span class="nx">shimmer</span> <span class="mi">2</span><span class="nx">s</span> <span class="nx">infinite</span> <span class="nx">linear</span><span class="p">;</span> <span class="nl">background</span><span class="p">:</span> <span class="nx">linear</span><span class="o">-</span><span class="nf">gradient</span><span class="p">(</span> <span class="nx">to</span> <span class="nx">right</span><span class="p">,</span> <span class="nf">hsl</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="nx">border</span><span class="o">-</span><span class="k">default</span><span class="p">))</span> <span class="mi">4</span><span class="o">%</span><span class="p">,</span> <span class="nf">hsl</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="nx">background</span><span class="o">-</span><span class="nx">surface</span><span class="o">-</span><span class="mi">200</span><span class="p">))</span> <span class="mi">25</span><span class="o">%</span><span class="p">,</span> <span class="nf">hsl</span><span class="p">(</span><span class="nf">var</span><span class="p">(</span><span class="o">--</span><span class="nx">border</span><span class="o">-</span><span class="k">default</span><span class="p">))</span> <span class="mi">36</span><span class="o">%</span> <span class="p">);</span> <span class="nx">background</span><span class="o">-</span><span class="nx">size</span><span class="p">:</span> <span class="mi">1000</span><span class="nx">px</span> <span class="mi">100</span><span class="o">%</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <h2> How It Works </h2> <ol> <li> <strong>Animation Definition</strong>: The shimmer keyframes animation moves the background gradient from left to right.</li> <li> <strong>Gradient Background</strong>: The background is a linear gradient that creates the shimmer effect. The colors transition smoothly to give the appearance of a light moving across the surface.</li> <li> <strong>Animation Application</strong>: The shimmering-loader class applies this animation to any element, making it shimmer.</li> </ol> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/supabase/supabase/blob/master/apps/studio/components/interfaces/Home/ProjectList/ShimmeringCard.tsx#L3" rel="noopener noreferrer">https://github.com/supabase/supabase/blob/master/apps/studio/components/interfaces/Home/ProjectList/ShimmeringCard.tsx#L3</a> </li> <li> <a href="proxy.php?url=https://github.com/supabase/supabase/blob/ce4213956045da0c174f4881b4300b508e30978c/apps/docs/styles/main.scss#L308C1-L317C2" rel="noopener noreferrer">https://github.com/supabase/supabase/blob/ce4213956045da0c174f4881b4300b508e30978c/apps/docs/styles/main.scss#L308C1-L317C2</a> </li> </ol> supabas cssanimate opensource typescript Function overload in TypeScript Ramu Narasinga Thu, 01 Aug 2024 21:36:44 +0000 https://dev.to/ramunarasinga/function-overload-in-typescript-2he1 https://dev.to/ramunarasinga/function-overload-in-typescript-2he1 <p>When working with TypeScript, you may encounter situations where a function needs to handle different types of input while maintaining type safety. This is where function overloading comes into play. Let’s look at a practical example of function overloading, inspired by a code snippet from the <a href="proxy.php?url=https://github.com/supabase/supabase/blob/master/apps/studio/hooks/misc/useIsFeatureEnabled.ts#L24" rel="noopener noreferrer">Supabase source code</a>.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFCRJRdxytumA%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722547552496%3Fe%3D1727913600%26v%3Dbeta%26t%3DBrmgiRDPInq4dOEhsHbHUslSof0DJkDYuMCiEup6vLw" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFCRJRdxytumA%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722547552496%3Fe%3D1727913600%26v%3Dbeta%26t%3DBrmgiRDPInq4dOEhsHbHUslSof0DJkDYuMCiEup6vLw"></a></p> <h2> Example: useIsFeatureEnabled </h2> <p>The useIsFeatureEnabled function is a great example of function overloading. It can handle both an array of features and a single feature, returning appropriate results for each case.</p> <p>Here’s the overloaded function definition:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">useIsFeatureEnabled</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">Feature</span><span class="err">\</span><span class="p">[</span><span class="err">\</span><span class="p">]</span><span class="o">&gt;</span><span class="p">(</span> <span class="nx">features</span><span class="p">:</span> <span class="nx">T</span> <span class="p">):</span> <span class="p">{</span> <span class="err">\</span><span class="p">[</span><span class="nx">key</span> <span class="k">in</span> <span class="nx">FeatureToCamelCase</span><span class="o">&lt;</span><span class="nx">T</span><span class="err">\</span><span class="p">[</span><span class="nx">number</span><span class="err">\</span><span class="p">]</span><span class="o">&gt;</span><span class="err">\</span><span class="p">]:</span> <span class="nx">boolean</span> <span class="p">}</span> <span class="kd">function</span> <span class="nf">useIsFeatureEnabled</span><span class="p">(</span><span class="nx">features</span><span class="p">:</span> <span class="nx">Feature</span><span class="p">):</span> <span class="nx">boolean</span> <span class="kd">function</span> <span class="nf">useIsFeatureEnabled</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">Feature</span> <span class="o">|</span> <span class="nx">Feature</span><span class="err">\</span><span class="p">[</span><span class="err">\</span><span class="p">]</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">features</span><span class="p">:</span> <span class="nx">T</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="p">{</span> <span class="nx">profile</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">useProfile</span><span class="p">()</span> <span class="k">if </span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nf">isArray</span><span class="p">(</span><span class="nx">features</span><span class="p">))</span> <span class="p">{</span> <span class="k">return</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">fromEntries</span><span class="p">(</span> <span class="nx">features</span><span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">feature</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="err">\</span><span class="p">[</span> <span class="nf">featureToCamelCase</span><span class="p">(</span><span class="nx">feature</span><span class="p">),</span> <span class="nf">checkFeature</span><span class="p">(</span><span class="nx">feature</span><span class="p">,</span> <span class="nx">profile</span><span class="p">?.</span><span class="nx">disabled</span><span class="err">\</span><span class="nx">_features</span><span class="p">),</span> <span class="err">\</span><span class="p">])</span> <span class="p">)</span> <span class="p">}</span> <span class="k">return</span> <span class="nf">checkFeature</span><span class="p">(</span><span class="nx">features</span><span class="p">,</span> <span class="nx">profile</span><span class="p">?.</span><span class="nx">disabled</span><span class="err">\</span><span class="nx">_features</span><span class="p">)</span> <span class="p">}</span> <span class="k">export</span> <span class="p">{</span> <span class="nx">useIsFeatureEnabled</span> <span class="p">}</span> </code></pre> </div> <h2> How It Works </h2> <ol> <li> <strong>Function Overloads</strong>: The first two declarations are overload signatures. They define the different ways the function can be called. The actual implementation comes last, handling both cases.</li> <li> <strong>Implementation</strong>: The function implementation checks if the input features is an array. If it is, it processes each feature, converts it to camelCase, and checks if it's enabled. If features is a single feature, it directly checks its status.</li> </ol> <h2> Supporting Functions and Types </h2> <p>To understand this better, let’s look at the supporting checkFeature function and the type utility FeatureToCamelCase.</p> <h3> checkFeature Function </h3> <p>The checkFeature function determines if a given feature is enabled or not:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">checkFeature</span><span class="p">(</span><span class="nx">feature</span><span class="p">:</span> <span class="nx">Feature</span><span class="p">,</span> <span class="nx">features</span><span class="p">?:</span> <span class="nx">Feature</span><span class="err">\</span><span class="p">[</span><span class="err">\</span><span class="p">])</span> <span class="p">{</span> <span class="k">return</span> <span class="o">!</span><span class="nx">features</span><span class="p">?.</span><span class="nf">includes</span><span class="p">(</span><span class="nx">feature</span><span class="p">)</span> <span class="o">??</span> <span class="kc">true</span> <span class="p">}</span> </code></pre> </div> <p>This function returns true if the feature is not in the disabled features list or if no disabled features are provided.</p> <blockquote> <p><em>Do watch the</em> <a href="proxy.php?url=https://www.youtube.com/watch?v=Vr1BUFw6dJM" rel="noopener noreferrer"><em>Matt Pocock’s Youtube video explanation</em></a> <em>about the function overloads in TypeScript.</em></p> </blockquote> <h2> Conclusion </h2> <p>Function overloading in TypeScript allows you to define multiple ways to call a function with different types of input while ensuring type safety. The useIsFeatureEnabled function from Supabase is an excellent example of this concept in action. It demonstrates how to handle different input types seamlessly, providing both flexibility and strong typing.</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/supabase/supabase/blob/master/apps/studio/hooks/misc/useIsFeatureEnabled.ts#L24" rel="noopener noreferrer">https://github.com/supabase/supabase/blob/master/apps/studio/hooks/misc/useIsFeatureEnabled.ts#L24</a> </li> <li> <a href="proxy.php?url=https://www.youtube.com/watch?v=Vr1BUFw6dJM" rel="noopener noreferrer">Matt Pocock’s Youtube video explanation.</a> </li> </ol> javascript opensource supabase functionoverload Convert a string to camelCase using this function in Javascript. Ramu Narasinga Wed, 31 Jul 2024 21:31:43 +0000 https://dev.to/ramunarasinga/convert-a-string-to-camelcase-using-this-function-in-javascript-484j https://dev.to/ramunarasinga/convert-a-string-to-camelcase-using-this-function-in-javascript-484j <p>Ever needed to convert a string to camelCase? I found an interesting code snippet while exploring the open-source <a href="proxy.php?url=https://github.com/supabase/supabase/blob/master/apps/studio/hooks/misc/useIsFeatureEnabled.ts#L16" rel="noopener noreferrer">Supabase repository</a>. Here’s the method they use:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">featureToCamelCase</span><span class="p">(</span><span class="nx">feature</span><span class="p">:</span> <span class="nx">Feature</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">feature</span> <span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/:/g</span><span class="p">,</span> <span class="dl">'</span><span class="se">\</span><span class="s1">_</span><span class="dl">'</span><span class="p">)</span> <span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="se">\</span><span class="s1">_</span><span class="dl">'</span><span class="p">)</span> <span class="p">.</span><span class="nf">map</span><span class="p">((</span><span class="nx">word</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span> <span class="p">?</span> <span class="nx">word</span> <span class="p">:</span> <span class="nx">word</span><span class="err">\</span><span class="p">[</span><span class="mi">0</span><span class="err">\</span><span class="p">].</span><span class="nf">toUpperCase</span><span class="p">()</span> <span class="o">+</span> <span class="nx">word</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)))</span> <span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">''</span><span class="p">)</span> <span class="k">as</span> <span class="nx">FeatureToCamelCase</span><span class="o">&lt;</span><span class="k">typeof</span> <span class="nx">feature</span><span class="o">&gt;</span> <span class="p">}</span> </code></pre> </div> <p>This function is pretty neat. It replaces colons with underscores, splits the string into words, and then maps through each word to convert it to camelCase. The first word is kept in lowercase, and the subsequent words have their first character capitalized before being joined back together. Simple yet effective!</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD5612AQGimOyzWHEIFQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722460552945%3Fe%3D1727913600%26v%3Dbeta%26t%3DmU2dtDVH99c0WbNiJYbyZi9dUS3Akxab3ibpyqNvG3s" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD5612AQGimOyzWHEIFQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722460552945%3Fe%3D1727913600%26v%3Dbeta%26t%3DmU2dtDVH99c0WbNiJYbyZi9dUS3Akxab3ibpyqNvG3s"></a></p> <p>I came across another approach on Stack Overflow that doesn’t use regular expressions. Here’s the alternative:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">function</span> <span class="nf">toCamelCase</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">str</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">).</span><span class="nf">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">word</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// If it is the first word make sure to lowercase all the chars.</span> <span class="k">if </span><span class="p">(</span><span class="nx">index</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">word</span><span class="p">.</span><span class="nf">toLowerCase</span><span class="p">();</span> <span class="p">}</span> <span class="c1">// If it is not the first word only upper case the first char and lowercase the rest.</span> <span class="k">return</span> <span class="nx">word</span><span class="p">.</span><span class="nf">charAt</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="nf">toUpperCase</span><span class="p">()</span> <span class="o">+</span> <span class="nx">word</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">).</span><span class="nf">toLowerCase</span><span class="p">();</span> <span class="p">}).</span><span class="nf">join</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>This <a href="proxy.php?url=https://stackoverflow.com/a/35976812" rel="noopener noreferrer">code snippet from SO</a> has comments explaining what this code does except it does not use any sort of regex. The code found in Supabase’s way of converting a string to camelCase is very similar to this <a href="proxy.php?url=https://stackoverflow.com/a/35976812" rel="noopener noreferrer">SO answer</a>, except for the comments and the regex used.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/:/g</span><span class="p">,</span> <span class="dl">'</span><span class="se">\</span><span class="s1">_</span><span class="dl">'</span><span class="p">)</span> </code></pre> </div> <p>This method splits the string by spaces and then maps over each word. The first word is entirely lowercased, while the subsequent words are capitalized at the first character and lowercased for the rest. Finally, the words are joined back together to form a camelCase string.</p> <p>One interesting comment from a Stack Overflow user mentioned the performance advantage of this approach:</p> <blockquote> <p><em>“+1 for not using regular expressions, even if the question asked for a solution using them. This is a much clearer solution, and also a clear win for performance (because processing complex regular expressions is a much harder task than just iterating over a bunch of strings and joining bits of them together). See</em> <a href="proxy.php?url=http://jsperf.com/camel-casing-regexp-or-character-manipulation/1" rel="noopener noreferrer"><em>jsperf.com/camel-casing-regexp-or-character-manipulation/1</em></a> <em>where I’ve taken some of the examples here along with this one (and also my own modest improvement of it for performance, although I would probably prefer this version for clarity’s sake in most cases).”</em></p> </blockquote> <p>Both methods have their merits. The regex approach in the Supabase code is concise and leverages powerful string manipulation techniques. On the other hand, the non-regex approach is praised for its clarity and performance, as it avoids the computational overhead associated with regular expressions.</p> <p>Here’s how you can choose between them:</p> <ul> <li> <strong>Use the regex approach</strong> if you need a compact, one-liner solution that leverages JavaScript’s powerful regex capabilities. Also be sure to add comments explaining what your regex does, so that your future self or next dev working with your code can understand.</li> <li> <strong>Opt for the non-regex method</strong> if you prioritize readability and performance, especially when dealing with longer strings or running this conversion multiple times.</li> </ul> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/supabase/supabase/blob/master/apps/studio/hooks/misc/useIsFeatureEnabled.ts#L16" rel="noopener noreferrer">https://github.com/supabase/supabase/blob/master/apps/studio/hooks/misc/useIsFeatureEnabled.ts#L16</a> </li> <li> <a href="proxy.php?url=https://stackoverflow.com/a/35976812" rel="noopener noreferrer">https://stackoverflow.com/a/35976812</a> </li> </ol> javascript supabase opensource camelcase Comparison of file and component structures among Shadcn-ui, Plane.so and Gitroom. Ramu Narasinga Tue, 30 Jul 2024 21:07:52 +0000 https://dev.to/ramunarasinga/comparison-of-file-and-component-structures-among-shadcn-ui-planeso-and-gitroom-105d https://dev.to/ramunarasinga/comparison-of-file-and-component-structures-among-shadcn-ui-planeso-and-gitroom-105d <p>In this article, we will look at folder structures in Shadcn-ui, <a href="proxy.php?url=http://Plane.so" rel="noopener noreferrer">Plane.so</a> and Gitroom used to organise a Next.js project.</p> <h2> Shadcn-ui file and component structures: </h2> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQF4UF4APVtiIg%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372738568%3Fe%3D1727913600%26v%3Dbeta%26t%3D3Rdv9YRBm2F5h_Mvo0lHUqduPJm8f4qVZ2fnJe00Bv4" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQF4UF4APVtiIg%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372738568%3Fe%3D1727913600%26v%3Dbeta%26t%3D3Rdv9YRBm2F5h_Mvo0lHUqduPJm8f4qVZ2fnJe00Bv4"></a></p> <h3> File structure </h3> <p><a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/app/(app)/examples/dashboard/page.tsx" rel="noopener noreferrer">Shadcn-ui/ui</a> uses app router and has a <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)" rel="noopener noreferrer">Router group named (app)</a>.</p> <p>In the (app) folder, you will find a folder with page.tsx and/or layout.tsx in some routes such as <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/colors" rel="noopener noreferrer">colors</a>, <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/blocks" rel="noopener noreferrer">blocks</a>, <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/docs" rel="noopener noreferrer">docs</a>.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGDsegVVfy8RA%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372736818%3Fe%3D1727913600%26v%3Dbeta%26t%3DIYf5oksddQmZ1t1f3trA8xtOlpP675ItY1r0tZFo8-w" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGDsegVVfy8RA%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372736818%3Fe%3D1727913600%26v%3Dbeta%26t%3DIYf5oksddQmZ1t1f3trA8xtOlpP675ItY1r0tZFo8-w"></a></p> <h3> Component structure </h3> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHpd6b4EEArFQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372737207%3Fe%3D1727913600%26v%3Dbeta%26t%3DHD1O6UKz2GCe8W26DHvtWoJLqi7PstfFq-q01NYaoQc" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHpd6b4EEArFQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372737207%3Fe%3D1727913600%26v%3Dbeta%26t%3DHD1O6UKz2GCe8W26DHvtWoJLqi7PstfFq-q01NYaoQc"></a></p> <p>In <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/apps/www/app/(app)/blocks/page.tsx" rel="noopener noreferrer">blocks/page.tsx</a>, you will find that it imports components that are generic or that can be reused in other pages.</p> <p>But wait, there’s other kind of components.</p> <p>If you look at <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/examples" rel="noopener noreferrer">examples route</a>, each example has its own component that is specific to an example.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHoBf7y4Lwcgg%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372737913%3Fe%3D1727913600%26v%3Dbeta%26t%3DxNZekvvwkQr5xLy2e4o4fQNYiXA-JH8qNoG9s9nT-sg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHoBf7y4Lwcgg%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372737913%3Fe%3D1727913600%26v%3Dbeta%26t%3DxNZekvvwkQr5xLy2e4o4fQNYiXA-JH8qNoG9s9nT-sg"></a></p> <p>so there’s components available at <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/examples/cards/components" rel="noopener noreferrer">examples/card/components</a> and then there are also <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/components" rel="noopener noreferrer">generic components</a> that can be reused.</p> <h2> Plane.so file and component structures: </h2> <p><a href="proxy.php?url=http://plane.so" rel="noopener noreferrer">plane.so</a> has similar structure to that shadcn-ui/ui where you will find components specific to features by their names as shown below.</p> <h3> File structure </h3> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGdmVVKh3Q70w%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372738669%3Fe%3D1727913600%26v%3Dbeta%26t%3DaG-Uz3WHUJtmvlKp1t0hg2aJRgUeqWFWyD98GJUQeBE" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGdmVVKh3Q70w%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372738669%3Fe%3D1727913600%26v%3Dbeta%26t%3DaG-Uz3WHUJtmvlKp1t0hg2aJRgUeqWFWyD98GJUQeBE"></a></p> <p>The file structure in <a href="proxy.php?url=http://plane.so" rel="noopener noreferrer">plane.so</a> uses nested routes but it does have a header.tsx specific to a route defined in all the folders along with page.tsx or layout.tsx</p> <h3> Component structure </h3> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFrmxPYrkDHfA%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372740378%3Fe%3D1727913600%26v%3Dbeta%26t%3DXEd8jBd844N1lRw9kIesQB3iT5zQ6on1Bz4ZhmqeZSw" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFrmxPYrkDHfA%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372740378%3Fe%3D1727913600%26v%3Dbeta%26t%3DXEd8jBd844N1lRw9kIesQB3iT5zQ6on1Bz4ZhmqeZSw"></a></p> <p>All the components used in the app router are placed inside <a href="proxy.php?url=https://github.com/makeplane/plane/tree/preview/web/core" rel="noopener noreferrer">core folder</a>.</p> <p>So now comes the question, how come we see the imports as “@/components” in a router as shown below:</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGsQpSdQv6o9g%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372739720%3Fe%3D1727913600%26v%3Dbeta%26t%3DSbfKhegY-8_xsiW2aIa2F9VWETY5e157DLuWpLuwq8A" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGsQpSdQv6o9g%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372739720%3Fe%3D1727913600%26v%3Dbeta%26t%3DSbfKhegY-8_xsiW2aIa2F9VWETY5e157DLuWpLuwq8A"></a></p> <p>when there is no components folder at app folder level:</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHMKyEjRZxhCA%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372737894%3Fe%3D1727913600%26v%3Dbeta%26t%3DY46u6zTvWvV6oAZRPTiHev_aEx0w-wfw2VyppFB0QMo" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHMKyEjRZxhCA%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722372737894%3Fe%3D1727913600%26v%3Dbeta%26t%3DY46u6zTvWvV6oAZRPTiHev_aEx0w-wfw2VyppFB0QMo"></a></p> <p>Well, the components are inside core folder and tsconfig.json has the paths defined to locate the folders in core as base.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHYjjZIRqu-yg%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372739438%3Fe%3D1727913600%26v%3Dbeta%26t%3DgqcnqrIc3T3JEJQHOPfWdfOKj0rFCAXWudHXZsY17_4" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHYjjZIRqu-yg%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372739438%3Fe%3D1727913600%26v%3Dbeta%26t%3DgqcnqrIc3T3JEJQHOPfWdfOKj0rFCAXWudHXZsY17_4"></a></p> <h2> Gitroom file and component structures: </h2> <p>Gitroom has a straight forward structure. Routes have page.tsx and each page.tsx uses components from components folder and this components folder contains files specific to features.</p> <h3> File structure: </h3> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHmd0vB6fWW8A%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372737469%3Fe%3D1727913600%26v%3Dbeta%26t%3DPwYSaU20CstZ9WRQJ71XYTtS96Wn92GDo6mSj11CVWs" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHmd0vB6fWW8A%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372737469%3Fe%3D1727913600%26v%3Dbeta%26t%3DPwYSaU20CstZ9WRQJ71XYTtS96Wn92GDo6mSj11CVWs"></a></p> <p>As you can see, router only has one file name page.tsx and that’s it. We see in shadcn-ui where it contains components specific to a route that are scoped to that route, similarly in <a href="proxy.php?url=http://plane.so" rel="noopener noreferrer">plane.so</a>, it has route specific header.tsx along with page.tsx however, one commonality in all of these projects is that there is a components folder that can be reused or specific to a feature depending on the folder name.</p> <h3> Component structure: </h3> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHZB9Cpd2QiTw%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372739133%3Fe%3D1727913600%26v%3Dbeta%26t%3DHA1Axl7LN_5Saa6IVCJTimynP7fXZyanfvWPVmlaD3U" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQHZB9Cpd2QiTw%2Farticle-inline_image-shrink_1000_1488%2F0%2F1722372739133%3Fe%3D1727913600%26v%3Dbeta%26t%3DHA1Axl7LN_5Saa6IVCJTimynP7fXZyanfvWPVmlaD3U"></a></p> <p>Notice how the route/file structures has the folder names that match with some of the folders names in component structure.</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/examples/cards/components" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/tree/main/apps/www/app/(app)/examples/cards/components</a> </li> <li> <a href="proxy.php?url=https://github.com/makeplane/plane/tree/preview" rel="noopener noreferrer">https://github.com/makeplane/plane/tree/preview</a> </li> <li> <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/main/apps/frontend/src/app/(site)/launches/page.tsx" rel="noopener noreferrer">https://github.com/gitroomhq/gitroom/blob/main/apps/frontend/src/app/(site)/launches/page.tsx</a> </li> </ol> opensource gitroom planeso shadcnui Cache your fetcher in useSWR with useCallback. Ramu Narasinga Mon, 29 Jul 2024 21:04:57 +0000 https://dev.to/ramunarasinga/cache-your-fetcher-in-useswr-with-usecallback-188m https://dev.to/ramunarasinga/cache-your-fetcher-in-useswr-with-usecallback-188m <p>I found a way that caches the fetcher in useSWR using useCallback in the <a href="proxy.php?url=https://github.com/search?q=repo%3Agitroomhq%2Fgitroom+useSWR&amp;type=code" rel="noopener noreferrer">Gitroom source code</a>.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--VXDLo7g5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.licdn.com/dms/image/D5612AQFxMxRsUGCFHQ/article-inline_image-shrink_1500_2232/0/1722286143725%3Fe%3D1727913600%26v%3Dbeta%26t%3DKfKDhUCYMz7xJ1O5GcnqYxSswxM2ll5qLOVGWTl8p_I" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--VXDLo7g5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.licdn.com/dms/image/D5612AQFxMxRsUGCFHQ/article-inline_image-shrink_1500_2232/0/1722286143725%3Fe%3D1727913600%26v%3Dbeta%26t%3DKfKDhUCYMz7xJ1O5GcnqYxSswxM2ll5qLOVGWTl8p_I" width="800" height="548"></a></p> <p>The above image is from <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/c03b96215fa30b267a97d7eafc2281f482a3192f/apps/frontend/src/components/platform-analytics/render.analytics.tsx#L25" rel="noopener noreferrer">platform-analytics/</a><a href="proxy.php?url=http://render.analytics" rel="noopener noreferrer">render.analytics</a><a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/c03b96215fa30b267a97d7eafc2281f482a3192f/apps/frontend/src/components/platform-analytics/render.analytics.tsx#L25" rel="noopener noreferrer">.tsx</a>. Let’s try to understand this code snippet.</p> <p>we’ll explore how to optimize data fetching in your React applications using the useCallback hook in combination with useSWR. We'll break down the provided code snippet, explain why caching your fetcher function is important.</p> <h3> Understanding the Code </h3> <p>Let’s dive into the code step by step:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">load</span> <span class="o">=</span> <span class="nf">useCallback</span><span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nf">setLoading</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">load</span> <span class="o">=</span> <span class="p">(</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="err">\</span><span class="s2">`/analytics/</span><span class="p">${</span><span class="nx">integration</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">?date=</span><span class="p">${</span><span class="nx">date</span><span class="p">}</span><span class="se">\`</span><span class="s2">) ).json(); setLoading(false); return load; }, \[integration, date\]); </span></code></pre> </div> <p>Here, we’re defining an asynchronous function load inside a useCallback hook. This function fetches data from a specified endpoint and handles the loading state. The useCallback hook ensures that this function is memoized and only recreated when the dependencies (integration and date) change.</p> <p>Next, we use useSWR to manage the data fetching:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">useSWR</span><span class="p">(</span><span class="err">\</span><span class="s2">`/analytics-</span><span class="p">${</span><span class="nx">integration</span><span class="p">?.</span><span class="nx">id</span><span class="p">}</span><span class="s2">-</span><span class="p">${</span><span class="nx">date</span><span class="p">}</span><span class="se">\`</span><span class="s2">, load, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, revalidateIfStale: false, refreshWhenOffline: false, revalidateOnMount: true, }); </span></code></pre> </div> <p>Here, useSWR is configured with a key (/analytics-${integration?.id}-${date}) and our memoized load function. The configuration options control the revalidation behavior of the data.</p> <h2> How useCallback Prevents Unnecessary Re-fetches </h2> <p>To understand how useCallback prevents unnecessary re-fetches, we need to delve into how React handles function references and how useSWR works.</p> <h3> Function References in React </h3> <p>In React, every time a component re-renders, all functions defined within it are recreated. This means that without useCallback, a new instance of your load function would be created on every render.</p> <p><a href="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--hIcChx_j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.licdn.com/dms/image/D5612AQEiVUsxRlM6eg/article-inline_image-shrink_1000_1488/0/1722286144727%3Fe%3D1727913600%26v%3Dbeta%26t%3DKajxKLf2SBpKy1gi7I7pUdK2zdX8WRMNdWykCamv-Nw" class="article-body-image-wrapper"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--hIcChx_j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.licdn.com/dms/image/D5612AQEiVUsxRlM6eg/article-inline_image-shrink_1000_1488/0/1722286144727%3Fe%3D1727913600%26v%3Dbeta%26t%3DKajxKLf2SBpKy1gi7I7pUdK2zdX8WRMNdWykCamv-Nw" width="800" height="521"></a></p> <h3> Impact on useSWR </h3> <p>useSWR is a data fetching library for React. It uses a key to identify the data and a fetcher function to fetch it. useSWR relies on the stability of the fetcher function reference. If the reference changes, useSWR might interpret this as a signal that the data needs to be refetched, even if the actual logic of the fetcher hasn't changed.</p> <p>Here’s a detailed explanation:</p> <ol> <li> <strong>Without</strong> useCallback: </li> </ol> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">load</span> <span class="o">=</span> <span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nf">setLoading</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">load</span> <span class="o">=</span> <span class="p">(</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="err">\</span><span class="s2">`/analytics/</span><span class="p">${</span><span class="nx">integration</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">?date=</span><span class="p">${</span><span class="nx">date</span><span class="p">}</span><span class="se">\`</span><span class="s2">) ).json(); setLoading(false); return load; }; const { data } = useSWR(</span><span class="se">\`</span><span class="s2">/analytics-</span><span class="p">${</span><span class="nx">integration</span><span class="p">?.</span><span class="nx">id</span><span class="p">}</span><span class="s2">-</span><span class="p">${</span><span class="nx">date</span><span class="p">}</span><span class="se">\`</span><span class="s2">, load, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, revalidateIfStale: false, refreshWhenOffline: false, revalidateOnMount: true, }); </span></code></pre> </div> <p>In this case, every render creates a new load function. useSWR sees a different function reference each time, which can lead to unnecessary re-fetches even when integration and date haven't changed.</p> <p><strong>With</strong> useCallback:<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">load</span> <span class="o">=</span> <span class="nf">useCallback</span><span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="nf">setLoading</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">load</span> <span class="o">=</span> <span class="p">(</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="err">\</span><span class="s2">`/analytics/</span><span class="p">${</span><span class="nx">integration</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span><span class="s2">?date=</span><span class="p">${</span><span class="nx">date</span><span class="p">}</span><span class="se">\`</span><span class="s2">) ).json(); setLoading(false); return load; }, \[integration, date\]); const { data } = useSWR(</span><span class="se">\`</span><span class="s2">/analytics-</span><span class="p">${</span><span class="nx">integration</span><span class="p">?.</span><span class="nx">id</span><span class="p">}</span><span class="s2">-</span><span class="p">${</span><span class="nx">date</span><span class="p">}</span><span class="se">\`</span><span class="s2">, load, { refreshInterval: 0, refreshWhenHidden: false, revalidateOnFocus: false, revalidateOnReconnect: false, revalidateIfStale: false, refreshWhenOffline: false, revalidateOnMount: true, }); </span></code></pre> </div> <p>By wrapping the load function in useCallback, we ensure that it is only recreated when its dependencies (integration and date) change. This stability in the function reference tells useSWR that the fetcher function hasn't changed unless integration or date changes, thus preventing unnecessary re-fetches.</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://res.cloudinary.com/practicaldev/image/fetch/s--yeg4HJzS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://media.licdn.com/dms/image/D4E12AQFdaAsIZFCFDQ/article-inline_image-shrink_1500_2232/0/1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI" width="800" height="453"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/c03b96215fa30b267a97d7eafc2281f482a3192f/apps/frontend/src/components/platform-analytics/render.analytics.tsx#L25" rel="noopener noreferrer">https://github.com/gitroomhq/gitroom/blob/c03b96215fa30b267a97d7eafc2281f482a3192f/apps/frontend/src/components/platform-analytics/render.analytics.tsx#L25</a> </li> <li> <a href="proxy.php?url=https://github.com/search?q=repo%3Agitroomhq%2Fgitroom%20useSWR&amp;type=code" rel="noopener noreferrer">https://github.com/search?q=repo%3Agitroomhq%2Fgitroom%20useSWR&amp;type=code</a> </li> <li> <a href="proxy.php?url=https://swr.vercel.app/docs/getting-started" rel="noopener noreferrer">https://swr.vercel.app/docs/getting-started</a> </li> <li> <a href="proxy.php?url=https://react.dev/reference/react/useCallback" rel="noopener noreferrer">https://react.dev/reference/react/useCallback</a> </li> </ol> javascript nextjs gitroom opensource Using a custom backend server with Next.js in a monorepo. Ramu Narasinga Fri, 26 Jul 2024 21:03:29 +0000 https://dev.to/ramunarasinga/using-a-custom-backend-server-with-nextjs-in-a-monorepo-5acc https://dev.to/ramunarasinga/using-a-custom-backend-server-with-nextjs-in-a-monorepo-5acc <p>I was searching for an open source repository that schedules and posts on social media. I found <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/tree/main" rel="noopener noreferrer">Gitroom</a>.</p> <p>Gitroom is a awesome, built by <a href="proxy.php?url=https://github.com/nevo-david" rel="noopener noreferrer">Nevo David</a>. You can 📨 schedule social media and articles. Exchange or buy posts from other members 👨🏻💻. Monitor your GitHub trending, and so much more 📈.</p> <p>The following are some interesting things I learnt from this <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/tree/main" rel="noopener noreferrer">repository</a></p> <p>1. You can use your own backend with Next.js 2. A note about customFetch 3. The way the files are named.</p> <h2> 1. You can use your own backend with Next.js </h2> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGYt3hBBBn3mQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722026627178%3Fe%3D1727308800%26v%3Dbeta%26t%3DK7ZNRgk8uTw4rLGL0p_TVoD5HEXvOGjyTn4nJ23E_X8" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGYt3hBBBn3mQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722026627178%3Fe%3D1727308800%26v%3Dbeta%26t%3DK7ZNRgk8uTw4rLGL0p_TVoD5HEXvOGjyTn4nJ23E_X8"></a></p> <p>Gitroom uses the following tech stack:</p> <ul> <li> NX (Monorepo)</li> <li> NextJS (React)</li> <li> NestJS</li> <li> Prisma (Default to PostgreSQL)</li> <li> Redis</li> <li> Resend (emails notifications)</li> </ul> <p>It has folders named as frontend, backend, cron etc.,</p> <p>I wondered for quite some time now, if we could use our own backend when you are already using the Next.js, a “full stack” react framework but, I kept seeing people advising to use your custom backend when you need advanced features like cron, web sockets etc., on Reddit. Now that I found Gitroom that demonstrates the custom backend usage along with Next.js and cron, I have a good feeling that you could learn some advanced patterns studying this repository.</p> <h2> 2. A note about customFetch </h2> <p>The following code snippet is picked from <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/e7b669f1253e3ef7ae6b9cc9d2f1d529ea86b288/apps/frontend/src/app/(site)/settings/page.tsx#L21" rel="noopener noreferrer">apps/frontend/src/app/(site)/settings/page.tsx#L21</a><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">if </span><span class="p">(</span><span class="nx">searchParams</span><span class="p">.</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span> <span class="k">await</span> <span class="nf">internalFetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/settings/github</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span> <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span> <span class="na">code</span><span class="p">:</span> <span class="nx">searchParams</span><span class="p">.</span><span class="nx">code</span> <span class="p">}),</span> <span class="p">});</span> <span class="k">return</span> <span class="nf">redirect</span><span class="p">(</span><span class="dl">'</span><span class="s1">/settings</span><span class="dl">'</span><span class="p">,</span> <span class="nx">RedirectType</span><span class="p">.</span><span class="nx">replace</span><span class="p">);</span> <span class="p">}</span> </code></pre> </div> <p>‘internalFetch’ uses customFetch.</p> <p>The below code snippet is picked from <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/e7b669f1253e3ef7ae6b9cc9d2f1d529ea86b288/libraries/helpers/src/utils/custom.fetch.func.ts#L10" rel="noopener noreferrer">libraries/helpers/src/utils/custom.fetch.func.ts</a><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">customFetch</span> <span class="o">=</span> <span class="p">(</span> <span class="nx">params</span><span class="p">:</span> <span class="nx">Params</span><span class="p">,</span> <span class="nx">auth</span><span class="p">?:</span> <span class="nx">string</span><span class="p">,</span> <span class="nx">showorg</span><span class="p">?:</span> <span class="nx">string</span> <span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">return</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">newFetch</span><span class="p">(</span><span class="na">url</span><span class="p">:</span> <span class="nx">string</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="nx">RequestInit</span> <span class="o">=</span> <span class="p">{})</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">newRequestObject</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">params</span><span class="p">?.</span><span class="nx">beforeRequest</span><span class="p">?.(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">fetchRequest</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">params</span><span class="p">.</span><span class="nx">baseUrl</span> <span class="o">+</span> <span class="nx">url</span><span class="p">,</span> <span class="p">{</span> <span class="na">credentials</span><span class="p">:</span> <span class="dl">'</span><span class="s1">include</span><span class="dl">'</span><span class="p">,</span> <span class="err">…</span><span class="p">(</span><span class="nx">newRequestObject</span> <span class="o">||</span> <span class="nx">options</span><span class="p">),</span> <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="err">…</span><span class="p">(</span><span class="nx">auth</span> <span class="p">?</span> <span class="p">{</span> <span class="nx">auth</span> <span class="p">}</span> <span class="p">:</span> <span class="p">{}),</span> <span class="err">…</span><span class="p">(</span><span class="nx">showorg</span> <span class="p">?</span> <span class="p">{</span> <span class="nx">showorg</span> <span class="p">}</span> <span class="p">:</span> <span class="p">{}),</span> <span class="err">…</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">body</span> <span class="k">instanceof</span> <span class="nx">FormData</span> <span class="p">?</span> <span class="p">{}</span> <span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span> <span class="p">}),</span> <span class="na">Accept</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span> <span class="err">…</span><span class="nx">options</span><span class="p">?.</span><span class="nx">headers</span><span class="p">,</span> <span class="p">},</span> <span class="c1">// @ts-ignore</span> <span class="err">…</span><span class="p">(</span><span class="o">!</span><span class="nx">options</span><span class="p">.</span><span class="nx">next</span> <span class="o">&amp;&amp;</span> <span class="nx">options</span><span class="p">.</span><span class="nx">cache</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">force-cache</span><span class="dl">'</span> <span class="p">?</span> <span class="p">{</span> <span class="na">cache</span><span class="p">:</span> <span class="nx">options</span><span class="p">.</span><span class="nx">cache</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">no-store</span><span class="dl">'</span> <span class="p">}</span> <span class="p">:</span> <span class="p">{}),</span> <span class="p">});</span> <span class="k">if </span><span class="p">(</span> <span class="o">!</span><span class="nx">params</span><span class="p">?.</span><span class="nx">afterRequest</span> <span class="o">||</span> <span class="p">(</span><span class="k">await</span> <span class="nx">params</span><span class="p">?.</span><span class="nx">afterRequest</span><span class="p">?.(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">,</span> <span class="nx">fetchRequest</span><span class="p">))</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">fetchRequest</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// @ts-ignore</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">Promise</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{})</span> <span class="k">as</span> <span class="nx">Response</span><span class="p">;</span> <span class="p">};</span> <span class="p">};</span> </code></pre> </div> <p>Why???, I don’t know the answer yet but, I can tell there is “beforeRequest” and “afterRequest” processing happening based on the above code snippet</p> <h2> 3. The way the files are named. </h2> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFsmGfv6VqVsQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722026627438%3Fe%3D1727308800%26v%3Dbeta%26t%3D3nG_K1p4LNOmoIvipqbf96hQWci1gupblTGXntWjNrk" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFsmGfv6VqVsQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1722026627438%3Fe%3D1727308800%26v%3Dbeta%26t%3D3nG_K1p4LNOmoIvipqbf96hQWci1gupblTGXntWjNrk"></a></p> <p>I have never seen a service file named using dots like “custom.fetch.func.ts”. Sure, there’s config files named as tailwind.config.ts etc.,</p> <p>Here’s what chatGPT has to say about this: “ This kind of naming does not fit into a traditional case style like snake_case, kebab-case, or camelCase.</p> <p>However, if we ignore the file extension (“.ts”) and consider only “custom.fetch.func,” it can be seen as:</p> <p>Dot notation: This isn’t a standard case style but is sometimes used in programming to represent a hierarchical relationship or to namespace parts of a name. “</p> <p>To be honest, choose w/e naming conventions work for you. I use lowercase words separated by dashes as a file name, like custom-fetch-func.ts</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> Resources: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/e7b669f1253e3ef7ae6b9cc9d2f1d529ea86b288/libraries/helpers/src/utils/internal.fetch.ts#L4" rel="noopener noreferrer">https://github.com/gitroomhq/gitroom/blob/e7b669f1253e3ef7ae6b9cc9d2f1d529ea86b288/libraries/helpers/src/utils/internal.fetch.ts#L4</a> </li> <li> <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/tree/main" rel="noopener noreferrer">https://github.com/gitroomhq/gitroom/tree/main</a> </li> <li> <a href="proxy.php?url=https://github.com/gitroomhq/gitroom/blob/e7b669f1253e3ef7ae6b9cc9d2f1d529ea86b288/libraries/helpers/src/utils/custom.fetch.func.ts" rel="noopener noreferrer">https://github.com/gitroomhq/gitroom/blob/e7b669f1253e3ef7ae6b9cc9d2f1d529ea86b288/libraries/helpers/src/utils/custom.fetch.func.ts</a> </li> </ol> javascript typescript opensource nextjs shadcn-ui/ui codebase analysis: How does shadcn-ui CLI work? — Part 3.1 Ramu Narasinga Fri, 19 Jul 2024 22:01:53 +0000 https://dev.to/ramunarasinga/shadcn-uiui-codebase-analysis-how-does-shadcn-ui-cli-work-part-31-2cgo https://dev.to/ramunarasinga/shadcn-uiui-codebase-analysis-how-does-shadcn-ui-cli-work-part-31-2cgo <p>I wanted to find out how shadcn-ui CLI works. In this article, I discuss the code used to build the shadcn-ui/ui CLI.</p> <p>In part 3.0, I discussed how npx shadcn-ui add command is registered. We will look at few more lines of code from <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts" rel="noopener noreferrer">packages/cli/src/commands/add.ts</a>.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">cwd</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">cwd</span><span class="p">)</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nf">existsSync</span><span class="p">(</span><span class="nx">cwd</span><span class="p">))</span> <span class="p">{</span> <span class="nx">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="err">\</span><span class="s2">`The path </span><span class="p">${</span><span class="nx">cwd</span><span class="p">}</span><span class="s2"> does not exist. Please try again.</span><span class="se">\`</span><span class="s2">) process.exit(1) } const config = await getConfig(cwd) if (!config) { logger.warn( </span><span class="se">\`</span><span class="s2">Configuration is missing. Please run </span><span class="p">${</span><span class="nx">chalk</span><span class="p">.</span><span class="nf">green</span><span class="p">(</span> <span class="err">\</span><span class="s2">`init</span><span class="se">\`</span><span class="s2"> )} to create a components.json file.</span><span class="se">\`</span><span class="s2"> ) process.exit(1) } const registryIndex = await getRegistryIndex() </span></code></pre> </div> <p>So what’s happening in the above code snippet?</p> <ol> <li> Check if the cwd exists.</li> <li> getConfig function.</li> <li> getRegistryIndex.</li> </ol> <h2> Check if the cwd exists. </h2> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFccBQXYYdh5w%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721423819299%3Fe%3D1726704000%26v%3Dbeta%26t%3DoCWpyGVt11k1BYH4kfgy5zBKBvGio7wlvEg5t-jYGK0" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFccBQXYYdh5w%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721423819299%3Fe%3D1726704000%26v%3Dbeta%26t%3DoCWpyGVt11k1BYH4kfgy5zBKBvGio7wlvEg5t-jYGK0"></a></p> <p>This code checks the current directory exists and logs an error if it doesn’t.</p> <p>existsSync is imported from “fs” at the top of add.ts file.</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFTiW88-xqanQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721423819761%3Fe%3D1726704000%26v%3Dbeta%26t%3Db5QApc9SA8V8IgwS1WHsco8xFDu7YiYshmIMcx8QWWk" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFTiW88-xqanQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721423819761%3Fe%3D1726704000%26v%3Dbeta%26t%3Db5QApc9SA8V8IgwS1WHsco8xFDu7YiYshmIMcx8QWWk"></a></p> <h2> getConfig function </h2> <p>getConfig is not as simple as it looks like.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="kd">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getConfig</span><span class="p">(</span><span class="nx">cwd</span><span class="p">)</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">config</span><span class="p">)</span> <span class="p">{</span> <span class="nx">logger</span><span class="p">.</span><span class="nf">warn</span><span class="p">(</span> <span class="err">\</span><span class="s2">`Configuration is missing. Please run </span><span class="p">${</span><span class="nx">chalk</span><span class="p">.</span><span class="nf">green</span><span class="p">(</span> <span class="err">\</span><span class="s2">`init</span><span class="se">\`</span><span class="s2"> )} to create a components.json file.</span><span class="se">\`</span><span class="s2"> ) process.exit(1) } </span></code></pre> </div> <p>getConfig is imported from a different file named <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L55" rel="noopener noreferrer">get-config</a>. Reason behind this could be that context matters when it comes where you place your function. For example, logically, a function named getConfig can never be placed in a file named get-project-info.<br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">getConfig</span><span class="p">(</span><span class="nx">cwd</span><span class="p">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">config</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getRawConfig</span><span class="p">(</span><span class="nx">cwd</span><span class="p">)</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">config</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">null</span> <span class="p">}</span> <span class="k">return</span> <span class="k">await</span> <span class="nf">resolveConfigPaths</span><span class="p">(</span><span class="nx">cwd</span><span class="p">,</span> <span class="nx">config</span><span class="p">)</span> <span class="p">}</span> </code></pre> </div> <p>This function calls another function named getRawConfig.</p> <p>I explained in great detail how getConfig works in the article <a href="proxy.php?url=https://medium.com/@ramu.narasinga_61050/shadcn-ui-ui-codebase-analysis-how-does-shadcn-ui-cli-work-part-2-2-73cff5651b06" rel="noopener noreferrer">Part 2.2</a></p> <h2> getRegistryIndex </h2> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">getRegistryIndex</span><span class="p">()</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="err">\</span><span class="p">[</span><span class="nx">result</span><span class="err">\</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetchRegistry</span><span class="p">(</span><span class="err">\</span><span class="p">[</span><span class="dl">"</span><span class="s2">index.json</span><span class="dl">"</span><span class="err">\</span><span class="p">])</span> <span class="k">return</span> <span class="nx">registryIndexSchema</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="err">\</span><span class="s2">`Failed to fetch components from registry.</span><span class="se">\`</span><span class="s2">) } } </span></code></pre> </div> <p>A simple utility function that fetches index.json available at <a href="proxy.php?url=https://ui.shadcn.com/registry/index.json" rel="noopener noreferrer">https://ui.shadcn.com/registry/index.json</a></p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGfJABVfBRGiA%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721423819680%3Fe%3D1726704000%26v%3Dbeta%26t%3DTZwbpuViLF3gOmD8VDR6m3Y9rfM6pFSCeQBY9qWJ5GY" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGfJABVfBRGiA%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721423819680%3Fe%3D1726704000%26v%3Dbeta%26t%3DTZwbpuViLF3gOmD8VDR6m3Y9rfM6pFSCeQBY9qWJ5GY"></a></p> <h2> Conclusion: </h2> <p>Once the options and the argument passed to add command in shadcn-ui CLI, In the action, there is a lot of code, about 218 lines in <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L1" rel="noopener noreferrer">add.ts</a>. I picked a few lines of code that followed part 3.0 explanation and discussed about</p> <ol> <li> Check if the cwd exists.</li> </ol> <p>This code checks the current directory exists and logs an error if it doesn’t.</p> <p>existsSync is imported from “fs” at the top of add.ts file.</p> <p>2. getConfig function.</p> <p>getConfig is imported from a different file named <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L55" rel="noopener noreferrer">get-config</a>. Reason behind this could be that context matters when it comes where you place your function. For example, logically, a function named getConfig can never be placed in a file named get-project-info.</p> <p>I explained in great detail how getConfig works in the article <a href="proxy.php?url=https://medium.com/@ramu.narasinga_61050/shadcn-ui-ui-codebase-analysis-how-does-shadcn-ui-cli-work-part-2-2-73cff5651b06" rel="noopener noreferrer">Part 2.2</a></p> <p>3. getRegistryIndex.</p> <p>A simple utility function that fetches index.json available at <a href="proxy.php?url=https://ui.shadcn.com/registry/index.json" rel="noopener noreferrer">https://ui.shadcn.com/registry/index.json</a></p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L55" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L55</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L91" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-config.ts#L91</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/registry/index.ts#L19C1-L27C2" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/registry/index.ts#L19C1-L27C2</a> </li> </ol> javascript opensource nextjs shadcnui shadcn-ui/ui codebase analysis: How does shadcn-ui CLI work? — Part 3.0 Ramu Narasinga Thu, 18 Jul 2024 21:27:08 +0000 https://dev.to/ramunarasinga/shadcn-uiui-codebase-analysis-how-does-shadcn-ui-cli-work-part-30-1564 https://dev.to/ramunarasinga/shadcn-uiui-codebase-analysis-how-does-shadcn-ui-cli-work-part-30-1564 <p>I wanted to find out how shadcn-ui CLI works. In this article, I discuss the code used to build the shadcn-ui/ui CLI.</p> <p>In part 2.0 to 2.15, I discussed how <em>npx shadcn-ui init</em> works under the hood.</p> <p>We will look at how <em>npx shadcn-ui add </em> works in this part 3.x.</p> <p>Since the <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts" rel="noopener noreferrer">packages/cli/src/commands/add.ts</a> file is large, I will break this analysis down into parts and talk about code snippets and explain how stuff works.</p> <p>In this article, we will look the concepts:</p> <ol> <li> Add command.</li> <li> Commander.js package</li> <li> How add command is registered?</li> <li> Argument and options</li> <li> addOptionsSchema</li> </ol> <h2> add command </h2> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="k">export</span> <span class="kd">const</span> <span class="nx">add</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Command</span><span class="p">()</span> <span class="p">.</span><span class="nf">name</span><span class="p">(</span><span class="dl">"</span><span class="s2">add</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nf">description</span><span class="p">(</span><span class="dl">"</span><span class="s2">add a component to your project</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nf">argument</span><span class="p">(</span><span class="dl">"</span><span class="se">\</span><span class="s2">[components...</span><span class="se">\</span><span class="s2">]</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">the components to add</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-y, --yes</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">skip confirmation prompt.</span><span class="dl">"</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-o, --overwrite</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">overwrite existing files.</span><span class="dl">"</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span> <span class="dl">"</span><span class="s2">-c, --cwd &lt;cwd&gt;</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">the working directory. defaults to the current directory.</span><span class="dl">"</span><span class="p">,</span> <span class="nx">process</span><span class="p">.</span><span class="nf">cwd</span><span class="p">()</span> <span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-a, --all</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">add all available components</span><span class="dl">"</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-p, --path &lt;path&gt;</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">the path to add the component to.</span><span class="dl">"</span><span class="p">)</span> <span class="p">.</span><span class="nf">action</span><span class="p">(</span><span class="k">async </span><span class="p">(</span><span class="nx">components</span><span class="p">,</span> <span class="nx">opts</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> <span class="k">try</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="nx">addOptionsSchema</span><span class="p">.</span><span class="nf">parse</span><span class="p">({</span> <span class="nx">components</span><span class="p">,</span> <span class="p">...</span><span class="nx">opts</span><span class="p">,</span> <span class="p">})</span> </code></pre> </div> <p>We will begin with how add command is added. The above code snippet is picked from <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L31C1-L49C9" rel="noopener noreferrer">packages/cli/src/commands/add.ts</a></p> <h2> Commander.js package: </h2> <p>Command is imported from <a href="proxy.php?url=https://www.npmjs.com/package/commander" rel="noopener noreferrer">commander.js</a>, a complete solution for <a href="proxy.php?url=http://nodejs.org/" rel="noopener noreferrer">node.js</a> command-line interfaces.</p> <h2> How add command is registered? </h2> <p>The way add command is registered is that, if you open this <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/index.ts#L24" rel="noopener noreferrer">src/commands/index.ts</a> in a new tab, you will find this code as shown below</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD5612AQGLAnf0CAnolQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721337494563%3Fe%3D1726704000%26v%3Dbeta%26t%3Dsxji4B70awLuMDYj_ifFxLjicQujUaAsGz0x82eYKuQ" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD5612AQGLAnf0CAnolQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1721337494563%3Fe%3D1726704000%26v%3Dbeta%26t%3Dsxji4B70awLuMDYj_ifFxLjicQujUaAsGz0x82eYKuQ"></a></p> <p>Commands are created separately in the <a href="proxy.php?url=https://github.com/shadcn-ui/ui/tree/main/packages/cli/src/commands" rel="noopener noreferrer">folder named commands</a> for maintainability purposes. If you were to fork this shadcn-ui/ui repo and want to add your own command, this is one way to do it.</p> <h2> Argument and options </h2> <p>When you write something like npx shadcn-ui add Button, Button here is an argument.</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="p">.</span><span class="nf">argument</span><span class="p">(</span><span class="dl">"</span><span class="se">\</span><span class="s2">[components...</span><span class="se">\</span><span class="s2">]</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">the components to add</span><span class="dl">"</span><span class="p">)</span> </code></pre> </div> <p>The above code snippet is picked from <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L34C3-L34C56" rel="noopener noreferrer">here</a>.</p> <p>You also have options that go with your add command as shown below:</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-y, --yes</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">skip confirmation prompt.</span><span class="dl">"</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-o, --overwrite</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">overwrite existing files.</span><span class="dl">"</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span> <span class="dl">"</span><span class="s2">-c, --cwd &lt;cwd&gt;</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">the working directory. defaults to the current directory.</span><span class="dl">"</span><span class="p">,</span> <span class="nx">process</span><span class="p">.</span><span class="nf">cwd</span><span class="p">()</span> <span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-a, --all</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">add all available components</span><span class="dl">"</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span> <span class="p">.</span><span class="nf">option</span><span class="p">(</span><span class="dl">"</span><span class="s2">-p, --path &lt;path&gt;</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">the path to add the component to.</span><span class="dl">"</span><span class="p">)</span> </code></pre> </div> <p>and then you have action</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="p">.</span><span class="nf">action</span><span class="p">(</span><span class="k">async </span><span class="p">(</span><span class="nx">components</span><span class="p">,</span> <span class="nx">opts</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span> </code></pre> </div> <h2> addOptionsSchema </h2> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="nx">addOptionsSchema</span><span class="p">.</span><span class="nf">parse</span><span class="p">({</span> <span class="nx">components</span><span class="p">,</span> <span class="p">...</span><span class="nx">opts</span><span class="p">,</span> <span class="p">})</span> <span class="kd">const</span> <span class="nx">cwd</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">options</span><span class="p">.</span><span class="nx">cwd</span><span class="p">)</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nf">existsSync</span><span class="p">(</span><span class="nx">cwd</span><span class="p">))</span> <span class="p">{</span> <span class="nx">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="err">\</span><span class="s2">`The path </span><span class="p">${</span><span class="nx">cwd</span><span class="p">}</span><span class="s2"> does not exist. Please try again.</span><span class="se">\`</span><span class="s2">) process.exit(1) } </span></code></pre> </div> <p>The above snippet is picked from <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L34C3-L34C56" rel="noopener noreferrer">add.ts</a></p> <p>addOptionsSchema is declared just above the add function as shown below:</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="kd">const</span> <span class="nx">addOptionsSchema</span> <span class="o">=</span> <span class="nx">z</span><span class="p">.</span><span class="nf">object</span><span class="p">({</span> <span class="na">components</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="nx">z</span><span class="p">.</span><span class="nf">string</span><span class="p">()).</span><span class="nf">optional</span><span class="p">(),</span> <span class="na">yes</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">boolean</span><span class="p">(),</span> <span class="na">overwrite</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">boolean</span><span class="p">(),</span> <span class="na">cwd</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">string</span><span class="p">(),</span> <span class="na">all</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">boolean</span><span class="p">(),</span> <span class="na">path</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">string</span><span class="p">().</span><span class="nf">optional</span><span class="p">(),</span> <span class="p">})</span> </code></pre> </div> <p>This schema basically ensures all the options and arguments are valid before processing them further.</p> <h2> Conclusion: </h2> <p>In Part 2.0 to 2.15, I discussed how npx shadcn-ui init works under the hood. It is time for a version bump to my articles. In 3.x articles, I will write about how npx shadcn-ui add works under the hood. Please note that <a href="proxy.php?url=https://semver.org/" rel="noopener noreferrer">semver</a> is not applicable to my articles lol.</p> <p>Command is imported from <a href="proxy.php?url=https://www.npmjs.com/package/commander" rel="noopener noreferrer">commander.js</a>, a complete solution for <a href="proxy.php?url=http://nodejs.org/" rel="noopener noreferrer">node.js</a> command-line interfaces. The way add command is registered with npx shadcn-ui CLI is that, if you open this <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/index.ts#L24" rel="noopener noreferrer">src/commands/index.ts</a> in a new tab, you will find this code as shown below:</p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code> <span class="nx">program</span><span class="p">.</span><span class="nf">addCommand</span><span class="p">(</span><span class="nx">init</span><span class="p">).</span><span class="nf">addCommand</span><span class="p">(</span><span class="nx">add</span><span class="p">).</span><span class="nf">addCommand</span><span class="p">(</span><span class="nx">diff</span><span class="p">)</span> </code></pre> </div> <p>There’s an argument that accepts a single component name or an array of component names, that is why you would write something like npx shadcn-ui add Button</p> <p>Buttonhere is an argument. add command also has few options such -y, -o, -c, -a, -p. Read more about these in the <a href="proxy.php?url=https://ui.shadcn.com/docs/cli" rel="noopener noreferrer">shadcn-ui CLI documentation</a>.</p> <p>addOptionsSchema ensures that arguments and the options passed to the add command are valid using zod</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References </h2> <ol> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L31C1-L49C9" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L31C1-L49C9</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/index.ts#L24" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/index.ts#L24</a> </li> <li> <a href="proxy.php?url=https://www.npmjs.com/package/commander" rel="noopener noreferrer">https://www.npmjs.com/package/commander</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L22" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts#L22</a> </li> </ol> javascript opensource nextjs shadcnui shadcn-ui/ui codebase analysis: How does shadcn-ui CLI work? — Part 2.15 Ramu Narasinga Wed, 17 Jul 2024 21:26:42 +0000 https://dev.to/ramunarasinga/shadcn-uiui-codebase-analysis-how-does-shadcn-ui-cli-work-part-215-1f9p https://dev.to/ramunarasinga/shadcn-uiui-codebase-analysis-how-does-shadcn-ui-cli-work-part-215-1f9p <p>I wanted to find out how shadcn-ui CLI works. In this article, I discuss the code used to build the shadcn-ui/ui CLI.</p> <p>In part 2.11, we looked at runInit function and how shadcn-ui/ui ensures directories provided in resolvedPaths in config exist.</p> <p>The following operations are performed in runInit function:</p> <p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGFcCTs6FVIgQ%2Farticle-inline_image-shrink_1000_1488%2F0%2F1721251304237%3Fe%3D1726704000%26v%3Dbeta%26t%3DYxjC5hAoIja2KddEfvAmV9-4QG2M0UjAErwUXaTgYjo" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQGFcCTs6FVIgQ%2Farticle-inline_image-shrink_1000_1488%2F0%2F1721251304237%3Fe%3D1726704000%26v%3Dbeta%26t%3DYxjC5hAoIja2KddEfvAmV9-4QG2M0UjAErwUXaTgYjo"></a></p> <ol> <li> Ensure all resolved paths directories exist.</li> <li> Write tailwind config.</li> <li> Write css file.</li> <li> Write cn file.</li> <li> Install dependencies.</li> </ol> <p>1, 2, 3, 4 from the above are covered in part 2.12, 2.13 and 2.14, let’s find out how “Install dependencies” operation is done.</p> <h2> Install dependencies </h2> <p>The below code snippet is picked from <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/init.ts#L382" rel="noopener noreferrer">cli/src/commands/init.ts</a><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="c1">// Install dependencies.</span> <span class="kd">const</span> <span class="nx">dependenciesSpinner</span> <span class="o">=</span> <span class="nf">ora</span><span class="p">(</span><span class="err">\</span><span class="s2">`Installing dependencies...</span><span class="se">\`</span><span class="s2">)?.start() const packageManager = await getPackageManager(cwd) // TODO: add support for other icon libraries. const deps = \[ ...PROJECT\_DEPENDENCIES, config.style === "new-york" ? "@radix-ui/react-icons" : "lucide-react", \] await execa( packageManager, \[packageManager === "npm" ? "install" : "add", ...deps\], { cwd, } ) dependenciesSpinner?.succeed() </span></code></pre> </div> <p><a href="proxy.php?url=https://www.npmjs.com/package/ora" rel="noopener noreferrer">ora</a> is an elegant terminal spinner and is used to show the message “Installing dependencies…” when you run the npx shadcn init command.</p> <h3> getPackageManager </h3> <p>getPackageManager is imported <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-package-manager.ts#L3" rel="noopener noreferrer">packages/cli/src/utils/get-package-manager.ts</a><br> </p> <div class="highlight js-code-highlight"> <pre class="highlight javascript"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">detect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@antfu/ni</span><span class="dl">"</span> <span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nf">getPackageManager</span><span class="p">(</span> <span class="nx">targetDir</span><span class="p">:</span> <span class="nx">string</span> <span class="p">):</span> <span class="nb">Promise</span><span class="o">&lt;</span><span class="dl">"</span><span class="s2">yarn</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">pnpm</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">bun</span><span class="dl">"</span> <span class="o">|</span> <span class="dl">"</span><span class="s2">npm</span><span class="dl">"</span><span class="o">&gt;</span> <span class="p">{</span> <span class="kd">const</span> <span class="nx">packageManager</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">detect</span><span class="p">({</span> <span class="na">programmatic</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="na">cwd</span><span class="p">:</span> <span class="nx">targetDir</span> <span class="p">})</span> <span class="k">if </span><span class="p">(</span><span class="nx">packageManager</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">yarn@berry</span><span class="dl">"</span><span class="p">)</span> <span class="k">return</span> <span class="dl">"</span><span class="s2">yarn</span><span class="dl">"</span> <span class="k">if </span><span class="p">(</span><span class="nx">packageManager</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">pnpm@6</span><span class="dl">"</span><span class="p">)</span> <span class="k">return</span> <span class="dl">"</span><span class="s2">pnpm</span><span class="dl">"</span> <span class="k">if </span><span class="p">(</span><span class="nx">packageManager</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">bun</span><span class="dl">"</span><span class="p">)</span> <span class="k">return</span> <span class="dl">"</span><span class="s2">bun</span><span class="dl">"</span> <span class="k">return</span> <span class="nx">packageManager</span> <span class="o">??</span> <span class="dl">"</span><span class="s2">npm</span><span class="dl">"</span> <span class="p">}</span> </code></pre> </div> <p>Have you ever used npm in a pnpm project? I have and often times, it fails to install the package because you are using npm in a pnpm project.</p> <p><a href="proxy.php?url=https://github.com/antfu-collective/ni#readme" rel="noopener noreferrer">@antfu/ni</a> lets you use the right package manager and detect is a function that gives the packageManager used in a given project based on cwd.</p> <p>I cannot find anything mentioned about detect method in the <a href="proxy.php?url=https://github.com/antfu-collective/ni#readme" rel="noopener noreferrer">@antfu/ni</a> Github readme. How would you know such a method exists unless you read it in some open source codebase?</p> <h3> execa </h3> <p><a href="proxy.php?url=https://www.npmjs.com/package/execa" rel="noopener noreferrer">Execa</a> runs commands in your script, application or library. Unlike shells, it is <a href="proxy.php?url=https://github.com/sindresorhus/execa/blob/HEAD/docs/bash.md" rel="noopener noreferrer">optimized</a> for programmatic usage. Built on top of the child_process core module. This is built by the legend, <a href="proxy.php?url=https://github.com/sindresorhus" rel="noopener noreferrer">Sindre Sorhus</a></p> <p>shadcn-ui/ui CLI uses execa to install the neccessary dependencies as part of npx shadcn-ui init command.</p> <h2> Conclusion </h2> <p>shadcn-ui CLI uses <a href="proxy.php?url=https://www.npmjs.com/package/execa" rel="noopener noreferrer">execa</a>, built by the legend, <a href="proxy.php?url=https://github.com/sindresorhus" rel="noopener noreferrer">Sindre Sorhu</a>. Execa is used to install the necessary dependencies in a script file. We all are familiar with executing installation commands but if you want to install some packages in a script programatically, execa can be used.</p> <p>shadcn-ui CLI detects the package manager used in your project using “detect” method from <a href="proxy.php?url=https://github.com/antfu-collective/ni#readme" rel="noopener noreferrer">@antfu/ni</a>.</p> <p>This article marks my quest to study and learn what happens under the hood when you run npx shadcn-ui init command as completed.</p> <p>I am moving on to study the <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/add.ts" rel="noopener noreferrer">add command</a>.</p> <p><a href="proxy.php?url=https://app.thinkthroo.com/build-from-scratch" rel="noopener noreferrer"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.licdn.com%2Fdms%2Fimage%2FD4E12AQFdaAsIZFCFDQ%2Farticle-inline_image-shrink_1500_2232%2F0%2F1723241642441%3Fe%3D1728518400%26v%3Dbeta%26t%3DOL_AsRxQ5ylHW9ND_A_a6Af5F-yUgySsQzaRNFvT7vI"></a></p> <blockquote> <p><a href="proxy.php?url=https://app.thinkthroo.com/best-practices" rel="noopener noreferrer"><em>Get free courses inspired by the best practices used in open source.</em></a></p> </blockquote> <h2> About me: </h2> <p>Website: <a href="proxy.php?url=https://ramunarasinga.com/" rel="noopener noreferrer">https://ramunarasinga.com/</a></p> <p>Linkedin: <a href="proxy.php?url=https://www.linkedin.com/in/ramu-narasinga-189361128/" rel="noopener noreferrer">https://www.linkedin.com/in/ramu-narasinga-189361128/</a></p> <p>Github: <a href="proxy.php?url=https://github.com/Ramu-Narasinga" rel="noopener noreferrer">https://github.com/Ramu-Narasinga</a></p> <p>Email: <a href="proxy.php?url=//mailto:[email protected]">[email protected]</a></p> <p><a href="proxy.php?url=https://thinkthroo.com/" rel="noopener noreferrer">Learn the best practices used in open source.</a></p> <h2> References: </h2> <ol> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/init.ts#L382" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/commands/init.ts#L382</a> </li> <li> <a href="proxy.php?url=https://www.npmjs.com/package/ora" rel="noopener noreferrer">https://www.npmjs.com/package/ora</a> </li> <li> <a href="proxy.php?url=https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-package-manager.ts#L3" rel="noopener noreferrer">https://github.com/shadcn-ui/ui/blob/main/packages/cli/src/utils/get-package-manager.ts#L3</a> </li> <li> <a href="proxy.php?url=https://www.npmjs.com/package/@antfu/ni" rel="noopener noreferrer">https://www.npmjs.com/package/@antfu/ni</a> </li> <li> <a href="proxy.php?url=https://github.com/antfu-collective/ni#readme" rel="noopener noreferrer">https://github.com/antfu-collective/ni#readme</a> </li> <li> <a href="proxy.php?url=https://github.com/search?q=import+%7B+detect+%7D+from+%22%40antfu%2Fni%22&amp;type=code" rel="noopener noreferrer">https://github.com/search?q=import+%7B+detect+%7D+from+%22%40antfu%2Fni%22&amp;type=code</a> </li> <li> <a href="proxy.php?url=https://www.npmjs.com/package/execa" rel="noopener noreferrer">https://www.npmjs.com/package/execa</a> </li> <li> <a href="proxy.php?url=https://github.com/sindresorhus" rel="noopener noreferrer">https://github.com/sindresorhus</a> </li> </ol> javascript nextjs shadcnui opensource