<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Okiki Ojo</title>
    <description>The latest articles on Forem by Okiki Ojo (@okikio).</description>
    <link>https://forem.com/okikio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F340695%2Fdb28f94d-badf-4753-86f3-6c976a390f1e.png</url>
      <title>Forem: Okiki Ojo</title>
      <link>https://forem.com/okikio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/okikio"/>
    <language>en</language>
    <item>
      <title>Hot take: Apple v US gov't, the US gov't is right</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Fri, 05 Apr 2024 03:58:12 +0000</pubDate>
      <link>https://forem.com/okikio/hot-take-apple-v-us-govt-the-us-govt-is-right-4k1n</link>
      <guid>https://forem.com/okikio/hot-take-apple-v-us-govt-the-us-govt-is-right-4k1n</guid>
      <description>&lt;p&gt;There seems to be a misunderstanding as to how the US gov't is using anti-competition laws in this case against Apple. I'm no legal expert but from what I've read, the core problem is Apple's various platforms allowing 3rd parties but through &lt;strong&gt;Market Manipulation&lt;/strong&gt; (limited API access, stopping legal and fair products from appearing on their stores, etc...) never actually allow for free and fair competition on said Platforms and as thus is liable to be sued.&lt;/p&gt;

&lt;p&gt;Basically you can summarize the law suit down to 2 points,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Apple is using their platform to limit competition in unfair ways; if iOS and others were not open to 3rd parties this really wouldn't be a problem&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apple may not be the largest player per-se but their &lt;strong&gt;Market Power&lt;/strong&gt; and &lt;strong&gt;Abuse&lt;/strong&gt; of said power is an indication of &lt;strong&gt;Monopolistic&lt;/strong&gt; behaviour&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : Gov't don't &lt;em&gt;necessarily&lt;/em&gt; care if you're a Monopoly, what they care about is if you're actively abusing your enhanced Market Position to limit competition or promote your own products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Another Note&lt;/strong&gt; : The US gov't doesn't necessarily use anti-trust laws for the sake of consumers they use it to guarantee competition and a fair playground/market place with the idea being that with more competition, customers get better deals and better products&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Another Another Note&lt;/strong&gt; : The US gov't is not suing Apple for the Phones (they can't touch that since people are voting with their wallets there) they're suing Apple for their OS, and the rules being unfair on said OS&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;TL;DR;&lt;/strong&gt; Apple is abusing &lt;strong&gt;Market Power&lt;/strong&gt; in a way that makes it difficult to compete on their platforms. Apple may have built the best sandbox but so long as they're allowing others to play in that sandbox they need to make sure the game is played fairly, this same rule applied to Microsoft during their anti-trust lawsuit case. Windows is Microsoft's baby but if Microsoft unfairly promotes their products at the detriment to other competitors then anti-trust lawsuit (as simple as that)&lt;/p&gt;




&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@einfachlaurenz?utm_source=dev.to&amp;amp;utm_medium=referral"&gt;Laurenz Heymann&lt;/a&gt; on &lt;a href="https://unsplash.com/?utm_source=dev.to&amp;amp;utm_medium=referral"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hottake</category>
      <category>lawsuit</category>
      <category>antitrust</category>
      <category>devs</category>
    </item>
    <item>
      <title>From Docker to Podman - VS Code DevContainers</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Thu, 12 Oct 2023 06:58:29 +0000</pubDate>
      <link>https://forem.com/okikio/from-docker-to-podman-vs-code-devcontainers-3206</link>
      <guid>https://forem.com/okikio/from-docker-to-podman-vs-code-devcontainers-3206</guid>
      <description>&lt;h2&gt;
  
  
  Been a while... 👀
&lt;/h2&gt;

&lt;p&gt;It's been a minute, hasn't it? I've been...busy, but I'm back with something that I think you'll find both useful and intriguing. While I was away, I stumbled upon a nifty trick that involves swapping Docker for &lt;a href="https://podman.io/" rel="noopener noreferrer"&gt;Podman&lt;/a&gt; in &lt;a href="https://code.visualstudio.com/docs/devcontainers/containers" rel="noopener noreferrer"&gt;DevContainers&lt;/a&gt; on VS Code. This isn't a step-by-step guide; it's more like sharing a cool discovery I made. So, let's get to it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vt5hq36bs64cx5u4lkq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vt5hq36bs64cx5u4lkq.gif" alt="GIF from Titanic of Rose Dewitt Bukater who is now much older than when the Titanic occured, saying " width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;A Quick Refresher on DevContainers 🏝&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you're new to the concept, DevContainers in VS Code are a way to containerize your development environment. This ensures that your setup is both portable and consistent, effectively eliminating the "it works on my machine" syndrome.&lt;/p&gt;

&lt;p&gt;Think of DevContainers as your very own &lt;a href="https://github.com/features/codespaces" rel="noopener noreferrer"&gt;Codespace&lt;/a&gt; or &lt;a href="https://www.gitpod.io/" rel="noopener noreferrer"&gt;Gitpod&lt;/a&gt;, but without needing the cloud. It's like having a sandbox, but your computer is the playground. You get to build your castles in an isolated space, keeping the rest of your system pristine.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Podman Appeal 🎸&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Switching gears to Podman, why would you want to replace Docker? Imagine Docker as the old, reliable minivan. It gets the job done, but it's not the sleekest. Podman is like the electric car that just rolled off the assembly lineefficient, user-friendly, and secure.&lt;/p&gt;

&lt;p&gt;Docker has been the go-to containerization tool for years, but Podman is emerging as a strong alternative. Podman offers a few advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rootless Containers&lt;/strong&gt; : Run containers without needing root privileges, enhancing security. &lt;a href="https://opensource.com/article/19/2/how-does-rootless-podman-work" rel="noopener noreferrer"&gt;Learn more about rootless containers&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Systemd Integration&lt;/strong&gt; : Better integration with Linux's init system, &lt;a href="https://www.freedesktop.org/wiki/Software/systemd/" rel="noopener noreferrer"&gt;systemd&lt;/a&gt;. For Linux users, this is a significant benefit. Podman's compatibility with &lt;a href="https://www.freedesktop.org/wiki/Software/systemd/" rel="noopener noreferrer"&gt;systemd&lt;/a&gt; offers better process management and orchestration. &lt;a href="https://www.linux.com/training-tutorials/understanding-and-using-systemd/" rel="noopener noreferrer"&gt;Learn more about systemd&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Follows Open Standards&lt;/strong&gt; : Fully compatible with &lt;a href="https://opencontainers.org/" rel="noopener noreferrer"&gt;Open Container Initiative (OCI)&lt;/a&gt; standards. Podman is &lt;a href="https://opencontainers.org/" rel="noopener noreferrer"&gt;OCI-compliant&lt;/a&gt;, which means it adheres to industry standards for container images, making it easier to switch between different container technologies. &lt;a href="https://opencontainers.org/about/overview/" rel="noopener noreferrer"&gt;Learn more about OCI&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, if you're looking to break free from Docker's grasp, Podman is worth considering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge: VS Code Loves Docker
&lt;/h2&gt;

&lt;p&gt;Here's the hiccup: VS Code's DevContainers extension is tightly coupled with Docker. When you try to use Podman, VS Code throws a fit and keeps asking you to install Docker. That's the issue we're going to solve today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.discordapp.net%2Fattachments%2F988952050345857024%2F1161839612466044928%2FCleanShot_2023-10-11_at_21.35.322x.png%3Fex%3D6539c275%26is%3D65274d75%26hm%3Dfa14bd8f116da084ee5f64a6cc269e4e52daa74af17f4a3f153aed7fd98933f2%26%3D%26width%3D1314%26height%3D1302" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia.discordapp.net%2Fattachments%2F988952050345857024%2F1161839612466044928%2FCleanShot_2023-10-11_at_21.35.322x.png%3Fex%3D6539c275%26is%3D65274d75%26hm%3Dfa14bd8f116da084ee5f64a6cc269e4e52daa74af17f4a3f153aed7fd98933f2%26%3D%26width%3D1314%26height%3D1302" alt="Image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Trick VS Code with a Shell Script
&lt;/h2&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 1: Install Podman&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;First things first, you'll need to install Podman. On a Mac, you can use &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;podman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Step 2: Initialize and Start Podman Machine&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Before using Podman, you need to initialize and start a Podman machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman machine init
podman machine start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Step 3: Create a Shell Script&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;VS Code is looking for a command named &lt;code&gt;docker&lt;/code&gt;. We'll give it what it wants, but we'll secretly redirect it to &lt;code&gt;podman&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a new shell script and name it &lt;code&gt;docker&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /usr/local/bin/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the script, add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;podman &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file and exit the text editor.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 4: Make the Script Executable&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now, make the script executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Step 5: Restart VS Code&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Close and reopen VS Code to apply the changes. It should now be none the wiser, happily using Podman instead of Docker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 1: Install Podman&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Open your terminal and run the following command to install Podman:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; podman
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;strong&gt;Steps 2-5: Follow the same steps as for macOS&lt;/strong&gt;
&lt;/h4&gt;

&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 1: Install Podman for Windows&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Download and install Podman from the &lt;a href="https://podman.io/docs/installation#windows" rel="noopener noreferrer"&gt;official site&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 2: Create a Batch File&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Create a batch file named &lt;code&gt;docker.bat&lt;/code&gt; to act as an alias for Podman:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="kd"&gt;podman&lt;/span&gt; &lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place this batch file in a directory that's in your system's &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;Option 1: Use an Existing Directory in&lt;/strong&gt; &lt;code&gt;PATH&lt;/code&gt;
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open a Command Prompt and type &lt;code&gt;echo %PATH%&lt;/code&gt; to see the directories currently in your &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose an existing directory that you have write access to, such as &lt;code&gt;C:\Users\YourUsername\bin&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save or move your &lt;code&gt;docker.bat&lt;/code&gt; file into that directory.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;Option 2: Create a New Directory and Add It to&lt;/strong&gt; &lt;code&gt;PATH&lt;/code&gt;
&lt;/h5&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a new directory where you want to store your batch files, for example, &lt;code&gt;C:\batch_files&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Move your &lt;code&gt;docker.bat&lt;/code&gt; file into this new directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To add this directory to your &lt;code&gt;PATH&lt;/code&gt;, right-click on 'This PC' or 'Computer' on your desktop or File Explorer, and choose &lt;code&gt;Properties&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the left-hand side, click on &lt;code&gt;Advanced system settings&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the &lt;code&gt;Environment Variables&lt;/code&gt; button near the bottom right.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under the 'System variables' section, find and select the &lt;code&gt;Path&lt;/code&gt; variable, then click on &lt;code&gt;Edit&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on &lt;code&gt;New&lt;/code&gt; and add the path to your new directory, &lt;code&gt;C:\batch_files&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;code&gt;OK&lt;/code&gt; on all the dialog boxes to save your changes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;Verify the Setup&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;To verify that the batch file is accessible:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open a new Command Prompt window (important, as existing windows won't pick up the change).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type &lt;code&gt;docker&lt;/code&gt; and hit Enter. If everything is set up correctly, this should now execute Podman due to the aliasing in your &lt;code&gt;docker.bat&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By following these steps, you ensure that the batch file is in a directory listed in your &lt;code&gt;PATH&lt;/code&gt;, making it accessible from any command prompt window.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 3: Restart VS Code&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Close and reopen VS Code to apply the changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;There you have ita Podman-powered DevContainer in VS Code, right on your local machine. It's like having a VIP pass to a more secure and efficient coding environment. Whether you're a newcomer or a seasoned developer, I hope you find this as useful as I did.&lt;/p&gt;

&lt;p&gt;So go ahead, give Podman a spin, and bring a little more freedom to your containerized development environments.&lt;/p&gt;

&lt;p&gt;Bye!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fopr0v7mdl4wvyfxjkr8p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fopr0v7mdl4wvyfxjkr8p.gif" alt="Video gif. Two young men stand together. One holds up his fingers as if saying peace out and vanishes." width="318" height="178"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Originally posted on &lt;a href="https://blog.okikio.dev/from-docker-to-podman-vs-code-devcontainers" rel="noopener noreferrer"&gt;blog.okikio.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>podman</category>
      <category>vscode</category>
    </item>
    <item>
      <title>ESM &amp; CJS: The subtle shift in bundlejs' behaviour</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Sat, 08 Jul 2023 07:40:48 +0000</pubDate>
      <link>https://forem.com/okikio/changelog-july-8-2023-the-subtle-shift-in-bundlejs-behaviour-4bpg</link>
      <guid>https://forem.com/okikio/changelog-july-8-2023-the-subtle-shift-in-bundlejs-behaviour-4bpg</guid>
      <description>&lt;p&gt;Greetings, fellow devs and bundlejs aficionados! 🚀&lt;/p&gt;

&lt;p&gt;I was closing out some long lived issues over on &lt;a href="http://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs&lt;/a&gt;, when issue &lt;a href="https://github.com/okikio/bundlejs/issues/50" rel="noopener noreferrer"&gt;#50&lt;/a&gt; reminded me of the ongoing debate about how bundlejs should handle the ESM and CJS packages.&lt;/p&gt;

&lt;p&gt;Lightbulbs flickered, coffee was consumed (I don't drink coffee, but you get the point), and I'm pretty sure I've cracked a solution. But there are a few slight behavior changes you need to be aware of. So buckle up. If anything looks off or confusing, please let me know in either the comments below, on &lt;a href="https://github.com/okikio/bundlejs/discussions/53" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; or issues &lt;a href="https://github.com/okikio/bundlejs/issues/50" rel="noopener noreferrer"&gt;#50&lt;/a&gt; directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Issue at Hand 🧐
&lt;/h2&gt;

&lt;p&gt;The root of the problem lies in how bundlejs was handling CommonJS files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"CommonJS files only export their default, which created a conundrum. How can we tell if a module is CommonJS if we don't fetch it? And if we fetch it first, it's too late for esbuild to deal with it properly."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But never fear! I've worked out a solution that is impartial to whether the module is ESM or CommonJS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution 🧩
&lt;/h2&gt;

&lt;p&gt;Let's walk through this step by step, using our handy &lt;a href="http://deno.bundlejs.com" rel="noopener noreferrer"&gt;deno.bundlejs.com&lt;/a&gt; API for demonstration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single CJS File:&lt;/strong&gt; Only export the default (no renaming involved). Check out this example using &lt;code&gt;postcss-easings&lt;/code&gt;: &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=postcss-easings&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=postcss-easings&amp;amp;minify=false&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
 &lt;span class="c1"&gt;// virtual-filesystem:/index.ts&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;index_exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
 &lt;span class="nf"&gt;__export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index_exports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;import_postcss_easings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="nf"&gt;__reExport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index_exports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;__toESM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_postcss_easings_4_0&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;import_postcss_easings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__toESM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_postcss_easings_4_0&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;export_default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;import_postcss_easings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;export_default&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;
 &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multiple CJS Files:&lt;/strong&gt; Export the default but use the file URL as the import name. Also, append &lt;code&gt;...Default&lt;/code&gt; to the end of it like so: &lt;code&gt;reactDefault&lt;/code&gt; &amp;amp; &lt;code&gt;reactDomDefault&lt;/code&gt;. Take a peek here: &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=react,react-dom&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=react,react-dom&amp;amp;minify=false&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
 &lt;span class="c1"&gt;// virtual-filesystem:/index.ts&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;index_exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
 &lt;span class="nf"&gt;__export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index_exports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;reactDefault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;import_react&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;reactDomDefault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;import_react_dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="nf"&gt;__reExport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index_exports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;__toESM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_react_18_2&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;import_react&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__toESM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_react_18_2&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
 &lt;span class="nf"&gt;__reExport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index_exports&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;__toESM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_react_dom_18_2&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;import_react_dom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__toESM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;require_react_dom_18_2&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;export_reactDefault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;import_react&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;export_reactDomDefault&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;import_react_dom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;export_reactDefault&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;reactDefault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;export_reactDomDefault&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;reactDomDefault&lt;/span&gt;
 &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single ESM File:&lt;/strong&gt; Export everything, including the default. No renaming here, folks! &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=spring-easing&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=spring-easing&amp;amp;minify=false&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;BatchSpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;CSSSpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseInOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseOutIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingDurationCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingFunctionKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingFunctions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;FramePtsCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;GenerateSpringFrames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;INFINITE_LOOP_LIMIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringInFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringInOutFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringOutFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringOutInFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateComplex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateUsingIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringEasing&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- The default export&lt;/span&gt;
   &lt;span class="nx"&gt;getLinearSyntax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getOptimizedPoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getSpringDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getUnit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateComplex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateUsingIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;isNumberLike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;parseEasingParameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;ramerDouglasPeucker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;registerEasingFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;registerEasingFunctions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;squaredSegmentDistance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;toAnimationFrames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;toFixed&lt;/span&gt;
 &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multiple ESM Files:&lt;/strong&gt; Export all methods and variables from all exports, but remember to use the naming rules for all default exports. For instance, after exporting the other module exports, rename the default export to &lt;code&gt;springEasingDefault&lt;/code&gt; and &lt;code&gt;codepointIteratorDefault&lt;/code&gt;: &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,codepoint-iterator&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,codepoint-iterator&amp;amp;minify=false&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mod_default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asCodePointsIterator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;BITS_FOR_2B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;BITS_FOR_3B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;BITS_FOR_4B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;BatchSpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;CSSSpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseInOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseOutIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingDurationCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingFunctionKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingFunctions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;FramePtsCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;GenerateSpringFrames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;INFINITE_LOOP_LIMIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;LEAD_FOR_1B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;LEAD_FOR_2B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;LEAD_FOR_3B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;LEAD_FOR_4B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;LEAD_FOR_5B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;MASK_FOR_1B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;MASK_FOR_2B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;MASK_FOR_3B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;MASK_FOR_4B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringInFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringInOutFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringOutFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringOutInFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;UTF8_MAX_BYTE_LENGTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;asCodePointsArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;asCodePointsCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;asCodePointsIterator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateComplex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateUsingIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;bytesToCodePoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;bytesToCodePointFromBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;codePointAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;mod_default&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;codepointIteratorDefault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- codepoint-iterator's default export&lt;/span&gt;
   &lt;span class="nx"&gt;getByteLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getIterableStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getLinearSyntax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getOptimizedPoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getSpringDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getUnit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateComplex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateUsingIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;isNumberLike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;parseEasingParameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;ramerDouglasPeucker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;registerEasingFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;registerEasingFunctions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringEasing&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;springEasingDefault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- spring-easing's default export&lt;/span&gt;
   &lt;span class="nx"&gt;squaredSegmentDistance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;toAnimationFrames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;toFixed&lt;/span&gt;
 &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Treeshaking:&lt;/strong&gt; Here, we assume you want the driver's seat if you add the treeshake query param to the URL. So, all of the above rules are null and void, and you now have complete control over what is exported, including the default exports not being automatic. &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,react&amp;amp;treeshake=[*],[*]&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,react&amp;amp;treeshake=[*],[*]&amp;amp;minify=false&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
 &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;BatchSpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;CSSSpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseInOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EaseOutIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingDurationCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingFunctionKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingFunctions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;EasingOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;FramePtsCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;GenerateSpringFrames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;INFINITE_LOOP_LIMIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringEasing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringInFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringInOutFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringOutFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;SpringOutInFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateComplex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;batchInterpolateUsingIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getLinearSyntax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getOptimizedPoints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getSpringDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;getUnit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateComplex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateSequence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;interpolateUsingIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;isNumberLike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;parseEasingParameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;ramerDouglasPeucker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;registerEasingFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;registerEasingFunctions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;squaredSegmentDistance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;toAnimationFrames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="nx"&gt;toFixed&lt;/span&gt;
 &lt;span class="p"&gt;};&lt;/span&gt;
 &lt;span class="c1"&gt;// ^ React isn't exported at all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🚨 Note: You might run into issues with CJS modules if you don't export default properly. Part of this is because tree-shaking is somewhat of a no-show for CJS packages, so tread lightly here!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There you have it! A quick and dirty rundown of the latest updates to the way &lt;a href="http://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; handles CJS and ESM packages.&lt;/p&gt;

&lt;p&gt;So, go ahead and take the new system for a spin. Let me know what you think. Take it for a ride 🚗&lt;/p&gt;

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

&lt;p&gt;Photo by Marcin Jozwiak: &lt;a href="https://www.pexels.com/photo/abstract-red-and-white-waves-background-subtle-gradients-flow-liquid-lines-design-element-13835514/" rel="noopener noreferrer"&gt;https://www.pexels.com/photo/abstract-red-and-white-waves-background-subtle-gradients-flow-liquid-lines-design-element-13835514/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>esm</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>cjs</category>
    </item>
    <item>
      <title>Mastering the Art of ESM and CJS Package Handling</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Sat, 08 Jul 2023 07:40:48 +0000</pubDate>
      <link>https://forem.com/okikio/mastering-the-art-of-esm-and-cjs-package-handling-3g4o</link>
      <guid>https://forem.com/okikio/mastering-the-art-of-esm-and-cjs-package-handling-3g4o</guid>
      <description>&lt;p&gt;Greetings, fellow devs and bundlejs aficionados! 🚀&lt;/p&gt;

&lt;p&gt;I was closing out some long lived issues over on &lt;a href="http://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs&lt;/a&gt;, when issue &lt;a href="https://github.com/okikio/bundlejs/issues/50" rel="noopener noreferrer"&gt;#50&lt;/a&gt; reminded me of the ongoing debate about how bundlejs should handle the ESM and CJS packages.&lt;/p&gt;

&lt;p&gt;Lightbulbs flickered, coffee was consumed (I don't drink coffee, but you get the point), and I'm pretty sure I've cracked a solution. But there are a few slight behavior changes you need to be aware of. So buckle up. If anything looks off or confusing, please let me know in either the comments below, on &lt;a href="https://github.com/okikio/bundlejs/discussions/53" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; or issue &lt;a href="https://github.com/okikio/bundlejs/issues/50" rel="noopener noreferrer"&gt;#50&lt;/a&gt; directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Issue at Hand 🧐
&lt;/h2&gt;

&lt;p&gt;The root of the problem lies in how bundlejs was handling CommonJS files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"CommonJS files only export their default, which created a conundrum. How can we tell if a module is CommonJS if we don't fetch it? And if we fetch it first, it's too late for esbuild to deal with it properly."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But never fear! I've worked out a solution that is impartial to whether the module is ESM or CommonJS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution 🧩
&lt;/h2&gt;

&lt;p&gt;Let's walk through this step by step, using our handy &lt;a href="http://deno.bundlejs.com" rel="noopener noreferrer"&gt;deno.bundlejs.com&lt;/a&gt; API for demonstration.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single CJS File:&lt;/strong&gt; Only export the default (no renaming involved). Check out this example using &lt;code&gt;postcss-easings&lt;/code&gt;: &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=postcss-easings&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=postcss-easings&amp;amp;minify=false&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple CJS Files:&lt;/strong&gt; Export the default but use the file URL as the import name. Also, append &lt;code&gt;...Default&lt;/code&gt; to the end of it like so: &lt;code&gt;reactDefault&lt;/code&gt; &amp;amp; &lt;code&gt;reactDomDefault&lt;/code&gt;. Take a peek here: &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=react,react-dom&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=react,react-dom&amp;amp;minify=false&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Single ESM File:&lt;/strong&gt; Export everything, including the default. No renaming here, folks! &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=spring-easing&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=spring-easing&amp;amp;minify=false&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multiple ESM Files:&lt;/strong&gt; Export all methods and variables from all exports, but remember to use the naming rules for all default exports. For instance, after exporting the other module exports, rename the default export to &lt;code&gt;springEasingDefault&lt;/code&gt; and &lt;code&gt;codepointIteratorDefault&lt;/code&gt;: &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,codepoint-iterator&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,codepoint-iterator&amp;amp;minify=false&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treeshaking:&lt;/strong&gt; Here, we assume you want the driver's seat if you add the treeshake query param to the URL. So, all of the above rules are null and void, and you now have complete control over what is exported, including the default exports not being automatic. &lt;a href="https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,react&amp;amp;treeshake=%5B*%5D,%5B*%5D&amp;amp;minify=false" rel="noopener noreferrer"&gt;https://deno.bundlejs.com/?file&amp;amp;q=spring-easing,react&amp;amp;treeshake=[*],[*]&amp;amp;minify=false&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨 Note: You might run into issues with CJS modules if you don't export default properly. Part of this is because tree-shaking is somewhat of a no-show for CJS packages, so tread lightly here!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There you have it! A quick and dirty rundown of the latest updates to the way &lt;a href="http://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; handles CJS and ESM packages.&lt;/p&gt;

&lt;p&gt;So, go ahead and take the new system for a spin. Let me know what you think. Take it for a ride 🚗&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgflip.com%2F7rwjsv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi.imgflip.com%2F7rwjsv.jpg" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://www.pexels.com/photo/abstract-red-and-white-waves-background-subtle-gradients-flow-liquid-lines-design-element-13835514/" rel="noopener noreferrer"&gt;Marcin Jozwiak&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originally posted on &lt;a href="https://blog.okikio.dev/mastering-the-art-of-esm-and-cjs-package-handling" rel="noopener noreferrer"&gt;blog.okikio.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>esm</category>
      <category>node</category>
    </item>
    <item>
      <title>Vercel + Support Center = 🚀</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Tue, 07 Feb 2023 04:31:37 +0000</pubDate>
      <link>https://forem.com/okikio/vercel-support-center--3pf7</link>
      <guid>https://forem.com/okikio/vercel-support-center--3pf7</guid>
      <description>&lt;p&gt;Support Center; my first major project at &lt;a href="https://vercel.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Vercel&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;,&lt;/strong&gt; it's been a blast working on it and it's now out. 🎉&lt;/p&gt;

&lt;p&gt;Check it out &lt;a href="https://vercel.com/changelog/support-center" rel="noopener noreferrer"&gt;vercel.com/changelog/support-center&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;BTW, I joined &lt;strong&gt;Vercel&lt;/strong&gt; 🎉 ~6 months ago...&lt;/p&gt;

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

&lt;p&gt;Well anyway...&lt;/p&gt;

&lt;p&gt;Support Center is a modern tool for viewing and communicating with Customer Support at Vercel. Built for enterprises to improve the customer support experience while retaining the practices and tools that the Customer Support Team at Vercel had built up over the years. From what customers and the Customer Support Team have told us we've achieved that goal.&lt;/p&gt;

&lt;p&gt;For some background. Before Support Center, customers had to email support before they could get any help 😬, it was clunky and prone to _" &lt;strong&gt;**email got sent to spam errors"&lt;/strong&gt; _, and _" &lt;strong&gt;**I didn't check my email problems"&lt;/strong&gt; _, realizing that the experience wouldn't do, the dev team drafted up the idea to connect the current Salesforce based ticketing system with a UI/UX customers would enjoy.&lt;/p&gt;

&lt;p&gt;The main hiccup we ran into during development was the excessively complex (and archaic) work of &lt;strong&gt;integrating with Salesforce&lt;/strong&gt; and the performance of the support center, of which the solution is &lt;strong&gt;caching&lt;/strong&gt; (the bane of all Software Engineers everywhere).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📚 &lt;strong&gt;Fun fact&lt;/strong&gt; : Salesforces uses a SQL-like syntax for querying data that's similar to SQL but yet very far away from SQL, it's called &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm" rel="noopener noreferrer"&gt;SOQL&lt;/a&gt;, and working with it can be very frustrating&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problems boiled down to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;How do you keep the cache always up to date?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to keep the support center fast, responsive, and reliable with the most up-to-date info?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to make working with Salesforce easier?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The solution to the first 2 problems... 🥁 are &lt;strong&gt;webhooks&lt;/strong&gt; of course.&lt;/p&gt;

&lt;p&gt;In essence, whenever a change is made on Salesforce we call a webhook and have that webhook sync with a cache that we use for the support cases 🤯.&lt;/p&gt;

&lt;p&gt;We can then expand how long the cache should securely hold case data, and add a cache warm-up for when on the main page.&lt;/p&gt;

&lt;p&gt;The last issue was dealing with the complexity of working with Salesforce, we just powered through and though frustrating, I think the results are pretty fantastic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbp3xzbfu3jptrkvxrms.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnbp3xzbfu3jptrkvxrms.webp" alt="Preview image of Support Center and a demo support case" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Working on this project was awesome, and I'm grateful for being able to work with such a capable team of designers &amp;amp; developers on this project.&lt;/p&gt;

</description>
      <category>vercel</category>
      <category>devex</category>
      <category>support</category>
      <category>center</category>
    </item>
    <item>
      <title>How to upload files with Astro</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Sun, 18 Sep 2022 05:23:16 +0000</pubDate>
      <link>https://forem.com/okikio/using-formdata-with-astro-5545</link>
      <guid>https://forem.com/okikio/using-formdata-with-astro-5545</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Someone recently asked me how to use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; with &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, to which I responded I'll create a small document for this (I'll create a pr to add this to the &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro docs&lt;/a&gt; a little later).&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;I won't go into detail on what &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; is, but here is a short summary&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;FormData&lt;/code&gt; [is an] interface [which] provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the &lt;code&gt;fetch()&lt;/code&gt; or &lt;code&gt;XMLHttpRequest.send()&lt;/code&gt; method. It uses the same format a form would use if the encoding type were set to &lt;code&gt;"multipart/form-data"&lt;/code&gt;. Source: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;MDN - FormData&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Basically instead of using JSON to send data to and from your server, you'd use &lt;code&gt;FormData&lt;/code&gt;, except unlike JSON it supports files natively.&lt;/p&gt;

&lt;p&gt;For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Create or Get a File &lt;/span&gt;
&lt;span class="cm"&gt;/** Creating a File */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Text content...Lorem Ipsium`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-file.txt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="cm"&gt;/** OR */&lt;/span&gt;
&lt;span class="cm"&gt;/** Getting a File */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#files&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;input id="files" type="file" multiple /&amp;gt; &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Create FormData&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Add File to FormData through the `file` field&lt;/span&gt;
&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// FormData keys are called fields&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;

&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;^ &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/files" rel="noopener noreferrer"&gt;&lt;code&gt;fileInput.files&lt;/code&gt;&lt;/a&gt; is a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList" rel="noopener noreferrer"&gt;FileList&lt;/a&gt;, which is similar but &lt;strong&gt;not&lt;/strong&gt; an array, to work around this you can convert the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList" rel="noopener noreferrer"&gt;FileList&lt;/a&gt; to an array of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt;'s using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from" rel="noopener noreferrer"&gt;Array.from&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For our use case, since we're only trying to upload one file, it'd be easier to select the first &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt; in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FileList" rel="noopener noreferrer"&gt;FileList&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Learn more on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/files" rel="noopener noreferrer"&gt;MDN - HTMLInputElement&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;MDN - File&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; you can also just directly use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader" rel="noopener noreferrer"&gt;FileReader&lt;/a&gt; instead of using an &lt;code&gt;&amp;lt;input /&amp;gt;&lt;/code&gt; element&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;There are 2 ways to support &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; in &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;; the easy and the hard way, I'll show you both.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : both the easy and hard way require &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; to be configured in &lt;a href="https://docs.astro.build/en/guides/server-side-rendering/" rel="noopener noreferrer"&gt;server (SSR)&lt;/a&gt; mode&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// https://astro.build/config&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Easy Way
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;easy way&lt;/strong&gt; requires you to create a new &lt;code&gt;.ts&lt;/code&gt; file that will act as your endpoint, for example, if you wanted a &lt;code&gt;/upload&lt;/code&gt; endpoint, you would create a &lt;code&gt;.ts&lt;/code&gt; file in &lt;code&gt;src/pages&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;'s official docs on &lt;a href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes" rel="noopener noreferrer"&gt;File Routes&lt;/a&gt; to learn more&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your basic file tree should look like this after creating your endpoint&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
 pages/
   upload.ts
   index.astro

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

&lt;/div&gt;


&lt;p&gt;Inside your &lt;code&gt;index.astro&lt;/code&gt; file follow the example I gave above in #getting-started, on getting &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; up and running.&lt;/p&gt;

&lt;p&gt;Once you've created an instance of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; and populated it with the files you'd like to upload, you then just setup a POST request to that endpoint.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;From the endpoint side you'd then need to export a post method to handle the POST request being sent,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here is where things get complex. I recommend going through &lt;a href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes" rel="noopener noreferrer"&gt;Astro's File Routes Docs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// File routes export a get() function, which gets called to generate the file.&lt;/span&gt;
&lt;span class="c1"&gt;// Return an object with `body` to save the file contents in your final build.&lt;/span&gt;
&lt;span class="c1"&gt;// If you export a post() function, you can catch post requests, and respond accordingly&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;APIContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;fileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;webkitRelativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webkitRelativePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Buffer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Int8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The basics of what's happening here are fairly simple, but the code all put together seems rather complex, so let's break it down.&lt;/p&gt;

&lt;p&gt;First, the exported post function handles POST requests as its name suggests, meaning if you send a get request and don't export a get function an error will occur.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;export async function post() { ... }&lt;/code&gt; what?! Yeah, I too recently learned that &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; supports this out of the box, which is awesome.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;W3Schools cover &lt;a href="https://www.w3schools.com/tags/ref_httpmethods.asp" rel="noopener noreferrer"&gt;POST and GET&lt;/a&gt; fairly well, take a look at their article if you're not familiar with POST and GET requests&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's first talk about the &lt;code&gt;request&lt;/code&gt; parameter. As it's name suggests &lt;code&gt;request&lt;/code&gt; is an instance of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Request" rel="noopener noreferrer"&gt;Request&lt;/a&gt; class which includes all the methods that &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Request" rel="noopener noreferrer"&gt;Request&lt;/a&gt; supports, including a method for transforming said request into &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; you can work with.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;APIContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;formData&lt;/a&gt; you can get all the instances of a specific field (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; keys are called fields), for example, get all &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt;'s in the &lt;code&gt;file&lt;/code&gt; field.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;APIContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// getAll('file') will return an array of File classes&lt;/span&gt;
      &lt;span class="na"&gt;fileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The problem with this solution is that it will return &lt;code&gt;{"fileNames":[{}]}&lt;/code&gt; due to &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description" rel="noopener noreferrer"&gt;JSON.stringify&lt;/a&gt; being unable to convert &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt; classes to a string&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bjyhjjab7z1c92ogtwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bjyhjjab7z1c92ogtwx.png" alt="Result of JSON.stringify not being able to handle File classes" width="396" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To deal with this formatting issue we need to format the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File" rel="noopener noreferrer"&gt;File&lt;/a&gt;'s array properly,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;APIContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// getAll('files') will return an array of File classes&lt;/span&gt;
      &lt;span class="na"&gt;fileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;webkitRelativePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webkitRelativePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The last part is converting &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer" rel="noopener noreferrer"&gt;ArrayBuffers&lt;/a&gt; into data that is easy to work with, for this case using arrays to represent buffers works rather well, so we just do some conversion,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;APIContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="c1"&gt;// getAll('file') will return an array of File classes&lt;/span&gt;
      &lt;span class="na"&gt;fileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
            &lt;span class="na"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Buffer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Int8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
              &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's the &lt;strong&gt;easy way&lt;/strong&gt;. Using &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro's&lt;/a&gt; baked in &lt;a href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes" rel="noopener noreferrer"&gt;file routes&lt;/a&gt; to act as an endpoint for your &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To actually run &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; with the &lt;code&gt;/upload&lt;/code&gt; endpoint all you need is &lt;code&gt;npm run dev&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can view a demo of the &lt;strong&gt;easy way&lt;/strong&gt; on &lt;a href="https://stackblitz.com/edit/github-a2gvve-izjjam?file=README.md,astro.config.mjs,src%2Findex.ts&amp;amp;on=stackblitz" rel="noopener noreferrer"&gt;Stackblitz&lt;/a&gt;, &lt;a href="https://codesandbox.io/p/github/okikio/astro-form-data-easy-edition/csb-w3tpu0/draft/awesome-mahavira" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt; and &lt;a href="https://github.com/okikio/astro-form-data-easy-edition" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://codesandbox.io/p/github/okikio/astro-form-data-easy-edition/csb-w3tpu0/draft/awesome-mahavira" rel="noopener noreferrer"&gt;
      codesandbox.io
    &lt;/a&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Hard Way
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;hard way&lt;/strong&gt; requires you to use the &lt;a href="https://github.com/expressjs/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; middleware together with &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;expressjs&lt;/a&gt;, in order to make the &lt;a href="https://docs.astro.build/en/guides/integrations-guide/node/" rel="noopener noreferrer"&gt;@astrojs/node&lt;/a&gt; integration support &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; requests.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;hard way&lt;/strong&gt; mostly builds on the #easy-way, except instead of a &lt;code&gt;src/pages/upload.ts&lt;/code&gt; file, you would instead use a &lt;code&gt;server.mjs&lt;/code&gt; file in the root directory to define your endpoints, so, your file structure would look more like this,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
 pages/
   index.astro
server.mjs

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

&lt;/div&gt;


&lt;p&gt;The core of the &lt;strong&gt;hard way&lt;/strong&gt; occurs inside &lt;code&gt;server.mjs&lt;/code&gt;. &lt;code&gt;server.mjs&lt;/code&gt; should look like this by the end of this blog post&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ssrHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist/server/entry.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist/client/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ssrHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// req.files is an object (String -&amp;gt; Array) where fieldname is the key, and the value is array of files&lt;/span&gt;
  &lt;span class="c1"&gt;//&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g.&lt;/span&gt;
  &lt;span class="c1"&gt;// req.files['avatar'][0] -&amp;gt; File&lt;/span&gt;
  &lt;span class="c1"&gt;// req.files['gallery'] -&amp;gt; Array&lt;/span&gt;
  &lt;span class="c1"&gt;//&lt;/span&gt;
  &lt;span class="c1"&gt;// req.body will contain the text fields, if there were any&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When you build an &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; project in &lt;a href="https://docs.astro.build/en/guides/server-side-rendering/" rel="noopener noreferrer"&gt;server (SSR)&lt;/a&gt; mode (e.g. &lt;code&gt;npm run build&lt;/code&gt;), &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; will automatically generate a &lt;code&gt;dist/server/entry.mjs&lt;/code&gt; file, it's this file that allows us to build our own custom nodejs server and then run &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; off this server.&lt;/p&gt;

&lt;p&gt;For this specific use case we are using &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; for the server, and to enable &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; support in &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; we need the &lt;a href="https://github.com/expressjs/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; middleware, so if you're familiar with &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; at all this should look familiar,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ssrHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./dist/server/entry.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dist/client/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ssrHandler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;ssrHandler&lt;/code&gt; enables &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; to run on the &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; server, for the most part it can be treated like any other &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; middleware and ignored.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you're not familiar with the code snippet above, please go through &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express' documentation&lt;/a&gt;, it'll make the rest of the explanation easier to understand&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The real interesting part is where &lt;a href="https://github.com/expressjs/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; and &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; meet.&lt;/p&gt;

&lt;p&gt;By using a POST request handler we are able to recieve POST requests made to the &lt;code&gt;/upload&lt;/code&gt; endpoint and respond back with the parsed &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; results, but unlike in the #easy-way, &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; is able to handle all the formatting allowing &lt;code&gt;File&lt;/code&gt; responses to be as expected.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;files&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// req.files is an object (String -&amp;gt; Array) where fieldname is the key, and the value is array of files&lt;/span&gt;
  &lt;span class="c1"&gt;//&lt;/span&gt;
  &lt;span class="c1"&gt;// e.g.&lt;/span&gt;
  &lt;span class="c1"&gt;// req.files['avatar'][0] -&amp;gt; File&lt;/span&gt;
  &lt;span class="c1"&gt;// req.files['gallery'] -&amp;gt; Array&lt;/span&gt;
  &lt;span class="c1"&gt;//&lt;/span&gt;
  &lt;span class="c1"&gt;// req.body will contain the text fields, if there were any&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fileNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Response to &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; POST request&lt;/p&gt;

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

&lt;p&gt;That's the &lt;strong&gt;hard way&lt;/strong&gt;. Using &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro's&lt;/a&gt; &lt;a href="https://docs.astro.build/en/guides/server-side-rendering/" rel="noopener noreferrer"&gt;SSR&lt;/a&gt; mode together with &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; and &lt;a href="https://github.com/expressjs/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; to create the &lt;code&gt;/upload&lt;/code&gt; endpoint which supports &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;formData&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To actually run &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; you need to do a bit more than you'd need for the #easy-way&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; and &lt;a href="https://github.com/expressjs/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; -&amp;gt;&lt;code&gt;npm install express multer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Build &lt;a href="https://docs.astro.build/en/guides/server-side-rendering/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; handler -&amp;gt;&lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;server.mjs&lt;/code&gt; -&amp;gt; &lt;code&gt;node server.mjs&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;strong&gt;hard way&lt;/strong&gt; may seem easier, but that is due to having done alot of the prep work in the #easy-way, it is actually more overall work than the easy way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can view a demo of the &lt;strong&gt;hard way&lt;/strong&gt; on &lt;a href="https://stackblitz.com/edit/github-a2gvve?file=server.mjs" rel="noopener noreferrer"&gt;Stackblitz&lt;/a&gt;, &lt;a href="https://codesandbox.io/p/github/okikio/astro-form-data/draft/nostalgic-hawking" rel="noopener noreferrer"&gt;CodeSandbox&lt;/a&gt; and &lt;a href="https://github.com/okikio/astro-form-data" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://codesandbox.io/p/github/okikio/astro-form-data/draft/nostalgic-hawking" rel="noopener noreferrer"&gt;
      codesandbox.io
    &lt;/a&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are 2 ways of using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; with &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;, either the &lt;strong&gt;easy way&lt;/strong&gt; or the &lt;strong&gt;hard way&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;easy way&lt;/strong&gt; is to use &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro's&lt;/a&gt; baked in &lt;a href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes" rel="noopener noreferrer"&gt;File Routes&lt;/a&gt; to act as an endpoint for your &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt; POST requests.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;hard way&lt;/strong&gt; is to use &lt;a href="https://docs.astro.build" rel="noopener noreferrer"&gt;Astro's&lt;/a&gt; &lt;a href="https://docs.astro.build/en/guides/server-side-rendering/" rel="noopener noreferrer"&gt;SSR&lt;/a&gt; mode together with &lt;a href="https://expressjs.com" rel="noopener noreferrer"&gt;express&lt;/a&gt; and &lt;a href="https://github.com/expressjs/multer" rel="noopener noreferrer"&gt;multer&lt;/a&gt; to create a &lt;code&gt;/upload&lt;/code&gt; endpoint which supports &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="noopener noreferrer"&gt;FormData&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is no right way, but I will recommend the &lt;strong&gt;easy way&lt;/strong&gt; as it is easier and less confusing to work with overall.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://unsplash.com/photos/R8p564Om44w" rel="noopener noreferrer"&gt;Photo&lt;/a&gt; by &lt;a href="https://unsplash.com/@hitthetrailjack" rel="noopener noreferrer"&gt;Caleb Jack (@hitthetrailjack)&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/R8p564Om44w" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://blog.okikio.dev/using-formdata-with-astro" rel="noopener noreferrer"&gt;blog.okikio.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>astro</category>
      <category>backend</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>bundlejs: An online esbuild based bundler &amp; npm package size checker</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Wed, 18 May 2022 22:18:48 +0000</pubDate>
      <link>https://forem.com/okikio/documenting-an-online-esbuild-based-bundler-bundlejs-4m52</link>
      <guid>https://forem.com/okikio/documenting-an-online-esbuild-based-bundler-bundlejs-4m52</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
Introduction

&lt;ul&gt;
&lt;li&gt;Quick feature run down&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Bundling, Treeshaking, and Minification&lt;/li&gt;

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

&lt;li&gt;Input and Output Tabs&lt;/li&gt;

&lt;li&gt;

Configuration

&lt;ul&gt;
&lt;li&gt;CDN Hosts&lt;/li&gt;
&lt;li&gt;Compression Algorithms&lt;/li&gt;
&lt;li&gt;The Brotli Problem&lt;/li&gt;
&lt;li&gt;Default to Gzip&lt;/li&gt;
&lt;li&gt;LZ4, Gotta Go Fast&lt;/li&gt;
&lt;li&gt;Compression Quality&lt;/li&gt;
&lt;li&gt;Aliases and Externals&lt;/li&gt;
&lt;li&gt;Esbuild Config Options&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Editor Buttons + Extra Features...&lt;/li&gt;

&lt;li&gt;Drag Handles...Interactive Fun&lt;/li&gt;

&lt;li&gt;JSX Support&lt;/li&gt;

&lt;li&gt;Sharing Bundle Sessions&lt;/li&gt;

&lt;li&gt;Bundle Analysis&lt;/li&gt;

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

&lt;li&gt;Discussions and Support&lt;/li&gt;

&lt;li&gt;Security and Performance&lt;/li&gt;

&lt;li&gt;Tips and Tricks&lt;/li&gt;

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

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

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs&lt;/a&gt;&lt;/strong&gt; (pronounced &lt;strong&gt;bundle js&lt;/strong&gt;) is a quick and easy way to treeshake, bundle, minify, and compress (in either &lt;a href="https://en.wikipedia.org/wiki/Gzip" rel="noopener noreferrer"&gt;gzip&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Brotli" rel="noopener noreferrer"&gt;brotli&lt;/a&gt;) your &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;typescript&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer"&gt;javascript&lt;/a&gt;, &lt;a href="https://reactjs.org/docs/introducing-jsx.html" rel="noopener noreferrer"&gt;jsx&lt;/a&gt; and &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt; projects, while receiving the total bundles' file size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bundlejs&lt;/strong&gt; aims to generate more accurate bundle size estimates by following the same approach that bundlers use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Doing all bundling locally&lt;/li&gt;
&lt;li&gt;  Outputing the treeshaken bundled code&lt;/li&gt;
&lt;li&gt;  Getting the resulting bundle size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefits of using &lt;strong&gt;bundlejs&lt;/strong&gt; are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; It's easier to debug errors&lt;/li&gt;
&lt;li&gt; You can verify the resulting bundled code&lt;/li&gt;
&lt;li&gt; The ability to configure your bundles&lt;/li&gt;
&lt;li&gt; The ability to treeshake bundles&lt;/li&gt;
&lt;li&gt; The ability view a visual analysis of bundles&lt;/li&gt;
&lt;li&gt; You can bundle offline (so long as the module has been used before)&lt;/li&gt;
&lt;li&gt; Supports different types of modules from varying Content Delivery Networks (CDNs), e.g. CDNs ranging from deno modules, to npm modules, to random github scripts, etc...&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This blog post is meant to highlight some of the most important changes as well as to give some insight into how &lt;strong&gt;bundlejs&lt;/strong&gt; works in the background, and to act as the docs for bundlejs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📒&lt;strong&gt;Note&lt;/strong&gt;: There will be a follow up article to this one, going into the technical nitty gritty on how &lt;strong&gt;bundlejs&lt;/strong&gt; works and how you can use what I've learned from this project to either create your own online bundler or an es build-wasm backed js repl.&lt;/p&gt;

&lt;p&gt;😅 &lt;a href="https://www.dictionary.com/browse/tldr" rel="noopener noreferrer"&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/a&gt;: this blog post is rather long, so take a look at the &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; website first, then skim through this blog post making sure to check out the images and the code examples, those can help cut down on confusion and reduce the required reading time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Quick feature run down
&lt;/h3&gt;

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

&lt;blockquote&gt;
&lt;p&gt;This video runs through all the major features of &lt;strong&gt;bundlejs&lt;/strong&gt; (there is audio but I don't have a good mic 😅)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Bundling, Treeshaking, and Minification
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Bundling&lt;/strong&gt; is the process of efficiently &lt;a href="https://www.dictionary.com/browse/concatenate" rel="noopener noreferrer"&gt;concatenating&lt;/a&gt; modules together into one file which we call a &lt;a href="https://www.dictionary.com/browse/bundle" rel="noopener noreferrer"&gt;bundle&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Treeshaking&lt;/strong&gt; is the process of a bundler traversing the modules to be bundled and removing unused code.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Minification&lt;/strong&gt; is the process of shrinking the amount of code necessary to have a functional program, e.g. removing blank space or reducing variable names, etc...&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;bundlejs&lt;/strong&gt; uses &lt;a href="https://esbuild.github.io" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; and it's incredible ability to bundle, transform, transpile, minify, treeshake and traverse files. Specifically, &lt;strong&gt;bundlejs&lt;/strong&gt; uses &lt;a href="https://esbuild.github.io" rel="noopener noreferrer"&gt;esbuild-wasm&lt;/a&gt; which is able to access a subset of those features, with the key limitations being,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; npm only runs on node, so no package.json or &lt;code&gt;npm install&lt;/code&gt; (a-la, a joke about using &lt;a href="https://stackblitz.com/" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt; &lt;a href="https://blog.stackblitz.com/posts/introducing-webcontainers/" rel="noopener noreferrer"&gt;&lt;code&gt;WebContainers&lt;/code&gt;&lt;/a&gt; to run node on the browser)&lt;/li&gt;
&lt;li&gt; browsers don't work the way nodejs does. They don't have a easy way to access file system, so storing and accessing files isn't practical. The way esbuild would normally work when installed on node just causes issues on the web&lt;/li&gt;
&lt;li&gt; due to the limitation of esbuild-wasm when running on a browser (no npm, and node nodejs), the only option is for modules to come from the web but esbuild doesn't natively support importing &lt;code&gt;http(s)://...&lt;/code&gt; modules, so a different solution is required&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To solve each of these problems esbuild's plugin system comes in clutch. I created a total of 4 plugins to solve these limitations, they are,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;a href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/http.ts" rel="noopener noreferrer"&gt;&lt;code&gt;HTTP&lt;/code&gt; plugin&lt;/a&gt; - Fetches and caches modules&lt;/li&gt;
&lt;li&gt; &lt;a href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/cdn.ts" rel="noopener noreferrer"&gt;&lt;code&gt;CDN&lt;/code&gt; plugin&lt;/a&gt; - Redirects npm package imports (sometimes referred to as bare imports) to &lt;a href="https://en.wikipedia.org/wiki/Content_delivery_network" rel="noopener noreferrer"&gt;Content Delivery Network (CDN)&lt;/a&gt; urls for fetching&lt;/li&gt;
&lt;li&gt; &lt;a href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/external.ts" rel="noopener noreferrer"&gt;&lt;code&gt;EXTERNALS&lt;/code&gt; plugin&lt;/a&gt; - Marks certain imports/exports as modules to exclude from bundling&lt;/li&gt;
&lt;li&gt; &lt;a href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/alias.ts" rel="noopener noreferrer"&gt;&lt;code&gt;ALIAS&lt;/code&gt; plugin&lt;/a&gt; - Aliases certain imports/exports to modules of a different name&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Content_delivery_network" rel="noopener noreferrer"&gt;Content Delivery Networks (CDNs)&lt;/a&gt; are a great way to distribute code all over the world at fast speeds. In the context of &lt;strong&gt;bundlejs&lt;/strong&gt;, CDNs represent online repositories of code that &lt;strong&gt;bundlejs&lt;/strong&gt; can fetch from.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://unpkg.com" rel="noopener noreferrer"&gt;unpkg.com&lt;/a&gt; is a fast global content delivery network for everything on npm. It's used to quickly and easily load any file from any package on npm using a URL like: &lt;code&gt;https://unpkg.com/package-name@version/file.js&lt;/code&gt;, a similar thing would apply for &lt;a href="https://cdn.skypack.dev" rel="noopener noreferrer"&gt;skypack.dev&lt;/a&gt;, &lt;a href="https://cdn.esm.sh" rel="noopener noreferrer"&gt;esm.sh&lt;/a&gt;, etc...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a later blog post I will delve deeper into the technical details of how these plugins work, but for now just keep in mind that these plugins assist &lt;a href="https://esbuild.github.io" rel="noopener noreferrer"&gt;esbuild-wasm&lt;/a&gt; to create javascript bundles.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;View the impact of treeshaking on bundle size.&lt;/strong&gt;
  &lt;blockquote&gt;
&lt;p&gt;ℹ️ &lt;strong&gt;Info&lt;/strong&gt;: This is the impact treeshaking and minifying a bundle has on bundle size,&lt;/p&gt;

&lt;p&gt;&lt;em&gt;treeshaken&lt;/em&gt; &lt;/p&gt;

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

&lt;p&gt;vs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;non&lt;/em&gt;&lt;/strong&gt;-&lt;em&gt;treeshaken&lt;/em&gt; &lt;/p&gt;

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



&lt;/p&gt;

&lt;h2&gt;
  
  
  Console
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98kh4qxmprbhyhgz79m4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F98kh4qxmprbhyhgz79m4.png" alt="Image of the bundlejs virtual console, just after esbuild-wasm has been initialized" width="374" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In previous versions of &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; I encouraged devs to use the devtools console for viewing console logs, and for a while I thought it was an &lt;em&gt;ok&lt;/em&gt; experience, but I started realizing that it was inconvenient and not very mobile friendly. Initially, I thought creating a virtual console would be a large undertaking, so I delayed adding a custom console for quite some time. Well in the March of this year inspired by &lt;a href="https://github.com/hyrious" rel="noopener noreferrer"&gt;@hyrious&lt;/a&gt;'s &lt;a href="https://hyrious.me/esbuild-repl/?mode=build" rel="noopener noreferrer"&gt;esbuild-repl&lt;/a&gt; I finally did it 👏.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Console Results&lt;/strong&gt;
  &lt;p&gt;The console functions by listing out details of the build pertinent to users e.g. fetched packages, errors, etc... This includes bundle result info. e.g.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvfqrw3b0fugevh5zi8g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvfqrw3b0fugevh5zi8g.png" alt="Image of bundle results in the console. It shows the bundle time and the compressed/un-compressed bundle size" width="345" height="133"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Fetching Packages&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf3np6ggvth2gu5uu4qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf3np6ggvth2gu5uu4qg.png" alt="Image of the fetch progress of bundlejs in the virtual console" width="339" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default the console will display the progress for fetching packages, it's often the best way to diagnose errors and issues, as well as to find points of improvement.&lt;/p&gt;

&lt;p&gt;For some packages (ahem &lt;code&gt;@babel/core&lt;/code&gt;) there are too many sub-packages. Having the virtual console handle that many logs will eat up too much memory and/or slow down less powerful devices, so I limit the number of logs to 250 and when that limit is passed &lt;strong&gt;bundlejs&lt;/strong&gt; will show this friendly message,&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;📒 &lt;strong&gt;Note&lt;/strong&gt;: you can still access the full console log from the devtools console, even if the virtual console does any kind of truncating.&lt;/p&gt;

&lt;p&gt;You can change the maximum amount of logs allowed in the config via,&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logLimit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Console Errors &amp;amp; Warnings&lt;/strong&gt;
  &lt;p&gt;Errors look like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwzqwowo93uwk70gntml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwzqwowo93uwk70gntml.png" alt="Image of errors in the bundlejs virtual console" width="662" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Warnings look like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytg42hrgsw57gl4hkjsn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytg42hrgsw57gl4hkjsn.png" alt="Image of warnings in the bundlejs virtual console" width="759" height="303"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Console Buttons&lt;/strong&gt;
  &lt;p&gt;The consoles were also given buttons to make them easier to navigate, they are these right here&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcmao4imfh4tgb03tdw5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcmao4imfh4tgb03tdw5e.png" alt="Image of the console scroll to top button" width="56" height="43"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;scroll to top&lt;/strong&gt; button. As new console logs are introduced, the console automatically sticks to the bottom, some users may find this behavior annoying so this button offers a quick and easy opt out, by taking the user straight to the top of the console.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flva3tgfts64qyxnv3b5s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flva3tgfts64qyxnv3b5s.png" alt="Image of the console scroll to bottom button" width="53" height="42"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;scroll to bottom&lt;/strong&gt; button. Basically a get to the bottom as quick as possible button, it's meant for quickly checking out the final bundle result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfx0vya1hefpece25bnr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfx0vya1hefpece25bnr.png" alt="Image of the console error &amp;amp; warnings expand/collapse button" width="52" height="44"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;expand/collapse button&lt;/strong&gt;, it expands/collapses all errors and warnings quickly, making it easier to navigate through a large set of errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapvv4o5n5lwpmc1277w3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapvv4o5n5lwpmc1277w3.png" alt="Image of the clear console button" width="53" height="43"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;clear console button&lt;/strong&gt;, it clears the console of contents leaving the console looking like this, &lt;/p&gt;

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



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Console Extras&lt;/strong&gt;
  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Sticky Console:&lt;/strong&gt;&lt;/em&gt; Sticks console scroll position to the bottom, for new logs. If you scroll &lt;code&gt;~50px&lt;/code&gt; away from the bottom this behavior no longer applies, if you scroll back to the bottom the behavior will apply again.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Pet-peeve alignment:&lt;/strong&gt;&lt;/em&gt; I get so annoyed when things that can align don't align, so I built into the console to align by default with the &lt;strong&gt;bundlejs&lt;/strong&gt; result section on large enough screen, e.g. laptops, tablets, desktops, etc... &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z8crtztgbigro64l1l7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8z8crtztgbigro64l1l7.png" alt="Image of the alignment between the console and the bundlejs results section, and it is glorious" width="112" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Clickable console links:&lt;/strong&gt;&lt;/em&gt; Exactly as it sounds, clickable console links highlight urls that start with &lt;code&gt;http(s)://...&lt;/code&gt;, they function just like how vscode and the devtools does, and act as an easy way to access console urls without having to copy and paste the url manually. &lt;/p&gt;

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

&lt;p&gt;It does have some limitations, namely it sometimes has difficulty recognizing which characters are links and which are not, e.g.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I couldn't find an example while making this blog post 🤣, when/if (hoping it's an if rather than a when) I find one I'll update this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Log bundle results:&lt;/strong&gt;&lt;/em&gt; The bundle results, e.g. the time it takes to create the bundle, the initial bundle size, the compressed bundle size and more, are logged to the console.&lt;/p&gt;

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



&lt;/p&gt;

&lt;h2&gt;
  
  
  Input and Output Tabs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p3338kg6ku1pnk0ctag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p3338kg6ku1pnk0ctag.png" alt="Image of the input and output tabs, with the config button off to the side" width="282" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the addition of the console I wanted to ensure that the editors weren't unwieldy, so I created a tab bar for the input, output and config editor. The tab bar allows for quick access to all editors while ensuring that the &lt;code&gt;console&lt;/code&gt; is always available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;v0.2&lt;/code&gt; I added support for custom configurations (configs), it supports most of &lt;a href="https://esbuild.github.io/api/#build-api" rel="noopener noreferrer"&gt;esbuild's build options&lt;/a&gt;, as well as some added options to change the default CDN and the compression algorithm.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The default config is&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://unpkg.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gzip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bundle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;treeShaking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;When you click the share button, it will also share the custom config you've setup, e.g.&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://unpkg.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lz4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es2018&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The config above would result in this share URL &lt;a href="https://bundlejs.com/?q=@okikio/animate&amp;amp;config={" rel="noopener noreferrer"&gt;bundlejs.com/?q=@okikio/animate&amp;amp;config={"compression":"lz4","esbuild":{"target":["es2018"]}}&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Notice how &lt;code&gt;cdn&lt;/code&gt; is missing from the share URL, that's because &lt;strong&gt;bundlejs&lt;/strong&gt; smartly decides on which config to send as a part of the share URL based on how different the new config is from the default config.&lt;/p&gt;

&lt;p&gt;📒 &lt;strong&gt;Note&lt;/strong&gt;: There are 3 available compression algorithms, &lt;code&gt;brotli&lt;/code&gt;, &lt;code&gt;gzip&lt;/code&gt;, and &lt;code&gt;lz4&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  CDN Hosts
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Content_delivery_network" rel="noopener noreferrer"&gt;Content Delivery Networks (CDNs)&lt;/a&gt; are a great way to distribute code all over the world at fast speeds. In the context of &lt;strong&gt;bundlejs&lt;/strong&gt;, CDNs represent online repositories of code that &lt;strong&gt;bundlejs&lt;/strong&gt; can fetch from.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://unpkg.com" rel="noopener noreferrer"&gt;unpkg.com&lt;/a&gt; is a fast global content delivery network for everything on npm. It's used to quickly and easily load any file from any package on npm using a URL like: &lt;code&gt;https://unpkg.com/package-name@version/file.js&lt;/code&gt;, a similar thing would apply for &lt;a href="https://cdn.skypack.dev" rel="noopener noreferrer"&gt;skypack.dev&lt;/a&gt;, &lt;a href="https://cdn.esm.sh" rel="noopener noreferrer"&gt;esm.sh&lt;/a&gt;, etc...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default &lt;strong&gt;bundlejs&lt;/strong&gt; lets you enter code like this,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But behind the scenes &lt;strong&gt;bundlejs&lt;/strong&gt; auto fetches that specific package from a CDN namely, &lt;a href="https://unpkg.com" rel="noopener noreferrer"&gt;unpkg&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In older versions of &lt;strong&gt;bundlejs&lt;/strong&gt; the default CDN used to be &lt;a href="https://cdn.skypack.dev" rel="noopener noreferrer"&gt;skypack&lt;/a&gt; but because skypack doesn't have easy access to the &lt;code&gt;package.json&lt;/code&gt; of node packages, I switched to using unpkg as the default CDN.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With later updates &lt;strong&gt;bundlejs&lt;/strong&gt; recieved the ability to update the default &lt;code&gt;cdn&lt;/code&gt; on a global or local scale.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Technical details and more info...&lt;/strong&gt;
  &lt;p&gt;You can choose CDNs by,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;(&lt;strong&gt;Global CDN&lt;/strong&gt;) Setting the CDN config to a different CDN host, e.g.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.esm.sh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// OR&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skypack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(&lt;strong&gt;Local CDN&lt;/strong&gt;) Using the CDN host as an inline url scheme, e.g.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skypack:@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="c1"&gt;//                       ^^^^^^^  https://cdn.skypack.dev/@okikio/animate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;There are a total of 8 supported inline CDN host url schemes:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   `skypack:react` -&amp;gt; [https://cdn.skypack.dev/react](https://cdn.skypack.dev/react)

*   `unpkg:react` -&amp;gt; [https://unpkg.com/react](https://unpkg.com/react)

*   `esm.sh:react` or `esm:react` -&amp;gt; [https://cdn.esm.sh/react](https://cdn.esm.sh/react)

*   `deno:preact` -&amp;gt; [https://deno.land/x/preact](https://deno.land/x/preact)

*   `esm.run:react` -&amp;gt; [https://esm.run/react](https://esm.run/react)

*   `github:facebook/react/main/packages/react/index.js` -&amp;gt; [https://raw.githubusercontent.com/facebook/react/main/packages/react/index.js](https://raw.githubusercontent.com/facebook/react/main/packages/react/index.js)

*   `jsdelivr:react` -&amp;gt; [https://cdn.jsdelivr.net/npm/react](https://cdn.jsdelivr.net/npm/react)

*   `jsdelivr.gh:facebook/react/packages/react-dom/index.js` -&amp;gt; [https://cdn.jsdelivr.net/gh/facebook/react/packages/react-dom/index.js](https://cdn.jsdelivr.net/gh/facebook/react/packages/react-dom/index.js)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;After determining the CDN to use, the next step is to determine if the CDN host supports npm style modules, examples of which are &lt;a href="https://unpkg.com" rel="noopener noreferrer"&gt;unpkg&lt;/a&gt;, &lt;a href="https://skypack.dev" rel="noopener noreferrer"&gt;skypack&lt;/a&gt;, &lt;a href="https://esm.sh" rel="noopener noreferrer"&gt;esm.sh&lt;/a&gt;, etc...&lt;/p&gt;

&lt;p&gt;The factors involved in determining that a CDN host supports npm style modules are that the CDN hosts supports:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The CDN supports package versioning through the &lt;code&gt;@version&lt;/code&gt; URL tag (e.g. &lt;code&gt;react@18&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The CDN can load a node packages, &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;📒 &lt;strong&gt;Note&lt;/strong&gt;: Without the &lt;code&gt;package.json&lt;/code&gt; you can't load &lt;a href="https://nodejs.org/api/packages.html#subpath-imports" rel="noopener noreferrer"&gt;subpath imports&lt;/a&gt;, plus it becomes more difficult to determine the correct exported modules to bundle with.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Warning&lt;/strong&gt;: If the chosen CDN doesn't support the &lt;code&gt;package.json&lt;/code&gt; file and it isn't a npm style CDN host, then &lt;strong&gt;bundlejs&lt;/strong&gt; will switch to trying to guess package versions, this may lead to inaccurate bundles with the wrong versions of packages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a later blog post I will delve deeper into the technical details of how the esbuild CDN plugin works to determine which CDN host to use, but for now you just keep in mind that the CDN plugins assist &lt;a href="https://esbuild.github.io" rel="noopener noreferrer"&gt;esbuild-wasm&lt;/a&gt; in resolving CDN host urls.&lt;/p&gt;



&lt;/p&gt;
&lt;h3&gt;
  
  
  Compression Algorithms
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;bundlejs&lt;/strong&gt; offers the options of bundling using:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;brotli&lt;/code&gt; - results in the smallest bundle size but it's the slowest&lt;/li&gt;
&lt;li&gt; &lt;code&gt;gzip&lt;/code&gt; - results in the 2nd smallest bundle size but it's faster than &lt;code&gt;brotli&lt;/code&gt; (&lt;strong&gt;default&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;lz4&lt;/code&gt; - results in the largest bundle size but it's the fastest bundle algorithm&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;📒 &lt;strong&gt;Note&lt;/strong&gt;: Each compression algorithm has it's own story.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;
  
  
  The Brotli Problem
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Brotli" rel="noopener noreferrer"&gt;brotli&lt;/a&gt; is a compression algorithm that compresses data really well, however, it's very slow compared to other alternatives. Adding brotli was quite an undertaking with lots of ups and downs, but thanks to &lt;a href="https://twitter.com/lewisl9029/status/1498928788477857794?s=20&amp;amp;t=iUL2EzC810kgGM6tn8nJeg" rel="noopener noreferrer"&gt;Lewis Liu&lt;/a&gt; on Twitter I was able to use &lt;a href="https://deno.land/x/brotli" rel="noopener noreferrer"&gt;deno-brotli&lt;/a&gt; to include a WASM version of brotli in &lt;strong&gt;bundlejs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Learn the story behind brotli support...&lt;/strong&gt;
  &lt;p&gt;No &lt;a href="https://www.urbandictionary.com/define.php?term=Shade" rel="noopener noreferrer"&gt;shade&lt;/a&gt; to the original creators of &lt;a href="https://github.com/httptoolkit/brotli-wasm" rel="noopener noreferrer"&gt;brotli-wasm&lt;/a&gt; and &lt;a href="https://github.com/dfrankland/wasm-brotli" rel="noopener noreferrer"&gt;wasm-brotli&lt;/a&gt; (different packages, similar name) but the way both packages handle WASM forces devs to use webpack (which wasn't happening, I value my time tooooo much for that), after multiple attempts and 6 months of work later I finally found &lt;a href="https://deno.land/x/brotli" rel="noopener noreferrer"&gt;deno-brotli&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An example of the way WASM is used in both packages (brotli-wasm and wasm-brotli) is as follows,&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WASM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./bg.wasm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;In all honesty, it's not fair for me to blame the creators of &lt;a href="https://github.com/httptoolkit/brotli-wasm" rel="noopener noreferrer"&gt;brotli-wasm&lt;/a&gt; and &lt;a href="https://github.com/dfrankland/wasm-brotli" rel="noopener noreferrer"&gt;wasm-brotli&lt;/a&gt;, it's not their fault. The fault lies in the js ecosystem not yet finding an interoperable solution for working with WASM. That's one of the key reasons I'm very thankful for &lt;a href="https://twitter.com/lewisl9029/status/1498928788477857794?s=20&amp;amp;t=iUL2EzC810kgGM6tn8nJeg" rel="noopener noreferrer"&gt;Lewis Liu&lt;/a&gt; pointing out &lt;a href="https://deno.land/x/brotli" rel="noopener noreferrer"&gt;deno-brotli&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;/p&gt;

&lt;p&gt;&lt;code&gt;deno-brotli&lt;/code&gt; does 2 things right, they are,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It compresses the huge WASM file required for &lt;code&gt;deno-brotli&lt;/code&gt; into an &lt;code&gt;lz4&lt;/code&gt; compressed string, which can then be decompressed by &lt;code&gt;lz4&lt;/code&gt; allowing for easy storage of the WASM as a js file (the result is great build tools support as the WASM is just a string inside a JS file, plus it solves the ecosystem problem really well).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For &lt;code&gt;lz4&lt;/code&gt; support &lt;strong&gt;bundlejs&lt;/strong&gt; is using &lt;a href="https://deno.land/x/lz4" rel="noopener noreferrer"&gt;deno-lz4&lt;/a&gt;, which also runs via WASM, but the way &lt;a href="https://deno.land/x/lz4" rel="noopener noreferrer"&gt;deno-lz4&lt;/a&gt; compress itself is slightly different than &lt;code&gt;deno-brotli&lt;/code&gt;. I'd highly encourage you to take a look at the source code for &lt;a href="https://deno.land/x/lz4" rel="noopener noreferrer"&gt;deno-lz4&lt;/a&gt; it's really informative.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;By having the WASM as a js file you can actually preload the WASM as a js module 🤯&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikw0dsrnxnm0xofj8k2x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fikw0dsrnxnm0xofj8k2x.gif" alt="The Universe, Tim And Eric, Mind Blown GIF" width="498" height="317"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can read through this tweet thread to learn more,&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1498898909879422977-365" src="https://platform.twitter.com/embed/Tweet.html?id=1498898909879422977"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1498898909879422977-365');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1498898909879422977&amp;amp;theme=dark"
  }



&lt;/p&gt;
&lt;h4&gt;
  
  
  Default to Gzip
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;bundlejs&lt;/strong&gt; has used &lt;a href="https://en.wikipedia.org/wiki/Gzip" rel="noopener noreferrer"&gt;gzip&lt;/a&gt; as the default for quite sometime, &lt;strong&gt;bundlejs&lt;/strong&gt; used to use &lt;a href="https://github.com/nodeca/pako" rel="noopener noreferrer"&gt;pako&lt;/a&gt;, but thanks to a discussion with &lt;a href="https://twitter.com/matthewcp/status/1503704061853450242?s=20&amp;amp;t=CDGJmTts_m2A9H7cyZfmew" rel="noopener noreferrer"&gt;@matthewcp&lt;/a&gt; who rightfully pointed out the &lt;code&gt;(De)compression Stream API&lt;/code&gt;, I started looking into alternative to &lt;code&gt;pako&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1503704061853450242-596" src="https://platform.twitter.com/embed/Tweet.html?id=1503704061853450242"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1503704061853450242-596');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1503704061853450242&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Learn the story behind gzip support...&lt;/strong&gt;
  &lt;p&gt;Thanks to the conversation with &lt;a href="https://twitter.com/matthewcp/status/1503704061853450242?s=20&amp;amp;t=CDGJmTts_m2A9H7cyZfmew" rel="noopener noreferrer"&gt;@matthewcp&lt;/a&gt;, I actually did some further research into &lt;code&gt;pako&lt;/code&gt; alternatives, I have 3 possible alternatives, they are &lt;a href="https://deno.land/x/denoflate" rel="noopener noreferrer"&gt;denoflate&lt;/a&gt; (which uses WASM), &lt;a href="https://deno.land/x/compress" rel="noopener noreferrer"&gt;deno-compress&lt;/a&gt; (which uses js), and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API" rel="noopener noreferrer"&gt;CompressionStream&lt;/a&gt; (which is built into browsers), yay fun 🎉😅.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;I eventually chose to replace &lt;a href="https://github.com/nodeca/pako" rel="noopener noreferrer"&gt;pako&lt;/a&gt; with &lt;a href="https://deno.land/x/denoflate" rel="noopener noreferrer"&gt;denoflate&lt;/a&gt; as the default compression algorithm for gzip, it's a bit faster and smaller than &lt;a href="https://github.com/nodeca/pako" rel="noopener noreferrer"&gt;pako&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  LZ4, Gotta Go Fast
&lt;/h4&gt;

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

&lt;p&gt;In order to use WASM in a portable way, &lt;a href="https://deno.land/x/brotli" rel="noopener noreferrer"&gt;deno-brotli&lt;/a&gt; would compress the WASM binary file into a base64 string using &lt;code&gt;lz4&lt;/code&gt; as the compression algorithm. I saw the oppertunity to add another compression algorithm, so I used the &lt;code&gt;lz4&lt;/code&gt; (&lt;a href="https://deno.land/x/lz4" rel="noopener noreferrer"&gt;deno-lz4&lt;/a&gt;) implementation used to compress the brotli WASM binary string (see #the-brotli-problem for more info.), it was by far the easiest compression algorithm to add to &lt;strong&gt;bundlejs&lt;/strong&gt; 🤣.&lt;/p&gt;
&lt;h3&gt;
  
  
  Compression Quality
&lt;/h3&gt;

&lt;p&gt;You can set the quality of the compression (from a scale of 1 to 11, with 1 being the least compressed and 11 being the most compressed), you can set the compression quality for any of the compression algorithms mentioned above by setting the compression config option to,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brotli&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;quality&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;You can check out a demo here, &lt;a href="https://bundlejs.com/?config={" rel="noopener noreferrer"&gt;bundlejs.com/?config={"compression":{"type":"brotli","quality":11}}&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Aliases and Externals
&lt;/h3&gt;

&lt;p&gt;Aliases are a way to redirect certain packages to other packages, e.g. redirecting the &lt;code&gt;fs&lt;/code&gt; to &lt;code&gt;memfs&lt;/code&gt;, because &lt;code&gt;fs&lt;/code&gt; isn't supported on the web, etc... This wasn't a direct feature request but I felt it would be a good addition.&lt;/p&gt;

&lt;p&gt;Externals are a direct feature request &lt;a href="https://github.com/okikio/bundle/issues/13" rel="noopener noreferrer"&gt;issue#13&lt;/a&gt;, it took a while but a good solution is finally a part of bundlejs, you use it the way you'd use the esbuild externals config option.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;More details...&lt;/strong&gt;
  &lt;p&gt;You use &lt;code&gt;aliases&lt;/code&gt; it like this,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alias&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@babel/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;You can try it out below, &lt;a href="https://bundlejs.com/?config={" rel="noopener noreferrer"&gt;bundlejs.com/?config={"alias":{"@okikio/animate":"@babel/core"}}&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You use &lt;code&gt;externals&lt;/code&gt; like this,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;external&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;You can try it out below, &lt;a href="https://bundlejs.com/?config={" rel="noopener noreferrer"&gt;bundlejs.com/?config={"esbuild":{"external":["@okikio/animate"]}}&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check out a complex example of using the external config &lt;a href="https://bundlejs.com/?q=@babel/core&amp;amp;config=22esbuild%22:22external%22:[%22debug%22,%22@babel/types%22,%22@babel/parser%22,%22@babel/generator%22,%22@babel/traverse%22,%22@babel/template%22,%22@babel/helper%22,%22semver%22,%22gensync%22,%22@babel/code-frame%22,%22json5%22,%22caniuse-lite%22,%22source-map%22,%22@ampproject/remapping%22,%22@babel/helper-compilation-targets%22,%22@babel/helper-validator-option%22,%22browserslist%22,%22@jridgewell/trace-mapping%22,%22convert-source-map%22,%22@babel/helpers%22,%22@babel/compat-data%22,%22@babel/helper-environment-visitor%22,%22@babel/helper-module-imports%22,%22@babel/helper-module-transforms%22,%22@babel/helper-validator-identifier%22,%22node-releases%22,%22@jridgewell/resolve-uri%22,%22@jridgewell/sourcemap-codec%22,%22electron-to-chromium%22]}}" rel="noopener noreferrer"&gt;bundlejs.com/?q=@babel/core&amp;amp;config={"esbuild":{"external":[...]}}&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No one else can understand my pain...I'm adding more feature to &lt;strong&gt;bundlejs&lt;/strong&gt; as I'm writing this blog post, so it's just getting longer and longer and longer, etc.... 😅&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndxupjfpf3x1ari5jv68.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndxupjfpf3x1ari5jv68.gif" alt="My Pain Is Greater Than Yours, Naruto GIF" width="498" height="280"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Esbuild Config Options
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://esbuild.github.io/api" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; config options are exactly how they sound, however, with esbuild running on the browser there are some limitations on what esbuild can do, due to the lack of native filesystem access some options don't work or are rendered obsolete.&lt;/p&gt;

&lt;p&gt;The supported esbuild build options are&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Simple options&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#bundle" rel="noopener noreferrer"&gt;Bundle&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#define" rel="noopener noreferrer"&gt;Define&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#external" rel="noopener noreferrer"&gt;External&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#format" rel="noopener noreferrer"&gt;Format&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#inject" rel="noopener noreferrer"&gt;Inject&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#loader" rel="noopener noreferrer"&gt;Loader&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#minify" rel="noopener noreferrer"&gt;Minify&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#platform" rel="noopener noreferrer"&gt;Platform&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#sourcemap" rel="noopener noreferrer"&gt;Sourcemap&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#splitting" rel="noopener noreferrer"&gt;Splitting&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#target" rel="noopener noreferrer"&gt;Target&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Advanced options&lt;/strong&gt;
  &lt;ul&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#analyze" rel="noopener noreferrer"&gt;Analyze&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#asset-names" rel="noopener noreferrer"&gt;Asset names&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#banner" rel="noopener noreferrer"&gt;Banner&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#charset" rel="noopener noreferrer"&gt;Charset&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#chunk-names" rel="noopener noreferrer"&gt;Chunk names&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#color" rel="noopener noreferrer"&gt;Color&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#drop" rel="noopener noreferrer"&gt;Drop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#entry-names" rel="noopener noreferrer"&gt;Entry names&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#footer" rel="noopener noreferrer"&gt;Footer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#global-name" rel="noopener noreferrer"&gt;Global name&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#ignore-annotations" rel="noopener noreferrer"&gt;Ignore annotations&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#incremental" rel="noopener noreferrer"&gt;Incremental&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#jsx" rel="noopener noreferrer"&gt;JSX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#jsx-factory" rel="noopener noreferrer"&gt;JSX factory&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#jsx-fragment" rel="noopener noreferrer"&gt;JSX fragment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#keep-names" rel="noopener noreferrer"&gt;Keep names&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#legal-comments" rel="noopener noreferrer"&gt;Legal comments&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#log-level" rel="noopener noreferrer"&gt;Log level&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#log-limit" rel="noopener noreferrer"&gt;Log limit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#mangle-props" rel="noopener noreferrer"&gt;Mangle props&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#metafile" rel="noopener noreferrer"&gt;Metafile&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#out-extension" rel="noopener noreferrer"&gt;Out extension&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#outbase" rel="noopener noreferrer"&gt;Outbase&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#public-path" rel="noopener noreferrer"&gt;Public path&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#pure" rel="noopener noreferrer"&gt;Pure&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#resolve-extensions" rel="noopener noreferrer"&gt;Resolve extensions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#source-root" rel="noopener noreferrer"&gt;Source root&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#sourcefile" rel="noopener noreferrer"&gt;Sourcefile&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#sources-content" rel="noopener noreferrer"&gt;Sources content&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#stdin" rel="noopener noreferrer"&gt;Stdin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#tree-shaking" rel="noopener noreferrer"&gt;Tree shaking&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://esbuild.github.io/api/#tsconfig-raw" rel="noopener noreferrer"&gt;Tsconfig raw&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;/p&gt;

&lt;p&gt;Quite a bit to work with I'd say.&lt;/p&gt;
&lt;h2&gt;
  
  
  Editor Buttons + Extra Features...
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88afjjnnolhtr1b3gpas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F88afjjnnolhtr1b3gpas.png" alt="Image of editor button panel with all the editor buttons listed" width="364" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The editor buttons add extra functionality to the editor, they enable easy access to common editor tasks.&lt;/p&gt;

&lt;p&gt;The current list of editor buttons are:&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Editor panel toggle&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncevzl9zidhy0gnwkv4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncevzl9zidhy0gnwkv4r.png" alt="Image of the editor panel toggle" width="63" height="60"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Toggles on or off the editor buttons, leaving more space for the code editor. It looks like this when the editor buttons are hidden,&lt;/p&gt;

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



&lt;/p&gt;

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

&lt;p&gt;Clears the editor of all its contents. &lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Format code button&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcwjf5fgmrexgiev6hr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvcwjf5fgmrexgiev6hr5.png" alt="Image of format editor button" width="61" height="49"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cleans up any messy code it finds. It uses &lt;a href="https://dprint.dev/" rel="noopener noreferrer"&gt;dprint&lt;/a&gt; to format the input and output editor code, but falls back to &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;monaco-editors&lt;/a&gt; baked in formatter for the config editor.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Reset code button&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6p3h8v6lu2u741umdd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed6p3h8v6lu2u741umdd.png" alt="Image of reset editor button" width="62" height="51"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Resets the editor to it's initial state.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;For the input editor, it resets it to this,&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Click Build for the bundled, minified and compressed package size&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For the output editor, it resets it to this,&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For the config editor, it resets it to this,&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://unpkg.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gzip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;analysis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bundle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;treeShaking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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



&lt;/p&gt;

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

&lt;p&gt;Copies the editors code, it's exactly as it sounds (what were you expecting? 🤣). When you copy code from the editor using the copy button, this delightful little message appears,&lt;/p&gt;

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



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Wrap code button&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xpv7ync7cux3w748c2b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3xpv7ync7cux3w748c2b.png" alt="Image of wrap around editor button toggle" width="60" height="52"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Toggles between &lt;code&gt;wrapped&lt;/code&gt; and &lt;code&gt;unwrapped&lt;/code&gt; code. Wrapping is all about making the editors code wrap around the constraints of the editors bounding box, removing the need to scroll horizontally to view all the code.&lt;/p&gt;

&lt;p&gt;This is how &lt;code&gt;wrapped&lt;/code&gt; code looks,&lt;/p&gt;

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

&lt;p&gt;This is how &lt;code&gt;unwrapped&lt;/code&gt; code looks,&lt;/p&gt;

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



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Bonus features&lt;/strong&gt;
  &lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bonus&lt;/strong&gt;: You can access monaco's built in command palette by pressing F1, e.g. &lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Extra Bonus&lt;/strong&gt;: You can use many of the code shortcuts vscode has by just right clicking while in focus on the &lt;a href="https://github.com/microsoft/monaco-editor" rel="noopener noreferrer"&gt;monaco-editor&lt;/a&gt;, e.g. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo3y2smsshmbpsyoqvhk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flo3y2smsshmbpsyoqvhk.png" alt="Image of code editor shortcuts on right click menu" width="369" height="378"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;



&lt;/p&gt;
&lt;h2&gt;
  
  
  Drag Handles...Interactive Fun
&lt;/h2&gt;

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

&lt;p&gt;The new drag handles enable a more interactive experience with the editor, they are a little like the drag handles in vscode, but mobile friendly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;bundlejs&lt;/strong&gt; being mobile friendly isn't a huge focus point for the project, but it's nice to have if you ever find yourself in the need for &lt;strong&gt;bundlejs&lt;/strong&gt; while on a mobile or touch enabled device.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  JSX Support
&lt;/h2&gt;

&lt;p&gt;JSX is now officially supported in &lt;strong&gt;bundlejs&lt;/strong&gt; 🎉.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Ignore the red error lines, for some reason the &lt;a href="https://github.com/microsoft/monaco-editor" rel="noopener noreferrer"&gt;monaco code editor&lt;/a&gt; doesn't want to work well with JSX 😅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To use JSX you need to set the &lt;code&gt;jsxFactory&lt;/code&gt; and the &lt;code&gt;jsxFragment&lt;/code&gt; config options according to the JSX based framework you are using.&lt;/p&gt;

&lt;p&gt;e.g. for &lt;a href="https://preactjs.com/" rel="noopener noreferrer"&gt;Preact&lt;/a&gt; the following config would be used:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsxFactory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jsxFragment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fragment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Try out the &lt;a href="https://bundlejs.com/?q=(import)preact,(import)preact/hooks&amp;amp;treeshake=%5B%7Bh,Fragment%7D%5D,%5B%7BuseState%7D%5D&amp;amp;share=KYDwDg9gTgLgBAE2AMwIYFcA29noHYDGMAlhHnAMIT4zBQAUA3nAQBbGYJTDkC+AlHEYAoAJAEyAZ3gBtCTQA0cScBhUaAXTgBeOOhUBlGKlr0ADPwDcYiXmlxUCBDrj1B2gHzLV6vDHr0xO5exHAA1HAAjFY2UvCS6ABGMFCoRC5uOl4qatR+AUFZcKEAtFExYtww6FDk9GKiADweDU0IxABuLJiokpLaAETyfnQDLaITTYnoMDBkcGQUmMQEANbajAnJqUS8HiWNAPTTs2Tjk41g3B6MwzC8R1fA5xONJ3Pki8trG44Ie2Eju8zq0ju0Oi9GuDur1+kM8rQoCUALbAPqoADmwDGt3YnG4eAeh3B5yO4xivCAA&amp;amp;config=%7Besbuild:%7BjsxFactory:h,jsxFragment:Fragment%7D%7D" rel="noopener noreferrer"&gt;preact demo&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Sharing Bundle Sessions
&lt;/h2&gt;

&lt;p&gt;To share bundle sessions* between multiple users (while avoiding the need for a server) we need a static and local way to store and share code between users. To solve this problem I decided to encode the bundle session* information right into the URL, this was because I wanted the entire project to run offline, and I didn't want the high maintenance cost of a server and database.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;*sessions are the specific state of &lt;strong&gt;bundlejs&lt;/strong&gt; at a specific time, they are not the entire bundle session history, just the input code and the bundle configuration at the time the share button is clicked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;
  &lt;strong&gt;Technical details...&lt;/strong&gt;
  &lt;p&gt;A high-level summary of how this works is that users make a change in the input code editor, that change then gets saved and encoded into the URL. The URL can then be used to create replays of the bundle session.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://tinyurl.com/3fptk973" rel="noopener noreferrer"&gt;sample session url&lt;/a&gt; is,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;)@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/emitter,&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;import&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;)@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate,&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;import&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;)@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate,@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate,@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate&amp;amp;treeshake=&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;T&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{ animate }&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{ animate as B }&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt; as TR&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{ type animate }&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{ animate as A }&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt; as PR&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;,&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;{ animate }&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;&amp;amp;text="export  as PR18 from &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sr"&gt;@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;export { animate as animate2 } from &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;share&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;MYewdgziA2CmB00QHMAUAiAwiG6CUQA&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skypack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brotli&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;treeShaking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;bundle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The resulting input code of this bundle session url is this,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Click Build for the Bundled, Minified &amp;amp; Compressed package size&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/emitter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt;  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;TR&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;PR&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;  &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;PR18&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;animate2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;with a config of,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skypack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brotli&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;target&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bundle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;treeShaking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;The URL breakdown is,&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;)@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/emitter,&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;import&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;)@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate,&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;import&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;)@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate,@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate,@okikio/&lt;/span&gt;&lt;span class="nx"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;,@&lt;/span&gt;&lt;span class="nd"&gt;okikio&lt;/span&gt;&lt;span class="sr"&gt;/animate&lt;/span&gt;&lt;span class="err"&gt;&amp;amp;
&lt;/span&gt;&lt;span class="nx"&gt;treeshake&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],[{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}],[{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;B&lt;/span&gt; &lt;span class="p"&gt;}],[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;TR&lt;/span&gt;&lt;span class="p"&gt;],[{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}],[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;],[{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;A&lt;/span&gt; &lt;span class="p"&gt;}],[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;PR&lt;/span&gt;&lt;span class="p"&gt;],[{&lt;/span&gt; &lt;span class="nx"&gt;animate&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;export * as PR18 from &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;export { animate as animate2 } from &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;@okikio/animate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="nx"&gt;share&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;MYewdgziA2CmB00QHMAUAiAwiG6CUQA&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skypack&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;brotli&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;format&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;minify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;treeShaking&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;
&lt;span class="nx"&gt;bundle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;q&lt;/code&gt; or &lt;code&gt;query&lt;/code&gt; represents the module, e.g. &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;vue&lt;/code&gt;, etc...&lt;/p&gt;

&lt;p&gt;You can add &lt;code&gt;(import)&lt;/code&gt; in-front of a specific module to make it an import instead of an export&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;treeshake&lt;/code&gt; represents the export/imports to treeshake.&lt;/p&gt;

&lt;p&gt;The treeshake syntax allows for specifying multiple exports per package, through this syntax&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[{ x,y,z }],[*],[* as X],[{ type xyz }]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
&lt;span class="c1"&gt;// to&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;xyz&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The square brackets represent seperate packages, and everything inside the squarebrackets, are the exported methods, types, etc...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;text&lt;/code&gt; represents the input code as a string (it's used for short input code)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;share&lt;/code&gt; represents compressed string version of the input code (it's used for large input code)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;config&lt;/code&gt; represents the bundle configuration to use when building the bundle&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;bundle&lt;/code&gt; tells &lt;strong&gt;bundlejs&lt;/strong&gt; to bundle the input code on start-up. This isn't on by default for security reasons. I want to discourage people from sending large complex bundles that crash browsers or that take a long time to load, especially before the input code is properly verified as non-malicious. So, if you want to bundle the code on startup, you have to manually add &lt;code&gt;&amp;amp;bundle&lt;/code&gt; to the end of the url yourself.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason why I decided on this syntax is because it allows for a lot of flexibility, and transparency concerning what is being bundled. I also wanted to make it easy to share bundle session between users.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bundle Analysis
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;bundlejs&lt;/strong&gt; can analyze and visually represent bundles as easy to navigate and easy to understand charts.&lt;/p&gt;

&lt;p&gt;Using a port of &lt;a href="https://github.com/btd/esbuild-visualizer" rel="noopener noreferrer"&gt;esbuild-visualizer&lt;/a&gt; and &lt;a href="https://github.com/btd/rollup-plugin-visualizer" rel="noopener noreferrer"&gt;rollup-plugin-visualizer&lt;/a&gt; by &lt;a href="https://twitter.com/bardadymchik" rel="noopener noreferrer"&gt;@bardadymchik&lt;/a&gt; I added the ability to visualize bundles, this feature comes from a feature request by &lt;a href="https://github.com/atomiks" rel="noopener noreferrer"&gt;@atomiks&lt;/a&gt; on &lt;a href="https://github.com/okikio/bundle/issues/22#:~:text=Further" rel="noopener noreferrer"&gt;issue#22&lt;/a&gt;, the issue is still open you can make suggestions to improve this feature.&lt;/p&gt;

&lt;p&gt;The bundle analysis charts are displayed right under the editor, like so,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvu6ibnz9loowh5dl5yn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvu6ibnz9loowh5dl5yn.png" alt="Image of the bundle analysis panel under the bundlejs code editor" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The charts displayed comes in 3 distinct flavours:&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Treemap Chart&lt;/strong&gt;
  &lt;p&gt;Treemap charts are the most memorable form of bundle analysis chart, the inspiration behind this chart is &lt;a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;webpack-bundle-analyzer&lt;/a&gt;. &lt;a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;webpack-bundle-analyzer&lt;/a&gt; is the &lt;a href="https://www.dictionary.com/browse/progenitor" rel="noopener noreferrer"&gt;progenitor&lt;/a&gt; of bundle analyzers, and a great inspiration to the approach &lt;strong&gt;bundlejs&lt;/strong&gt; took to creating charts.&lt;/p&gt;

&lt;p&gt;Treemap charts,&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt; Help you realize what's really inside your bundle,&lt;/li&gt;
&lt;li&gt; Find out what modules make up the most of its size&lt;/li&gt;
&lt;li&gt; Find modules that got there by mistake&lt;/li&gt;
&lt;li&gt; Optimize it!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;github.com/webpack-contrib/webpack-bundle-analyzer&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff53v6j4pi1wedygdgf3y.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff53v6j4pi1wedygdgf3y.gif" alt="Image of webpacks bundle analysis treemap" width="720" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;github.com/webpack-contrib/webpack-bundle-analyzer&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Though, the &lt;strong&gt;bundlejs&lt;/strong&gt; treemap chart is less powerful than the &lt;a href="https://github.com/webpack-contrib/webpack-bundle-analyzer" rel="noopener noreferrer"&gt;webpack-bundle-analyzer&lt;/a&gt;'s treemap chart, it is simpler, and faster to use, (&lt;strong&gt;bundlejs&lt;/strong&gt; uses esbuild and the bundle analysis is easily available online).&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Network Chart&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhixruaq4e48adosumgwz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhixruaq4e48adosumgwz.png" alt="Image of bundlejs' bundle analysis network chart" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Network charts don't change a lot from the treemap chart, however, they do offer a unique perspective on the impact of the relative sizes of modules in a bundle.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Sunburst Chart&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F429vybzs2ehtqmea9a0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F429vybzs2ehtqmea9a0v.png" alt="Image of bundlejs' bundle analysis sunburst chart" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similar to network charts, sunburst charts don't reinvent the wheel, they accomplish similar things to the treemap chart, though the difference comes in how they represent the relative size of modules in bundles.&lt;/p&gt;

&lt;p&gt;Sunburst charts use &lt;a href="https://en.wikipedia.org/wiki/Pie_chart" rel="noopener noreferrer"&gt;pie charts&lt;/a&gt; to represent bundle sizes, it aids in understanding just how much of the total bundle size certain modules take up.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Technical details...&lt;/strong&gt;
  &lt;blockquote&gt;
&lt;p&gt;📒 &lt;strong&gt;Note&lt;/strong&gt;: All analysis charts support the gzipped and brotli compressed sizes of bundles! When analyzing a bundle it will choose either &lt;code&gt;gzip&lt;/code&gt; or &lt;code&gt;brotli&lt;/code&gt; based on the compression type.&lt;/p&gt;

&lt;p&gt;e.g. A config of,&lt;/p&gt;


&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compression&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gzip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;will use gzip compression for the charts, resulting in,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqryddy30xsdbiw581hn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqryddy30xsdbiw581hn.png" alt="Image of a generated treemap chart with gzip compression on bundlejs" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;



&lt;/p&gt;
&lt;h2&gt;
  
  
  Analytics
&lt;/h2&gt;

&lt;p&gt;When I initially built the project I only used a simple page view counter, I wanted to view how popular the project was without violating user privacy, it worked but I felt it could be better, so I decided to also use &lt;a href="https://umami.is" rel="noopener noreferrer"&gt;umami&lt;/a&gt; as a privacy preserving, cookieless, open source, Google Analytics alternative, to which the analytics are public for anyone to view.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Extra details...&lt;/strong&gt;
  &lt;p&gt;For &lt;strong&gt;bundlejs&lt;/strong&gt; a self-hosted version of &lt;a href="https://umami.is" rel="noopener noreferrer"&gt;umami&lt;/a&gt; is used, this is to ensure user data is kept private and secure. When trying to setup the self-hosted version of umami, I found that the article &lt;a href="https://dev.to/jakobbouchard/setting-up-umami-with-vercel-and-supabase-3a73"&gt;Setting up Umami with Vercel and Supabase&lt;/a&gt; by &lt;a href="https://dev.to/jakobbouchard"&gt;Jakob Bouchard&lt;/a&gt;, was a great help.&lt;/p&gt;

&lt;p&gt;The analytics are publicly available, check them out at, &lt;a href="https://analytics.bundlejs.com/share/bPZELB4V/bundle" rel="noopener noreferrer"&gt;analytics.bundlejs.com/share/bPZELB4V/bundle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or click the page visit counter&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;📒&lt;strong&gt;Note&lt;/strong&gt;: &lt;strong&gt;bundlejs&lt;/strong&gt; is still using a page view counter, the view counter is powered by &lt;a href="https://countapi.xyz/" rel="noopener noreferrer"&gt;countapi&lt;/a&gt; (to the best of my knowledge countapi is now deprecated, however, the servers for the project are still up and running so I'll keep using the project until I switch to using umami for page views as well as general analytics).&lt;/p&gt;
&lt;/blockquote&gt;



&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/jakobbouchard" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F193889%2F79a7aa44-8fbd-45c1-968d-ac568a016ecc.png" alt="jakobbouchard"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/jakobbouchard/setting-up-umami-with-vercel-and-supabase-3a73" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Setting up Umami with Vercel and Supabase&lt;/h2&gt;
      &lt;h3&gt;Jakob Bouchard ・ Feb 2 '21&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#analytics&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#firstpost&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Discussions and Support
&lt;/h2&gt;

&lt;p&gt;To encourage discussion, give support and to gain feedback, I added a comment section to &lt;strong&gt;bundlejs&lt;/strong&gt;, I used &lt;a href="https://github.com/giscus/giscus" rel="noopener noreferrer"&gt;giscus&lt;/a&gt; for this.&lt;/p&gt;

&lt;p&gt;Initialy, when I created the &lt;strong&gt;bundlejs&lt;/strong&gt; project I also created a &lt;a href="https://github.com/okikio/bundle/discussions" rel="noopener noreferrer"&gt;GitHub Discussion&lt;/a&gt; for it as well. I didn't want to have the overhead of having to manage a Discord server, so I choose GitHub Discussions for chats about &lt;strong&gt;bundlejs&lt;/strong&gt;. The problem is that no one really uses &lt;a href="https://github.com/okikio/bundle/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;, so I to integrated it right into the website itself via &lt;a href="https://github.com/giscus/giscus" rel="noopener noreferrer"&gt;giscus&lt;/a&gt;, this was so new users can easily interact with others, get support from me, and leave me feedback.&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Technical details...&lt;/strong&gt;
  &lt;p&gt;&lt;a href="https://github.com/giscus/giscus" rel="noopener noreferrer"&gt;giscus&lt;/a&gt; is an open-source comments system powered by &lt;a href="https://docs.github.com/en/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;, it lets visitors leave comments and reactions on your website via GitHub! It was heavily inspired by &lt;a href="https://github.com/utterance/utterances" rel="noopener noreferrer"&gt;utterances&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;bundlejs&lt;/strong&gt; I'm using a self-hosted version of &lt;a href="https://github.com/giscus/giscus" rel="noopener noreferrer"&gt;giscus&lt;/a&gt;, this was for security reasons mostly. When trying to setup &lt;a href="https://github.com/giscus/giscus" rel="noopener noreferrer"&gt;giscus&lt;/a&gt; for &lt;strong&gt;bundlejs&lt;/strong&gt;, the &lt;a href="https://github.com/giscus/giscus/blob/main/SELF-HOSTING.md" rel="noopener noreferrer"&gt;self-hosting docs&lt;/a&gt; on the GitHub repo are very helpful, I highly suggest anyone thinking of using &lt;a href="https://github.com/giscus/giscus" rel="noopener noreferrer"&gt;giscus&lt;/a&gt; read it, it gives insight into how &lt;a href="https://github.com/giscus/giscus" rel="noopener noreferrer"&gt;giscus&lt;/a&gt; works on the backend.&lt;/p&gt;

&lt;p&gt;You can check out the integrated &lt;code&gt;giscus&lt;/code&gt; comments under the link, &lt;a href="https://bundlejs.com/#discus" rel="noopener noreferrer"&gt;bundlejs.com/#discus&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8tt81hf2uzw80pwuw46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8tt81hf2uzw80pwuw46.png" alt="Image of the discus section on bundlejs, it's pretty barren right now" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;As of right now the comments section is looking really bare and basic, why not leave your mark. Leave a comment with what you love and what you think needs improvement in &lt;strong&gt;bundlejs&lt;/strong&gt;, I'll go through them and try to integrate your ideas into &lt;strong&gt;bundlejs&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and Performance
&lt;/h2&gt;

&lt;p&gt;Security and performance are critical quality areas for &lt;strong&gt;bundlejs&lt;/strong&gt;. In order to bundle modules together, &lt;strong&gt;bundlejs&lt;/strong&gt; has to fetch multiple sets of modules from all over the internet, while ensuring that malicious actors don't get involved, and that &lt;a href="https://esbuild.github.io" rel="noopener noreferrer"&gt;esbuild-wasm&lt;/a&gt; isn't taken advantage of to crash other devices.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Some &lt;em&gt;really&lt;/em&gt;...&lt;strong&gt;really&lt;/strong&gt; large modules can take up to &lt;code&gt;4+ GB&lt;/code&gt; of memory to be bundled properly by &lt;code&gt;esbuild-wasm&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The security criterias I set for &lt;strong&gt;bundlejs&lt;/strong&gt; were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Don't leak personal user info.&lt;/li&gt;
&lt;li&gt; Don't go through a central server, e.g. the ability to get node modules from various sources&lt;/li&gt;
&lt;li&gt; Ensure people always know what packages they are bundling&lt;/li&gt;
&lt;li&gt; Ensure people can't use &lt;strong&gt;bundlejs&lt;/strong&gt; to maliciously slowdown browsers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To ensure I met the security criterias set,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I use strict Content Security Policies (CSP) for &lt;strong&gt;bundlejs&lt;/strong&gt;, ensuring no unintended 3rd party can get involved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I self-host as many of the 3rd party scripts I can. By enclosing the number of hands involved in &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; I reduce the chance that personal information is leaked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I use sandboxing techniques e.g. &lt;code&gt;Web Workers&lt;/code&gt; and &lt;code&gt;Shared Workers&lt;/code&gt; to ensure the main thread runs at a smooth &lt;code&gt;60fps&lt;/code&gt; while avoiding access to the DOM.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;Web Workers&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker" rel="noopener noreferrer"&gt;Shared Workers&lt;/a&gt; are scripts that run on a seperate thread, by using Workers I am able to isolate potentially malicious code while ensuring that the main-thread isn't affected.&lt;/p&gt;

&lt;p&gt;Most of the uses of Workers were &lt;code&gt;Shared Workers&lt;/code&gt;. &lt;code&gt;Shared Workers&lt;/code&gt; reduce the performance impact of multiple instances of the &lt;strong&gt;bundlejs&lt;/strong&gt; site/web app running on the same device.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To reduce the chance of &lt;strong&gt;bundlejs&lt;/strong&gt; being used to maliciously slowdown browsers, I ensure the share URL is easy to read and understand, and by default disable auto-bundling for shared URLs.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The current browser landscape for &lt;code&gt;Shared Worker&lt;/code&gt; support is spotty at best.&lt;/p&gt;

&lt;p&gt;The support table looks like this,&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Browser&lt;/th&gt;
&lt;th&gt;Shared Workers&lt;/th&gt;
&lt;th&gt;&lt;a href="https://web.dev/module-workers/" rel="noopener noreferrer"&gt;Module Workers&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Firefox&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Safari&lt;/td&gt;
&lt;td&gt;&lt;a href="https://twitter.com/webkit/status/1506787526937235464" rel="noopener noreferrer"&gt;Yes*&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;* Support for Safari is currently experimental, but should be coming in later versions&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://web.dev/module-workers/" rel="noopener noreferrer"&gt;Module Workers&lt;/a&gt; are esmodules that run in Workers.&lt;/p&gt;

&lt;p&gt;📒 &lt;strong&gt;Note&lt;/strong&gt;: I built a &lt;code&gt;Shared Worker&lt;/code&gt; polyfill &lt;a href="https://github.com/okikio/sharedworker" rel="noopener noreferrer"&gt;@okikio/sharedworker&lt;/a&gt;. It's a small but simple polyfill that falls back to a regular &lt;code&gt;Web Worker&lt;/code&gt; if &lt;code&gt;Shared Workers&lt;/code&gt; are not supported. You can use it while waiting for the next version of Safari to support &lt;code&gt;Shared Workers&lt;/code&gt;, or while supporting older versions of Safari.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Warning&lt;/strong&gt;: The &lt;a href="https://github.com/okikio/sharedworker" rel="noopener noreferrer"&gt;Shared Worker polyfill&lt;/a&gt; doesn't handle module workers, you will still need to somehow compile your modules to non-esm versions to support workers in Firefox. You can view how &lt;strong&gt;bundlejs&lt;/strong&gt; handles module workers in the &lt;a href="https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts" rel="noopener noreferrer"&gt;bundlejs source code&lt;/a&gt;, you may also wish to view the &lt;a href="https://github.com/withastro/astro-repl/blob/main/src/utils/WebWorker.ts" rel="noopener noreferrer"&gt;astro-repl source code&lt;/a&gt; to see how it handles module workers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most of the other security policies are passive in nature, e.g.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;bundlejs&lt;/strong&gt; only bundling on page load if the URL has &lt;code&gt;?bundle&lt;/code&gt; in it.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;bundlejs&lt;/strong&gt; enforcing &lt;code&gt;https://&lt;/code&gt; for all requests, including for iframes, etc...&lt;/li&gt;
&lt;li&gt;  Only have properly vetted CDN hosts for &lt;strong&gt;bundlejs&lt;/strong&gt; by default.&lt;/li&gt;
&lt;li&gt;  etc...&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tips and Tricks
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Top tier tip&lt;/strong&gt;, follow me (&lt;a href="https://twitter.com/okikio_dev" rel="noopener noreferrer"&gt;@okikio_dev&lt;/a&gt;) and &lt;strong&gt;bundlejs&lt;/strong&gt; (&lt;a href="https://twitter.com/jsbundle" rel="noopener noreferrer"&gt;@jsbundle&lt;/a&gt;) on twitter; &lt;a href="https://www.urbandictionary.com/define.php?term=Shameless%20Plug" rel="noopener noreferrer"&gt;shameless plug&lt;/a&gt; 🤣.&lt;/p&gt;

&lt;p&gt;I do post announcments and updates on these accounts, as well as small tips and tricks that help in making the most use of &lt;strong&gt;bundlejs&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;When bundling packages that also export CSS and other external files, &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; now checks the gzip/brotli size of these external files, however, it won't output the external files' code, this behaviour may change in the future but for now that is the approach I am going with. Keep this in mind this isn't a bug, however, if it causes confusion I am willing to change this behaviour.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treeshaking is available, but not all CDNs support access to each packages &lt;code&gt;package.json&lt;/code&gt; so there might be slight package version conflicts. The only verified CDN with access to the package.json is &lt;a href="https://unpkg.com" rel="noopener noreferrer"&gt;https://unpkg.com&lt;/a&gt;. The other CDN's that are used either pre-bundle the code for us (this is hit or miss depending on the package) or they aren't full npm CDN's e.g. &lt;a href="https://deno.land" rel="noopener noreferrer"&gt;https://deno.land&lt;/a&gt; or &lt;a href="https://raw.githubusercontent.com" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the full devtools console for error messages and warnings, if you are having trouble debuging an issue in &lt;strong&gt;bundlejs&lt;/strong&gt;, or even better yet enable &lt;code&gt;esbuilds&lt;/code&gt; verbose logging when trying to debug issues, e.g.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;esbuild&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;logLevel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verbose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Check out a &lt;a href="https://bundlejs.com/?config={" rel="noopener noreferrer"&gt;demo&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can use custom protocols to specify which CDN's specific imports/exports module should use. If an error occurs such that you can't bundle a package properly, I highly suggest switching CDN's via either custom protocols or by changing the &lt;code&gt;cdn&lt;/code&gt; config option. I recommend using custom protocols instead of the &lt;code&gt;cdn&lt;/code&gt; config option when trying to debug issues with a CDN:&lt;/p&gt;

&lt;p&gt;e.g.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cdn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unpkg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;or&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unpkg:typescript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Try using custom protocols to solve this &lt;a href="https://bundlejs.com/?q=@babel/core&amp;amp;config={" rel="noopener noreferrer"&gt;example issue&lt;/a&gt; on &lt;strong&gt;bundlejs&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For some packages a soft error occurs where the default export is excluded from the treeshaken bundle, the solution for this is to manually include the default export like so,&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skypack:solid-dismiss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// and&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;skypack:solid-dismiss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;blockquote&gt;
&lt;p&gt;If you have a tip and trick you would like to share, post a comment below, or send me a &lt;a href="https://twitter.com/okikio_dev" rel="noopener noreferrer"&gt;tweet&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Contribute
&lt;/h2&gt;

&lt;p&gt;The codebase is currently quite disorganized so, I'd suggest direct messaging me on &lt;a href="https://twitter.com/okikio_dev" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or starting a &lt;a href="https://github.com/okikio/bundle/discussions" rel="noopener noreferrer"&gt;GitHub Discussion&lt;/a&gt; to discuss ways to contribute.&lt;/p&gt;

&lt;p&gt;There is a lot of stuff happening on the &lt;strong&gt;bundlejs&lt;/strong&gt; project and it can be very overwhelming, if you think you can still contribute by all means please do! I will eventually get to writing detailed docs, on how to contribute, and how everything works in the backend, look forward to it.&lt;/p&gt;

&lt;p&gt;You can use a pre-made Gitpod dev environment to quickly get started with the project or to contribute quick changes to the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitpod.io/#https://github.com/okikio/bundle/blob/main/README.md" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgitpod.io%2Fbutton%2Fopen-in-gitpod.svg" alt="Open In Gitpod" width="162" height="48"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you love the project, I'd welcome if you'd spread the word, my goal is to make &lt;strong&gt;bundlejs&lt;/strong&gt; a viable alternative/replacement for &lt;a href="https://bundlephobia.com/" rel="noopener noreferrer"&gt;bundlephobia&lt;/a&gt; and even local bundlers, but right now the project is so small that most people who'd benefit from it don't know about it. I'd love to see people using it.&lt;/p&gt;

&lt;p&gt;Last note, &lt;strong&gt;bundlejs&lt;/strong&gt; is now on &lt;a href="https://opencollective.com/bundle" rel="noopener noreferrer"&gt;OpenCollective&lt;/a&gt;, so if you'd like to contribute to it financially, it'd be appreciated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs&lt;/a&gt;&lt;/strong&gt;, a quick and easy way to treeshake, bundle, minify, and compress (in either &lt;a href="https://en.wikipedia.org/wiki/Gzip" rel="noopener noreferrer"&gt;gzip&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Brotli" rel="noopener noreferrer"&gt;brotli&lt;/a&gt;) your &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;typescript&lt;/a&gt;, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript" rel="noopener noreferrer"&gt;javascript&lt;/a&gt;, &lt;a href="https://reactjs.org/docs/introducing-jsx.html" rel="noopener noreferrer"&gt;jsx&lt;/a&gt; and &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt; projects, while receiving the total bundles' file size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bundlejs&lt;/strong&gt; aims to generate more accurate bundle size estimates by following the same approach that bundlers use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Doing all bundling locally&lt;/li&gt;
&lt;li&gt;  Outputing the treeshaken bundled code&lt;/li&gt;
&lt;li&gt;  Getting the resulting bundle size&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benefits of using &lt;strong&gt;bundlejs&lt;/strong&gt; are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; It's easier to debug errors&lt;/li&gt;
&lt;li&gt; You can verify the resulting bundled code&lt;/li&gt;
&lt;li&gt; The ability to configure your bundles&lt;/li&gt;
&lt;li&gt; The ability to treeshake bundles&lt;/li&gt;
&lt;li&gt; The ability to view a visual analysis of bundles&lt;/li&gt;
&lt;li&gt; You can bundle offline (so long as the module has been used before)&lt;/li&gt;
&lt;li&gt; Supports different types of modules from varying Content Delivery Networks (CDNs), e.g. CDNs ranging from deno modules, to npm modules, to random github scripts, etc...&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next time you need to bundle a project or you need to know the bundle size of a project, give &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; a try.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📒&lt;strong&gt;Note&lt;/strong&gt;: There will be a follow up article to this one, going into the technical nitty gritty on how &lt;strong&gt;bundlejs&lt;/strong&gt; works and how you can use what I've learned from this project to either create your own online bundler or an esbuild-wasm backed js repl.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Photo by &lt;a href="https://okiki.dev/" rel="noopener noreferrer"&gt;Okiki Ojo&lt;/a&gt;, you can find the image on &lt;a href="https://www.dropbox.com/sh/dmu48xw2dbfiyui/AAD-LGqX_zwpZYgyIgYpCelba?dl=0" rel="noopener noreferrer"&gt;Dropbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://blog.okikio.dev/documenting-an-online-bundler-bundlejs" rel="noopener noreferrer"&gt;blog.okikio.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, published on &lt;a href="https://hackernoon.com/bundlejs-an-online-esbuild-based-bundler" rel="noopener noreferrer"&gt;Hackernoon&lt;/a&gt; and &lt;a href="https://dev.to/okikio/documenting-an-online-esbuild-based-bundler-bundlejs-4m52"&gt;dev.to&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>documentation</category>
      <category>bundlesize</category>
    </item>
    <item>
      <title>@okikio/sharedworker, SharedWorkers on all browsers</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Fri, 15 Oct 2021 22:49:14 +0000</pubDate>
      <link>https://forem.com/okikio/okikiosharedworker-sharedworkers-on-all-browsers-1ia0</link>
      <guid>https://forem.com/okikio/okikiosharedworker-sharedworkers-on-all-browsers-1ia0</guid>
      <description>&lt;p&gt;For &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt;, and &lt;a href="https://astro.build/play" rel="noopener noreferrer"&gt;astro.build/play&lt;/a&gt;, I found that I needed a way to use &lt;code&gt;SharedWorkers&lt;/code&gt; reliably on all browsers, so, I decided to make a &lt;a href="https://gist.github.com/okikio/6809cfc0cdbf1df4c0573addaaf7e259" rel="noopener noreferrer"&gt;miniature script&lt;/a&gt; that would act as a wrapper around the &lt;code&gt;SharedWorker&lt;/code&gt; class, by default it would try to create a &lt;code&gt;SharedWorker&lt;/code&gt; but otherwise would switch to normal web workers this made &lt;code&gt;SharedWorkers&lt;/code&gt; a type of &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement" rel="noopener noreferrer"&gt;progressive enhancement&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When I realized that a polyfill/ponyfill doesn't exist for &lt;code&gt;SharedWorkers&lt;/code&gt; I realized I needed to make one, and to ensure reliable that the polyfill was thoroughly vetted and tested for cross browser compatibility, so, I made &lt;a href="https://github.com/okikio/sharedworker#okikiosharedworker" rel="noopener noreferrer"&gt;@okikio/sharedworker&lt;/a&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/okikio" rel="noopener noreferrer"&gt;
        okikio
      &lt;/a&gt; / &lt;a href="https://github.com/okikio/sharedworker" rel="noopener noreferrer"&gt;
        sharedworker
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A small spec. compliant polyfill for SharedWorkers, it acts as a drop in replacement for normal Workers.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
&lt;a class="mentioned-user" href="https://dev.to/okikio"&gt;@okikio&lt;/a&gt;/sharedworker&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://bundlejs.com/?q=@okikio/sharedworker&amp;amp;bundle" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c1ddfaf33d8d14dfcf02ff4c5a99562cb051d6440bfe21b44ade6e5a36c433b2/68747470733a2f2f62756e646c656a732e636f6d2f62616467652d6c696768742e737667" alt="Open Bundle"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@okikio/sharedworker" rel="nofollow noopener noreferrer"&gt;NPM&lt;/a&gt; &lt;span&gt;|&lt;/span&gt; &lt;a href="https://github.com/okikio/sharedworker#readme" rel="noopener noreferrer"&gt;Github&lt;/a&gt; &lt;span&gt;|&lt;/span&gt; &lt;a href="https://sharedworker.okikio.dev" rel="nofollow noopener noreferrer"&gt;Docs&lt;/a&gt; &lt;span&gt;|&lt;/span&gt; &lt;a href="https://github.com/okikio/sharedworker/./LICENSE" rel="noopener noreferrer"&gt;Licence&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A small mostly spec. compliant polyfill/ponyfill for &lt;code&gt;SharedWorkers&lt;/code&gt;, it acts as a drop in replacement for normal &lt;code&gt;Workers&lt;/code&gt;, and supports a similar API surface that matches normal &lt;code&gt;Workers&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/sindresorhus/ponyfill" rel="noopener noreferrer"&gt;Ponyfills&lt;/a&gt; are seperate modules that are included to replicate the functionality of the original API, but are not required to be used.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/Polyfill" rel="nofollow noopener noreferrer"&gt;Polyfills&lt;/a&gt; update the original API on the global scope if it isn't supported in that specific environment or it's feature set is lacking compared to modern variations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Check out the &lt;a href="https://blog.okikio.dev/sharedworker" rel="nofollow noopener noreferrer"&gt;blog post&lt;/a&gt;, created for it's launch.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install &lt;a class="mentioned-user" href="https://dev.to/okikio"&gt;@okikio&lt;/a&gt;/sharedworker&lt;/pre&gt;

&lt;/div&gt;

    Others
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;yarn add &lt;a class="mentioned-user" href="https://dev.to/okikio"&gt;@okikio&lt;/a&gt;/sharedworker&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pnpm install &lt;a class="mentioned-user" href="https://dev.to/okikio"&gt;@okikio&lt;/a&gt;/sharedworker&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-ts notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-v"&gt;SharedWorkerPolyfill&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-v"&gt;SharedWorker&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"&lt;a class="mentioned-user" href="https://dev.to/okikio"&gt;@okikio&lt;/a&gt;/sharedworker"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-c"&gt;// or &lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;SharedWorker&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"&lt;a class="mentioned-user" href="https://dev.to/okikio"&gt;@okikio&lt;/a&gt;/sharedworker"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;You can also use it directly through a script tag:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt; &lt;span class="pl-c1"&gt;src&lt;/span&gt;="&lt;span class="pl-s"&gt;https://unpkg.com/&lt;a class="mentioned-user" href="https://dev.to/okikio"&gt;@okikio&lt;/a&gt;/sharedworker&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt; &lt;span class="pl-c1"&gt;type&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/okikio/sharedworker" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@okikio/sharedworker&lt;/code&gt; is a small mostly spec. compliant polyfill/ponyfill for &lt;code&gt;SharedWorkers&lt;/code&gt;, it acts as a drop in replacement for normal &lt;code&gt;Workers&lt;/code&gt;, and supports a similar API surface that matches normal &lt;code&gt;Workers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You use it like this,&lt;/p&gt;

&lt;p&gt;&lt;code&gt;shared-worker.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* 
 * All variables and values outside the `start(...)` function are shared between all pages, this behavior can cause unexpected bugs if you're not careful
 */
const start = (port) =&amp;gt; {
    // All your normal Worker and SharedWorker stuff should just work
    // With no more setup 

    /** 
     * All variables and values inside the `start(...)` function are isolated to each page, and will be allocated seperately per page. 
     */
    port.onmessage = ({ data }) =&amp;gt; {
        if (data == "Hey")
            port.postMessage("Hello, from the SharedWorker."); 
    };
};

self.onconnect = e =&amp;gt; {
    let [port] = e.ports;
    start(port);
};

// This is the fallback, just in case the browser doesn't support SharedWorkers
if ("SharedWorkerGlobalScope" in self) 
    start(self);

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import SharedWorker from "@okikio/sharedworker";

const sharedworker = new SharedWorker(new URL("shared-worker.js", import.meta.url));
sharedworker.onmessage = ({ data }) =&amp;gt; {
    console.log(data); //= Hello, from SharedWorker
};

sharedworker.postMessage("Hey");

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

&lt;/div&gt;



&lt;p&gt;In the cases of &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; and &lt;a href="https://astro.build/play" rel="noopener noreferrer"&gt;astro.build/play&lt;/a&gt;, &lt;code&gt;@okikio/sharedworker&lt;/code&gt; was used for &lt;a href="https://github.com/okikio/bundle/blob/main/src/ts/workers/esbuild.ts" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; as well as the &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;monaco-editors&lt;/a&gt; &lt;a href="https://github.com/snowpackjs/astro-repl/blob/main/src/editor/workers/editor.ts" rel="noopener noreferrer"&gt;editor&lt;/a&gt; and &lt;a href="https://github.com/okikio/bundle/blob/main/src/ts/workers/typescript.ts" rel="noopener noreferrer"&gt;typescript&lt;/a&gt; workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitation
&lt;/h2&gt;

&lt;p&gt;The major limitation with &lt;code&gt;@okikio/sharedworker&lt;/code&gt; is that on browsers that don't support &lt;code&gt;SharedWorker&lt;/code&gt;, you can't use &lt;code&gt;@okikio/sharedworker&lt;/code&gt; as a cross tab, communication tool. But for everything else it's feature parity and spec. compliance should be great.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So, will you use it? Tell me below, or say Hi on &lt;a href="https://twitter.com/okikio_dev/status/1449083583684124679?s=20" rel="noopener noreferrer"&gt;twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1449083583684124679-488" src="https://platform.twitter.com/embed/Tweet.html?id=1449083583684124679"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1449083583684124679-488');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1449083583684124679&amp;amp;theme=dark"
  }



&lt;/p&gt;




&lt;p&gt;&lt;a href="https://unsplash.com/photos/_j9PDVy-WYg" rel="noopener noreferrer"&gt;Image&lt;/a&gt; from &lt;a href="https://unsplash.com/@tengyart" rel="noopener noreferrer"&gt;Tengyart&lt;/a&gt; on &lt;a href="https://unsplash.com/" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ts</category>
      <category>astro</category>
    </item>
    <item>
      <title>Major updates for bundlejs.com v0.0.3</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Mon, 20 Sep 2021 05:46:54 +0000</pubDate>
      <link>https://forem.com/okikio/major-updates-for-bundle-js-org-v0-0-3-5f1i</link>
      <guid>https://forem.com/okikio/major-updates-for-bundle-js-org-v0-0-3-5f1i</guid>
      <description>&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1439825148769619972-215" src="https://platform.twitter.com/embed/Tweet.html?id=1439825148769619972"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1439825148769619972-215');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1439825148769619972&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;I released major updates for &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;https://bundlejs.com&lt;/a&gt;, they include&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add copy, wrap, format &amp;amp; reset btns&lt;/li&gt;
&lt;li&gt;add PWA screenshots &amp;amp; sharing to PWA&lt;/li&gt;
&lt;li&gt;detect offline/online mode&lt;/li&gt;
&lt;li&gt;faster preloading &amp;amp; prefetching&lt;/li&gt;
&lt;li&gt;prefetch workers&lt;/li&gt;
&lt;li&gt;preload monaco.min.js for faster perf&lt;/li&gt;
&lt;li&gt;and more....&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can read more about bundlejs.com on the Github repo,&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/okikio" rel="noopener noreferrer"&gt;
        okikio
      &lt;/a&gt; / &lt;a href="https://github.com/okikio/bundlejs" rel="noopener noreferrer"&gt;
        bundlejs
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An online tool to quickly bundle &amp;amp; minify your projects, while viewing the compressed gzip/brotli bundle size, all running locally on your browser.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;&lt;a href="https://bundlejs.com" rel="nofollow noopener noreferrer"&gt;bundlejs&lt;/a&gt;&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;✨ WOOOOOOH!! ✨ The bundlejs api is now out at &lt;a href="https://deno.bundlejs.com" rel="nofollow noopener noreferrer"&gt;deno.bundlejs.com&lt;/a&gt; and/or &lt;a href="https://edge.bundlejs.com" rel="nofollow noopener noreferrer"&gt;edge.bundlejs.com&lt;/a&gt;, bundle your code and get a badge.&lt;/p&gt;
&lt;p&gt;Check out the &lt;a href="https://twitter.com/jsbundle/status/1634455567321255936?s=20" rel="nofollow noopener noreferrer"&gt;announcement tweet&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I see a badge, you see a badge, we all see badges!!!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://bundlejs.com/?q=spring-easing" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/30b1b9482b2d369399b8e5a591bfb99586efcbedb36af84e92b15729e67a84fe/68747470733a2f2f64656e6f2e62756e646c656a732e636f6d2f3f713d737072696e672d656173696e672662616467653d64657461696c65642662616467652d7374796c653d666f722d7468652d6261646765" alt="spring-easing's badge"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;To create a badge just replace the domain &lt;code&gt;bundlejs.com&lt;/code&gt; domain with &lt;code&gt;deno.bundlejs.com&lt;/code&gt; and add &lt;code&gt;/?badge&lt;/code&gt;, yeah, that simple&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/posts/bundle-6?utm_source=badge-featured&amp;amp;utm_medium=badge&amp;amp;utm_souce=badge-bundle-6" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/bdce9bd5f65eb98dd71978ed1409616e0f06afc62392f8de61a9f407666404eb/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f66656174757265642e7376673f706f73745f69643d333030353638267468656d653d6461726b" alt="bundle - An online npm package bundle size checker | Product Hunt" width="250" height="54"&gt;&lt;/a&gt; &lt;a href="https://gitpod.io/#https://github.com/okikio/bundle/blob/main/README.md" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b04f5659467d23b5109ba935a40c00decd264eea25c22d50a118021349eea94f/68747470733a2f2f676974706f642e696f2f627574746f6e2f6f70656e2d696e2d676974706f642e737667" alt="Open In Gitpod"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A small online tool for checking the minified gzip/brotli size of npm packages.&lt;/p&gt;
&lt;p&gt;I used &lt;a href="https://github.com/microsoft/monaco-editor" rel="noopener noreferrer"&gt;monaco-editor&lt;/a&gt; for the code-editor, &lt;a href="https://github.com/evanw/esbuild" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt; as bundler and treeshaker respectively, &lt;a href="https://github.com/hazae41/denoflate" rel="noopener noreferrer"&gt;denoflate&lt;/a&gt; as a wasm port of gzip, &lt;a href="https://github.com/denosaurs/deno_brotli" rel="noopener noreferrer"&gt;deno_brotli&lt;/a&gt; as a wasm port of brotli, &lt;a href="https://github.com/denosaurs/deno_lz4" rel="noopener noreferrer"&gt;deno_lz4&lt;/a&gt; as a wasm port of lz4, &lt;a href="https://github.com/visionmedia/bytes.js" rel="noopener noreferrer"&gt;bytes&lt;/a&gt; to convert the compressed size to human readable values, &lt;a href="https://github.com/btd/esbuild-visualizer" rel="noopener noreferrer"&gt;esbuild-visualizer&lt;/a&gt; to visualize and analyze your esbuild bundle to see which modules are taking up space and, &lt;a href="https://github.com/mikecao/umami" rel="noopener noreferrer"&gt;umami&lt;/a&gt; for private, publicly available analytics and general usage stats all without cookies.&lt;/p&gt;
&lt;p&gt;This project was greatly influenced by…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/okikio/bundlejs" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>bundling</category>
      <category>esbuild</category>
    </item>
    <item>
      <title>Github Codespaces vs. Gitpod: Choosing the Best Online Code Editor</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Thu, 02 Sep 2021 13:44:01 +0000</pubDate>
      <link>https://forem.com/okikio/github-codespaces-vs-gitpod-an-in-depth-look-20lg</link>
      <guid>https://forem.com/okikio/github-codespaces-vs-gitpod-an-in-depth-look-20lg</guid>
      <description>&lt;p&gt;&lt;a href="https://www.gitpod.io/" rel="noopener noreferrer"&gt;Gitpod&lt;/a&gt; and &lt;a href="https://github.com/features/codespaces" rel="noopener noreferrer"&gt;Github Codespaces&lt;/a&gt; are both &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; based online code editors, with attached Linux dev environment servers, for running terminal tasks; in simple terms, both are cloud-based code editors, and are free* to use.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: Github Codespaces is only free for personal use during its beta period, which might change in the future, for Github Teams and Organizations there is a pay-as-you-go pricing setup. To get access to personal Github Codespaces you will need to sign up for the &lt;a href="https://github.com/features/codespaces/signup" rel="noopener noreferrer"&gt;beta&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article details my personal experiences with Gitpod and Github Codespaces, these are my opinions and not definitive facts, your experiences may differ from mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why online IDEs?
&lt;/h2&gt;

&lt;p&gt;I have more experience with Gitpod, as it's been around for quite a bit longer than Github Codespaces, however, my experience with online code editors and IDEs is longer than that. &lt;/p&gt;

&lt;p&gt;My first experience with an online code editor was &lt;a href="https://aws.amazon.com/cloud9/" rel="noopener noreferrer"&gt;Cloud9&lt;/a&gt; in 2016 (this was before Cloud9 was bought by Amazon and became AWS Cloud9). At the time Cloud9 was a free service and was readily available for personal use, I loved the convenience of the service, so much so, that I fully switched from programming locally to programming online for a short period. All good things come to an end, by December 2019, Cloud9 announced that they were shutting down their standalone service, and instead, Cloud9 would be offered as part of AWS, so, just before I lost access to Cloud9, I started searching for alternatives, ranging from &lt;a href="https://www.eclipse.org/che/" rel="noopener noreferrer"&gt;Eclipse Che&lt;/a&gt;, &lt;a href="https://developers.redhat.com/developer-sandbox/ide" rel="noopener noreferrer"&gt;Red Hat CodeReady Workspaces&lt;/a&gt;, &lt;a href="https://codeanywhere.com/" rel="noopener noreferrer"&gt;Codeanywhere&lt;/a&gt; to &lt;a href="https://stackblitz.com/" rel="noopener noreferrer"&gt;StackBlitz&lt;/a&gt;, out of all the alternatives the most competent of them was Gitpod.&lt;/p&gt;

&lt;h2&gt;
  
  
  The online vscode revolution
&lt;/h2&gt;

&lt;p&gt;The main reason I searched for alternatives was that at that point in time I was attempting to use a Chromebook for all my work. When I found Gitpod I liked the general user experience it was similar to vscode but had some extra additions that made more sense in an online environment, such as the ability to open small previews of websites while developing, etc... Plus, online editors were the only way to code on a Chromebook (this has now changed. Chrome OS now supports Linux as a subsystem), so, 🤷‍♂️. &lt;/p&gt;

&lt;h2&gt;
  
  
  Gitpod vs. Codespaces
&lt;/h2&gt;

&lt;p&gt;Gitpod themselves already made an article about their benefits over Github Codespaces, you can find it here &lt;a href="https://www.gitpod.io/gitpod-vs-github-codespaces" rel="noopener noreferrer"&gt;gitpod.io/gitpod-vs-github-codespaces&lt;/a&gt;. I will give a brief overview of the differences between the two, note where they overstate their differences and benefits, explain how to make the most of the each service, and then give my personal take on both services.&lt;/p&gt;

&lt;p&gt;The first point Gitpod makes is that it's &lt;a href="https://www.gitpod.io/gitpod-vs-github-codespaces#:~:text=25%20Sep%202020.-,Ready%20in%20a%20flash,-Gitpod%20removes%20long" rel="noopener noreferrer"&gt;"Ready in a flash"&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gitpod removes long init and build times by continuously pre-building workspaces for your project. Thereby it allows you to start coding or debugging immediately, from any context, at any time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is technically correct, at least to a certain point. Gitpod's actual building process takes &lt;em&gt;slightly&lt;/em&gt; longer than that of Github Codespaces or at the very least it feels that way, I haven't, nor do I plan to give any exact empirical performance data, as both services are constantly changing things, in fact, the week before I wrote this article, Github introduced &lt;a href="https://dev.to/lostintangent/10-awesome-things-you-can-do-with-github-dev-5fm7"&gt;github.dev&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;FYI (For your information)&lt;/strong&gt;: &lt;a href="https://dev.to/lostintangent/10-awesome-things-you-can-do-with-github-dev-5fm7"&gt;github.dev&lt;/a&gt; is an online vscode based web editor, the difference between this and Github Codespaces, is that Codespaces comes with a terminal, and this doesn't&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the defense of Gitpod, it can prebuild a workspace unlike Github Codespaces, so you can start coding immediately, without having to wait for the long build process to finish.&lt;/p&gt;

&lt;p&gt;Another point that Gitpod makes is that it has &lt;a href="https://www.gitpod.io/gitpod-vs-github-codespaces#:~:text=Gitpod%2C%20GitHub%20Codespaces.-,3x%20more%20power,-By%20leveraging%20cloud" rel="noopener noreferrer"&gt;"3x more power"&lt;/a&gt;,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By leveraging cloud technologies like containers and Kubernetes, Gitpod achieves best-in-class resource efficiency with scalable workspaces running on shared high-powered cloud servers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is probably the iffiest points they make, since I can't verify their server configs, nor can I verify that the plans they used for testing, end up being cheaper in actual use, especially, since Github has only released the pricing scheme for Github Teams and Enterprise, and not personal use. &lt;/p&gt;

&lt;p&gt;As of August 30, 2021, Gitpod has 4 plans*, they are,&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price (Per User/Month)&lt;/th&gt;
&lt;th&gt;Features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open Source&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;50 hours/month + Private &amp;amp; Public Repos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal&lt;/td&gt;
&lt;td&gt;$9&lt;/td&gt;
&lt;td&gt;100 hours/month + 4 Parallel Workspaces + 30min Timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Professional&lt;/td&gt;
&lt;td&gt;$25&lt;/td&gt;
&lt;td&gt;All in Personal + 8 Parallel Workspaces + Unlimited Hours + Teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unleashed&lt;/td&gt;
&lt;td&gt;$39&lt;/td&gt;
&lt;td&gt;All in Professional + 16 Parallel Workspaces + 1hr Timeout + 3hr Timeout boost&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For Github Codespaces, the pricing* is, &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;SKU&lt;/th&gt;
&lt;th&gt;Unit of measure&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codespaces Compute&lt;/td&gt;
&lt;td&gt;2 core&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;td&gt;$0.18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;4 core&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;td&gt;$0.36&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;8 core&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;td&gt;$0.72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;16 core&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;td&gt;$1.44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;32 core&lt;/td&gt;
&lt;td&gt;1 hour&lt;/td&gt;
&lt;td&gt;$2.88&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codespaces Storage&lt;/td&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;1 GB-month&lt;/td&gt;
&lt;td&gt;$0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*Both services have more details included in their pricing scheme, I recommend going through those for detailed and up-to-date information.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; You have to pay for your Codespaces storage for the time while said workspace exists, this includes while you are not actively using the workspace. Gitpod to my knowledge doesn't require paying for storage.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sources: &lt;a href="https://www.gitpod.io/gitpod-vs-github-codespaces" rel="noopener noreferrer"&gt;Gitpod&lt;/a&gt;, &lt;a href="https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-codespaces#codespaces-pricing" rel="noopener noreferrer"&gt;GitHub Codespaces&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the &lt;a href="https://www.gitpod.io/gitpod-vs-github-codespaces#:~:text=MULTI-IDE%20SUPPORT-,GitHub,-Codespaces" rel="noopener noreferrer"&gt;final point&lt;/a&gt; Gitpod makes, they list some of the benefits they offer in a table-like format, I will be blatantly honest, they are leaving out quite a lot of details, so, I will answer in kind but in a more detailed manner. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gitpod&lt;/th&gt;
&lt;th&gt;Github Codespaces&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PRICING (HOSTED)&lt;/td&gt;
&lt;td&gt;Free for Open-Source&lt;/td&gt;
&lt;td&gt;$$$&lt;/td&gt;
&lt;td&gt;Again, this is iffy, as it makes conclusions about Gitpod's prices being cheaper, that isn't quite accurate, and is rather misleading&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LICENSE&lt;/td&gt;
&lt;td&gt;Open Source&lt;/td&gt;
&lt;td&gt;Proprietary&lt;/td&gt;
&lt;td&gt;This is where Gitpod gets a win, their code is actually &lt;a href="https://github.com/gitpod-io/gitpod" rel="noopener noreferrer"&gt;open source&lt;/a&gt;, in-fact their extensions store uses the open-source &lt;a href="https://www.eclipse.org/community/eclipse_newsletter/2020/march/1.php" rel="noopener noreferrer"&gt;OpenVSX extension store&lt;/a&gt;, however, the OpenVSX store ends up being both a benefit and a detriment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GITHUB INTEGRATION&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Gitpod has good support for Github, but Codespaces has better integration. Gitpod requires an &lt;a href="https://www.gitpod.io/docs/getting-started" rel="noopener noreferrer"&gt;Open in Gitpod link&lt;/a&gt; or the &lt;a href="https://www.gitpod.io/docs/browser-extension/" rel="noopener noreferrer"&gt;Gitpod extension&lt;/a&gt;, but Github Codespaces just works right out of the gate, click on any green code dropdown on Github and it will just open Codespaces.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GITLAB INTEGRATION&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BITBUCKET INTEGRATION&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELF-HOST ON GCP&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELF-HOST ON AWS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELF-HOST ON KUBERNETES&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PREBUILDS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SNAPSHOTS&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Assume it's Accurate. In the Github Codespaces beta, you can't share snapshots of workspaces, essentially, each user is forced to build each repo from scratch, for their use case. At least to my knowledge, I am not sure, whether this limitation applies to Github Teams and/or Organizations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VS CODE EXTENSIONS&lt;/td&gt;
&lt;td&gt;Yes*&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Gitpod uses the OpenVSX store, the problem is that the OpenVSX store ends up being both a benefit and detriment to Gitpod. VS Code is open-source, but its store is closed source, so &lt;a href="https://www.eclipse.org/community/eclipse_newsletter/2020/march/1.php" rel="noopener noreferrer"&gt;Eclipse&lt;/a&gt; created the &lt;a href="https://open-vsx.org/" rel="noopener noreferrer"&gt;Open VSX store&lt;/a&gt;, an open-source alternative, the problem is that a bunch of extensions are missing from the OpenVSX store, ranging from &lt;a href="https://copilot.github.com/" rel="noopener noreferrer"&gt;Github Copilot&lt;/a&gt; to &lt;a href="https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsliveshare" rel="noopener noreferrer"&gt;Live Share&lt;/a&gt; and even some open-source extensions that you would expect to be available. In this case, I think Github Codespaces has the better extension store, as it directly uses the same extension store that the local installation of VS Code would use.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IPAD SUPPORT&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VIRTUAL DESKTOP (VNC)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MULTI-IDE SUPPORT&lt;/td&gt;
&lt;td&gt;Yes*&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;I &lt;em&gt;guess&lt;/em&gt; this is accurate. Gitpod allows you to change the base of their service from VS Code to &lt;a href="https://theia-ide.org/" rel="noopener noreferrer"&gt;Theia&lt;/a&gt; (a fully open-source variant of VS Code), I personally find &lt;a href="https://theia-ide.org/" rel="noopener noreferrer"&gt;Theia&lt;/a&gt; subpar compared to VS Code, so, it's not really a feature that will either add or remove to your experience while using Gitpod or Github Codespaces.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Workspace Setup
&lt;/h2&gt;

&lt;p&gt;Both Gitpod and Github Codespaces have config files based on Docker that configures your whole env. On Gitpod their config system uses a &lt;code&gt;.gitpod.yml&lt;/code&gt; file which stores your workspace config info and a &lt;code&gt;.gitpod.Dockerfile&lt;/code&gt; file which sets up a docker image that you can use to run your workspace. By default, Gitpod uses a &lt;a href="https://github.com/gitpod-io/workspace-images/blob/master/full/Dockerfile" rel="noopener noreferrer"&gt;standard docker image&lt;/a&gt; as the foundation for workspaces, the standard image has most of the default tools and programs devs require, plus you can also build on top of it to add small additions to it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.gitpod.yml&lt;/code&gt; files store basic configuration information, ranging from open ports to post-install scripts. Your basic &lt;code&gt;.gitpod.yml&lt;/code&gt; files look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Commands to start on workspace startup&lt;/span&gt;
&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn install&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yarn build&lt;/span&gt;
&lt;span class="c1"&gt;# Ports to expose on workspace startup&lt;/span&gt;
&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;
    &lt;span class="na"&gt;onOpen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;open-preview&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For most of the projects I use Gitpod for, I set up a &lt;code&gt;.gitpod.yml&lt;/code&gt; file like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .gitpod.yml&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.gitpod.Dockerfile&lt;/span&gt;

&lt;span class="c1"&gt;# List the ports you want to expose and what to do when they are served. See https://www.gitpod.io/docs/43_config_ports/&lt;/span&gt;
&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
    &lt;span class="na"&gt;onOpen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;open-preview&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3001&lt;/span&gt;
    &lt;span class="na"&gt;onOpen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ignore&lt;/span&gt;

&lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prebuilds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# enable for the master/default branch (defaults to true)&lt;/span&gt;
    &lt;span class="na"&gt;master&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="c1"&gt;# enable for all branches in this repo (defaults to false)&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="c1"&gt;# enable for pull requests coming from this repo (defaults to true)&lt;/span&gt;
    &lt;span class="na"&gt;pullRequests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="c1"&gt;# enable for pull requests coming from forks (defaults to false)&lt;/span&gt;
    &lt;span class="na"&gt;pullRequestsFromForks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="c1"&gt;# add a "Review in Gitpod" button as a comment to pull requests (defaults to true)&lt;/span&gt;
    &lt;span class="na"&gt;addComment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="c1"&gt;# add a "Review in Gitpod" button to pull requests (defaults to false)&lt;/span&gt;
    &lt;span class="na"&gt;addBadge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="c1"&gt;# add a label once the prebuild is ready to pull requests (defaults to false)&lt;/span&gt;
    &lt;span class="na"&gt;addLabel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prebuilt-in-gitpod&lt;/span&gt;

&lt;span class="c1"&gt;# List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/44_config_start_tasks/&lt;/span&gt;
&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;npm install -g pnpm &amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="s"&gt;pnpm install -g ultra-runner &amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="s"&gt;pnpm install&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;npm install -g pnpm &amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="s"&gt;pnpm install -g ultra-runner &amp;amp;&amp;amp;&lt;/span&gt;
      &lt;span class="s"&gt;pnpm build &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Gitpod prebuilds section sets up a prebuild for each branch, and pull request, and leaves a comment with a link to the prebuild, check out the &lt;a href="https://www.gitpod.io/docs/prebuilds" rel="noopener noreferrer"&gt;docs for Gitpod prebuilds&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;p&gt;However, where things get interesting is in the tasks section. The &lt;code&gt;init&lt;/code&gt; task is run once on workspace startup, and the &lt;code&gt;command&lt;/code&gt; task is run on workspace startup, and then on every workspace restart. &lt;/p&gt;

&lt;p&gt;The real problem is that the &lt;code&gt;init&lt;/code&gt; task even though it runs on startup, doesn't store any globally installed packages, and from what I can tell this comes from the fact that every terminal environment is based on the Gitpod docker image, as in it basically forces the newly created terminal to use the Gitpod docker image, I recommend reading through &lt;a href="https://www.gitpod.io/docs/config-start-tasks#execution-order" rel="noopener noreferrer"&gt;Gitpod's docs&lt;/a&gt; on the matter. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.gitpod.Dockerfile&lt;/code&gt;, is the file that directly gives you admin access, and enables you to install/do anything you want to your workspace. From my experience, you most likely won't need to change anything in here, except maybe for VNC, and even then, the &lt;a href="https://www.gitpod.io/docs/config-docker" rel="noopener noreferrer"&gt;docs&lt;/a&gt; are very clear.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitpod.Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gitpod/workspace-full:latest&lt;/span&gt;

&lt;span class="c"&gt;# Install custom tools, runtime, etc. using apt-get&lt;/span&gt;
&lt;span class="c"&gt;# For example, the command below would install "bastet" - a command-line tetris clone:&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# RUN sudo apt-get -q update &amp;amp;&amp;amp; &lt;/span&gt;
&lt;span class="c"&gt;#     sudo apt-get install -yq bastet &amp;amp;&amp;amp; &lt;/span&gt;
&lt;span class="c"&gt;#     sudo rm -rf /var/lib/apt/lists/*&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# More information: https://www.gitpod.io/docs/config-docker/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the other hand, setting up a workspace for Github Codespaces is hands-on. Picking a default container is easy enough*, you just follow the &lt;a href="https://code.visualstudio.com/docs/remote/containers" rel="noopener noreferrer"&gt;docs on VS Code&lt;/a&gt;, the real problem is that the setup for Github Codespaces is very overwhelming.&lt;/p&gt;

&lt;p&gt;For Codespaces you need to create a &lt;code&gt;.devcontainer.json&lt;/code&gt; file and store it in the &lt;code&gt;.devcontainer/&lt;/code&gt; folder. The &lt;code&gt;.devcontainer.json&lt;/code&gt; file is a json file that contains the information needed to set up your workspace. The &lt;code&gt;.devcontainer.json&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;details,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;see&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://aka.ms/devcontainer.json.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;options,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;see&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;README&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://github.com/microsoft/vscode-dev-containers/tree/v&lt;/span&gt;&lt;span class="mf"&gt;0.162&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;/containers/typescript-node&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node.js &amp;amp; TypeScript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dockerfile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dockerfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'VARIANT'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;version:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"VARIANT"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*default*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;settings.json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;create.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"npm.packageManager"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;IDs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extensions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;installed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;when&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;created.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"bierner.jsdoc-markdown-highlighting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"yzhang.markdown-all-in-one"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"shd101wyy.markdown-preview-enhanced"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"visualstudioexptteam.vscodeintellicode"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'forwardPorts'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;make&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;inside&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;available&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;locally.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"forwardPorts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'postCreateCommand'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;created.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postCreateCommand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pnpm install"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Comment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;instead.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;More&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;info:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://aka.ms/vscode-remote/containers/non-root.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remoteUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also need to create a &lt;code&gt;Dockerfile&lt;/code&gt; stored inside the &lt;code&gt;.devcontainer/&lt;/code&gt; folder. The &lt;code&gt;Dockerfile&lt;/code&gt; contains docker info, so any configs, you require for your workspace can be set here, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/typescript-node/.devcontainer/base.Dockerfile&lt;/span&gt;

&lt;span class="c"&gt;# [Choice] Node.js version: 16, 14, 12&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; VARIANT="16-buster"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}&lt;/span&gt;

&lt;span class="c"&gt;# [Optional] Uncomment this section to install additional OS packages.&lt;/span&gt;
&lt;span class="c"&gt;# RUN apt-get update &amp;amp;&amp;amp; export DEBIAN_FRONTEND=noninteractive \&lt;/span&gt;
&lt;span class="c"&gt;#     &amp;amp;&amp;amp; apt-get -y install --no-install-recommends &amp;lt;your-package-list-here&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# [Optional] Uncomment if you want to install an additional version of node using nvm&lt;/span&gt;
&lt;span class="c"&gt;# ARG EXTRA_NODE_VERSION=10&lt;/span&gt;
&lt;span class="c"&gt;# RUN su node -c "source /usr/local/share/nvm/nvm.sh &amp;amp;&amp;amp; nvm install ${EXTRA_NODE_VERSION}"&lt;/span&gt;

&lt;span class="c"&gt;# [Optional] Uncomment if you want to install more global node packages&lt;/span&gt;
&lt;span class="c"&gt;# RUN su node -c "npm install -g &amp;lt;your-package-list -here&amp;gt;"&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;su node &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"npm install -g pnpm"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This example is based on one of my Github projects &lt;a href="https://github.com/okikio/native/blob/master/.devcontainer/" rel="noopener noreferrer"&gt;okikio/native&lt;/a&gt;, it's a good starting point for setting up a workspace for your own project.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;: The major thing that can throw you off with the Github Codespaces config is that you can't install local packages (I'm talking about &lt;code&gt;node_modules&lt;/code&gt;) inside the docker config. Of course, this could be my lack of experience with Docker, but thus far I've been unable to get it to work properly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;FYI&lt;/strong&gt;: The Gitpod team is planning to add support for the Github Codespaces &lt;a href="https://github.com/gitpod-io/roadmap/issues/16" rel="noopener noreferrer"&gt;&lt;code&gt;devcontainer.json&lt;/code&gt; format&lt;/a&gt;, so, in the future, you might be able to easily switch between both Github Codespaces and Gitpod without breaking a sweat.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Collaboration
&lt;/h2&gt;

&lt;p&gt;I personally haven't collaborated with anyone on Gitpod or Github Codespaces, but each service does offer a way to collaborate with others. For one, Gitpod allows you to share workspaces with others, and for the other, Github Codespaces allows you to use Live Share to collaborate on the same project. &lt;/p&gt;

&lt;p&gt;I feel Live Share is a better collaboration tool, sure with Gitpod you can share workspaces with others, but with  Codespaces you can both be working on the same project, without skipping a beat, and technically it would be very similar to sharing workspaces with other devs, so...I'll leave the final judgment of this to you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;Github Codespaces has highly detailed documentation, but it's very content dense and a tad bit overwhelming. Gitpod's documentation on the other hand is simpler and more focused on the basics, it's a great way to get started with an online editor. Github Codespaces basically assumes you're already very experienced, while Gitpod assumes you're new to the world of online code editors and slowly builds on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  VNC
&lt;/h2&gt;

&lt;p&gt;Gitpod and Github Codespaces both have &lt;strong&gt;VNC&lt;/strong&gt; support. From my experiences, both are about equal in terms of how VNC works, however, Gitpod is easier to set up with VNC. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Virtual Network Computing (VNC)&lt;/strong&gt; is a graphical desktop-sharing system that uses the Remote Frame Buffer protocol (RFB) to remotely control another computer. It transmits the keyboard and mouse input from one computer to another, relaying the graphical-screen updates, over a network. Read more on &lt;a href="https://en.wikipedia.org/wiki/Virtual_Network_Computing" rel="noopener noreferrer"&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A little while back I tried experimenting to see if I could set up &lt;a href="https://webkit.org/" rel="noopener noreferrer"&gt;Webkit&lt;/a&gt; (Safari's browser engine) on my Windows laptop...I failed, I couldn't figure out how to build the Webkit source code on Windows (it was painfully difficult), so, I tried the next best thing, the &lt;em&gt;&lt;strong&gt;"cloud"&lt;/strong&gt;&lt;/em&gt;, it too failed, but interestingly enough, for a vastly different reason. &lt;/p&gt;

&lt;p&gt;For this experiment, I used Github Codespaces (for a previous experiment I had already tried using Gitpod with VNC, so, I thought I would try Github Codespaces this time). I was able to get VNC functional but couldn't set up Webkit, since, Docker has problems with some of the libraries that Webkit uses. I tried for a good ~16 hours before I gave up. I eventually found another alternative, &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; is an end-to-end framework for testing web apps on multiple browsers,  it's a great tool for testing web apps. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While trying out Playwright, I found it would only work on Windows (this was because of Webkit &amp;amp; Docker's library issue). &lt;/p&gt;

&lt;p&gt;Let's get back to the original topic, VNC. In my experiments, I found setting up VNC on Github Codespaces difficult and the build process for said workspace long, I actually timed it, the build process for Github Codespaces with VNC was ~6 minutes, while, on Gitpod it was much faster (I can't remember exactly how long it took, but what I do remember is that it was less than 5 minutes), also, Gitpod's documentation is much easier to digest and just get started with.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For &lt;strong&gt;VNC on Gitpod&lt;/strong&gt; check out &lt;a href="https://www.gitpod.io/blog/native-ui-with-vnc" rel="noopener noreferrer"&gt;gitpod.io/blog/native-ui-with-vnc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;VNC on Github Codespaces&lt;/strong&gt; check out &lt;a href="https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/desktop-lite.md" rel="noopener noreferrer"&gt;github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/desktop-lite.md&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Annoyances
&lt;/h2&gt;

&lt;p&gt;In Gitpod workspaces every terminal is built from the initial docker image, in theory, this sounds awesome, however, from my experience, it ends up being a huge pain to work around. For example, I have found that if you, use &lt;code&gt;nvm&lt;/code&gt; (node version manager) to install a new version of node, let's say &lt;code&gt;v16&lt;/code&gt; to replace the Gitpod standard images node, &lt;code&gt;v14&lt;/code&gt;, every new terminal you create will use the version of node set up in the standard image, this seems like a very minor issue, but it can cause a bunch of issues and just make you very annoyed over time, plus it just slows down your development velocity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Internet Problem
&lt;/h2&gt;

&lt;p&gt;By using Gitpod and Github Codespaces, you become reliant on the internet, and if you don't have access to the internet, you can't use either service. For most devs, this isn't really a problem, since, in most cases, they need access to the internet to commit changes to Github, Bitbucket, etc... &lt;/p&gt;

&lt;p&gt;For cases where you don't have access to the internet, let's say your ISP messed up somewhere and you lose access for a couple days (I am speaking from personal experience here), then you become unable to do any programming at all.&lt;/p&gt;

&lt;p&gt;For those who are worried about internet connections the best thing you can do is, to make sure you always have a local copy with all the tools and dependencies already installed, so, you can at least make some progress when you lose access.&lt;/p&gt;

&lt;p&gt;For small instances where you lose connection for maybe a minute or two, Github Codespaces and Gitpod keep local copies of all open files, and merge them into the online copy when the connection is re-established, so, no worries there. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Warning&lt;/strong&gt;: One thing you should pay attention to is, if you lose internet access for a long time while using Gitpod, you may lose your workspace, and have to start over. This doesn't really apply to Github Codespaces, since you must actively delete a workspace, Codespaces will shut down a workspace, but it won't delete it for you.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Miscellaneous
&lt;/h2&gt;

&lt;p&gt;A minor difference between Gitpod and Github Codespaces is that Github Codespaces supports using your locally installed version of VS Code to continue development using the VS Code you know and love, as well as forwarding remote ports to localhost ports and much more, all together Github Codespaces allows devs to develop in an environment very similar to local developments except with less resource use (as most of the processing is happening on the remote server), and slightly greater dependence on the internet, read through &lt;a href="https://docs.github.com/en/codespaces/developing-in-codespaces/using-codespaces-in-visual-studio-code" rel="noopener noreferrer"&gt;Github's docs&lt;/a&gt; to learn more. &lt;/p&gt;

&lt;p&gt;Gitpod supports something similar, if you install the Gitpod app as a PWA (from what I know, only Edge allows you to forcefully &lt;a href="https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/#pwas-on-microsoft-edge-chromium" rel="noopener noreferrer"&gt;install websites as apps&lt;/a&gt;), you can then forward the remote ports on the server to your computer's localhost ports, read more about this on &lt;a href="https://www.gitpod.io/blog/local-app" rel="noopener noreferrer"&gt;Gitpod's docs&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;FYI&lt;/strong&gt;: Gitpod is working on support for &lt;a href="https://github.com/gitpod-io/roadmap/issues/19" rel="noopener noreferrer"&gt;local VS Code&lt;/a&gt;, so, by the time you are reading this article it might already be live.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't know how important this is to devs, but Github Codespaces automatically syncs settings, between VS Code and itself. To use this feature with Gitpod, you need to do some setup with your VS Code installation, read through the &lt;a href="https://github.com/gitpod-io/gitpod/issues/3733#issuecomment-813917147" rel="noopener noreferrer"&gt;issue opened by Gitpod&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Github Codespaces?
&lt;/h2&gt;

&lt;p&gt;Github Codespaces is an easy to use and reliable VS Code service, its integration with Github is quite convenient and is hard to properly quantify, its extension support is top tier, its a coding experience that is hard to pass on, especially for devs who already use Github's other services. Github Codespaces is great for devs who need high-resource workspaces and are ok without self-hosting on other platforms. &lt;/p&gt;

&lt;p&gt;Github Codespaces is good, but it's not the perfect solution for everyone. Github's billing model is a bit strenuous, as workspace storage is not free, so, if you want to use Github Codespaces professionally, you might end up paying quite a bit unintentionally, plus, depending on how many hours you use each Github Codespace, your monthly bill can be a rather painful pill to swallow. &lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Gitpod?
&lt;/h2&gt;

&lt;p&gt;Gitpod is an easy-to-use and very reliable VS Code service, and its open-source design allows you to get involved, maybe even fix issues as they arise. Gitpod is great for open-source projects, for those that want a reliable and consistent monthly pricing scheme, for devs looking for non-proprietary VS Code workspaces, or devs that want to use Bitbucket, Gitlab, etc.. and/or to use a self-hosted option on AWS, GCP, etc... Really Gitpod is great for the same reasons as Github Codespaces, it just has a little less Github integration which makes Github Codespaces a nicer experience to use with Github.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Both services are amazing, as they bring a consistent and reliable VS Code experience to devs via the web. However, they are not the perfect solution for everyone, as they each have their ups and downs. &lt;/p&gt;

&lt;p&gt;I have stated my personal experience with both services, and I would recommend using the one that you feel is best for you. Personally, I switch between both services rather often, however, I prefer Github Codespaces. I find it to be the best option for me as it syncs my settings, supports local VS Code, and has great integration with Github and VS Code extensions (I am even able to use &lt;a href="https://copilot.github.com/" rel="noopener noreferrer"&gt;Github Copilot&lt;/a&gt; in Github Codespaces).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; I actually used Github Codespaces together with &lt;a href="https://copilot.github.com/" rel="noopener noreferrer"&gt;Github Copilot&lt;/a&gt; to write this article. Trust me when I say it's an excellent writing companion, it's almost like the Gmail autocomplete, but better, since it also recognizes code syntax, and can apply them rather effectively.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a more neutral and objective comparison between both services, I suggest reading through &lt;a href="https://www.freecodecamp.org/news/github-codespaces-vs-gitpod-cloud-based-dev-environments/" rel="noopener noreferrer"&gt;Nader Dabit's article&lt;/a&gt; on FreeCodeCamp comparing the two. &lt;/p&gt;




&lt;p&gt;The idea for this article came from a &lt;a href="https://twitter.com/nikmd23/status/1431024698754732033?s=20" rel="noopener noreferrer"&gt;tweet&lt;/a&gt; by &lt;a href="https://twitter.com/nikmd23" rel="noopener noreferrer"&gt;Nik Molnar @nikmd23&lt;/a&gt; and &lt;a href="https://twitter.com/meijer_s" rel="noopener noreferrer"&gt;Stephan Meijer @meijer_s&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://blog.okiki.dev/" rel="noopener noreferrer"&gt;Okiki Ojo&lt;/a&gt;, you can find the image on &lt;a href="https://www.dropbox.com/sh/dmu48xw2dbfiyui/AAD-LGqX_zwpZYgyIgYpCelba?dl=0" rel="noopener noreferrer"&gt;Dropbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://blog.okikio.dev/github-codespaces-vs-gitpod-an-in-depth-look" rel="noopener noreferrer"&gt;blog.okikio.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, published on &lt;a href="https://hackernoon.com/github-codespaces-vs-gitpod-choosing-the-best-online-code-editor" rel="noopener noreferrer"&gt;Hackernoon&lt;/a&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>javascript</category>
      <category>vscode</category>
      <category>docker</category>
    </item>
    <item>
      <title>GZIP on the Browser</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Tue, 27 Jul 2021 15:50:12 +0000</pubDate>
      <link>https://forem.com/okikio/gzip-on-the-browser-2mi4</link>
      <guid>https://forem.com/okikio/gzip-on-the-browser-2mi4</guid>
      <description>&lt;p&gt;Out of curiosity I created a small demo detailing how you can implement GZIP in the browser, I used &lt;a href="https://npmjs.com/fflate" rel="noopener noreferrer"&gt;&lt;code&gt;fflate&lt;/code&gt;&lt;/a&gt; for the GZIP compression, and &lt;a href="https://npmjs.com/pretty-bytes" rel="noopener noreferrer"&gt;&lt;code&gt;pretty-bytes&lt;/code&gt;&lt;/a&gt; to convert bytes into human readable format, the demo is really small but as an example it should be more than good enough. I based it on &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt;. Interestingly, I have yet to find a website that lets you see the GZIP size of some random text, all the ones I have found so far expect you to upload a file, the little demo takes a string as an input compresses it, and gives you the final size.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/okikio/embed/ExmQwPM?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I suggest checking out &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt; it's a specialized for bundling, minifying and compressing js, all locally, right on your browser.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://www.producthunt.com/posts/bundle-6" rel="noopener noreferrer"&gt;product hunt&lt;/a&gt; page for &lt;a href="https://bundlejs.com" rel="noopener noreferrer"&gt;bundlejs.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>codepen</category>
      <category>snippet</category>
    </item>
    <item>
      <title>What are your opinions of the Web Animation API (WAAPI)?</title>
      <dc:creator>Okiki Ojo</dc:creator>
      <pubDate>Sat, 10 Jul 2021 07:48:37 +0000</pubDate>
      <link>https://forem.com/okikio/what-are-your-opinions-of-the-web-animation-api-waapi-4ba</link>
      <guid>https://forem.com/okikio/what-are-your-opinions-of-the-web-animation-api-waapi-4ba</guid>
      <description>&lt;p&gt;I recently learned &lt;a href="https://twitter.com/mattgperry" rel="noopener noreferrer"&gt;Matt Perry&lt;/a&gt; the creator of Framer Motion, Popmotion, and Pose is creating another &lt;a href="https://twitter.com/mattgperry/status/1412121262449627140?s=20" rel="noopener noreferrer"&gt;animation library&lt;/a&gt; based on the Web Animation API. &lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1412121262449627140-844" src="https://platform.twitter.com/embed/Tweet.html?id=1412121262449627140"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1412121262449627140-844');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1412121262449627140&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;Similarly, last year I created &lt;a href="https://www.npmjs.com/package/@okikio/animate" rel="noopener noreferrer"&gt;@okikio/animate&lt;/a&gt; an animation library based on the Web Animation API. &lt;/p&gt;

&lt;p&gt;So, I am wondering is there any actual interest in a an animation library that uses the Web Animation API?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>discuss</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
