<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Okiki's Blog]]></title><description><![CDATA[Software Engineer at @vercel; Maintainer on @withastro; Creator of bundlejs.com & inthistweet.app]]></description><link>https://blog.okikio.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1626310784007/1LI0VudoP.png</url><title>Okiki&apos;s Blog</title><link>https://blog.okikio.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 20:28:10 GMT</lastBuildDate><atom:link href="https://blog.okikio.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Hot take: Apple v US gov't, the US gov't is right]]></title><description><![CDATA[There seems to be a misunderstanding as to how the US gov't is using anti-competition laws in the case against Apple. I'm no legal expert but from what I've read, the core problem is Apple's various platforms they allow 3rd parties but through Market...]]></description><link>https://blog.okikio.dev/hot-take-apple-v-us-govt-the-us-govt-is-right</link><guid isPermaLink="true">https://blog.okikio.dev/hot-take-apple-v-us-govt-the-us-govt-is-right</guid><category><![CDATA[hot take]]></category><category><![CDATA[anti-trust]]></category><category><![CDATA[Apple]]></category><category><![CDATA[Lawsuit]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Fri, 05 Apr 2024 03:58:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/wAygsCk20h8/upload/005f3988e448984e3abe9a44d7e1bdbb.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There seems to be a misunderstanding as to how the US gov't is using anti-competition laws in the case against Apple. I'm no legal expert but from what I've read, the core problem is Apple's various platforms they allow 3rd parties but through <strong>Market Manipulation</strong> (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.</p>
<p>Basically you can summarize the law suit down to 2 points,</p>
<ul>
<li><p>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</p>
</li>
<li><p>Apple may not be the largest player per-se but their <strong>Market Power</strong> and <strong>Abuse</strong> of said power is an indication of <strong>Monopolistic</strong> behaviour</p>
<ul>
<li>A good example here would be Amazon and Google. Amazon is not the only shopping website in the world (neither is Google for search) but they have a lot of <strong>Market Power</strong>. On their Platforms they need to ensure that they don't unfairly promote their products at the expense of other competition basically competition must be fair when you have a platform (same thing with Microsoft actually)</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Note</strong>: Gov't don't <em>necessarily</em> 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.</p>
<p><strong>Another Note</strong>: 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</p>
<p><strong>Another Another Note</strong>: 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</p>
</blockquote>
<p><strong>TL;DR;</strong> Apple is abusing <strong>Market Power</strong> 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)</p>
]]></content:encoded></item><item><title><![CDATA[From Docker to Podman - VS Code DevContainers]]></title><description><![CDATA[Been a while... 👀
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 Podman in DevContai...]]></description><link>https://blog.okikio.dev/from-docker-to-podman-vs-code-devcontainers</link><guid isPermaLink="true">https://blog.okikio.dev/from-docker-to-podman-vs-code-devcontainers</guid><category><![CDATA[Docker]]></category><category><![CDATA[podman]]></category><category><![CDATA[containers]]></category><category><![CDATA[Security]]></category><category><![CDATA[VS Code]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Thu, 12 Oct 2023 06:58:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697093367098/692a0669-2ebf-417d-b884-6c5d8bc9fa7a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-been-a-while">Been a while... 👀</h2>
<p>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 <a target="_blank" href="https://podman.io/">Podman</a> in <a target="_blank" href="https://code.visualstudio.com/docs/devcontainers/containers">DevContainers</a> 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!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697076387927/624b916f-7574-41c2-bc15-95de39e9c568.gif" alt /></p>
<h2 id="heading-a-quick-refresher-on-devcontainers"><strong>A Quick Refresher on DevContainers 🏝</strong></h2>
<p>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.</p>
<p>Think of DevContainers as your very own <a target="_blank" href="https://github.com/features/codespaces">Codespace</a> or <a target="_blank" href="https://www.gitpod.io/">Gitpod</a>, 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.</p>
<h2 id="heading-the-podman-appeal"><strong>The Podman Appeal 🎸</strong></h2>
<p>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 line—efficient, user-friendly, and secure.</p>
<p>Docker has been the go-to containerization tool for years, but Podman is emerging as a strong alternative. Podman offers a few advantages:</p>
<ol>
<li><p><strong>Rootless Containers</strong>: Run containers without needing root privileges, enhancing security. <a target="_blank" href="https://opensource.com/article/19/2/how-does-rootless-podman-work">Learn more about rootless containers</a>.</p>
</li>
<li><p><strong>Systemd Integration</strong>: Better integration with Linux's init system, <a target="_blank" href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</a>. For Linux users, this is a significant benefit. Podman's compatibility with <a target="_blank" href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</a> offers better process management and orchestration. <a target="_blank" href="https://www.linux.com/training-tutorials/understanding-and-using-systemd/">Learn more about systemd</a>.</p>
</li>
<li><p><strong>Follows Open Standards</strong>: Fully compatible with <a target="_blank" href="https://opencontainers.org/">Open Container Initiative (OCI)</a> standards. Podman is <a target="_blank" href="https://opencontainers.org/">OCI-compliant</a>, which means it adheres to industry standards for container images, making it easier to switch between different container technologies. <a target="_blank" href="https://opencontainers.org/about/overview/">Learn more about OCI</a>.</p>
</li>
</ol>
<p>So, if you're looking to break free from Docker's grasp, Podman is worth considering.</p>
<h2 id="heading-the-challenge-vs-code-loves-docker">The Challenge: VS Code Loves Docker</h2>
<p>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.</p>
<p><img src="https://media.discordapp.net/attachments/988952050345857024/1161839612466044928/CleanShot_2023-10-11_at_21.35.322x.png?ex=6539c275&amp;is=65274d75&amp;hm=fa14bd8f116da084ee5f64a6cc269e4e52daa74af17f4a3f153aed7fd98933f2&amp;=&amp;width=1314&amp;height=1302" alt="Image" /></p>
<h2 id="heading-the-solution-trick-vs-code-with-a-shell-script">The Solution: Trick VS Code with a Shell Script</h2>
<h3 id="heading-macos">macOS</h3>
<h4 id="heading-step-1-install-podman"><strong>Step 1: Install Podman</strong></h4>
<p>First things first, you'll need to install Podman. On a Mac, you can use <a target="_blank" href="https://brew.sh/">Homebrew</a>:</p>
<pre><code class="lang-bash">brew install podman
</code></pre>
<h4 id="heading-step-2-initialize-and-start-podman-machine"><strong>Step 2: Initialize and Start Podman Machine</strong></h4>
<p>Before using Podman, you need to initialize and start a Podman machine:</p>
<pre><code class="lang-bash">podman machine init
podman machine start
</code></pre>
<h4 id="heading-step-3-create-a-shell-script"><strong>Step 3: Create a Shell Script</strong></h4>
<p>VS Code is looking for a command named <code>docker</code>. We'll give it what it wants, but we'll secretly redirect it to <code>podman</code>.</p>
<p>Create a new shell script and name it <code>docker</code>:</p>
<pre><code class="lang-bash">sudo nano /usr/<span class="hljs-built_in">local</span>/bin/docker
</code></pre>
<p>In the script, add the following lines:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-built_in">exec</span> podman <span class="hljs-string">"<span class="hljs-variable">$@</span>"</span>
</code></pre>
<p>Save the file and exit the text editor.</p>
<h4 id="heading-step-4-make-the-script-executable"><strong>Step 4: Make the Script Executable</strong></h4>
<p>Now, make the script executable:</p>
<pre><code class="lang-bash">sudo chmod +x /usr/<span class="hljs-built_in">local</span>/bin/docker
</code></pre>
<h4 id="heading-step-5-restart-vs-code"><strong>Step 5: Restart VS Code</strong></h4>
<p>Close and reopen VS Code to apply the changes. It should now be none the wiser, happily using Podman instead of Docker.</p>
<h3 id="heading-linux">Linux</h3>
<h4 id="heading-step-1-install-podman-1"><strong>Step 1: Install Podman</strong></h4>
<p>Open your terminal and run the following command to install Podman:</p>
<pre><code class="lang-bash">sudo apt install -y podman
</code></pre>
<h4 id="heading-steps-2-5-follow-the-same-steps-as-for-macos"><strong>Steps 2-5: Follow the same steps as for macOS</strong></h4>
<h3 id="heading-windows">Windows</h3>
<h4 id="heading-step-1-install-podman-for-windows"><strong>Step 1: Install Podman for Windows</strong></h4>
<p>Download and install Podman from the <a target="_blank" href="https://podman.io/docs/installation#windows">official site</a>.</p>
<h4 id="heading-step-2-create-a-batch-file"><strong>Step 2: Create a Batch File</strong></h4>
<p>Create a batch file named <code>docker.bat</code> to act as an alias for Podman:</p>
<pre><code class="lang-powershell">@<span class="hljs-built_in">echo</span> off
podman %*
</code></pre>
<p>Place this batch file in a directory that's in your system's <code>PATH</code>.</p>
<h5 id="heading-option-1-use-an-existing-directory-in-path"><strong>Option 1: Use an Existing Directory in</strong> <code>PATH</code></h5>
<ol>
<li><p>Open a Command Prompt and type <code>echo %PATH%</code> to see the directories currently in your <code>PATH</code>.</p>
</li>
<li><p>Choose an existing directory that you have write access to, such as <code>C:\Users\YourUsername\bin</code>.</p>
</li>
<li><p>Save or move your <code>docker.bat</code> file into that directory.</p>
</li>
</ol>
<h5 id="heading-option-2-create-a-new-directory-and-add-it-to-path"><strong>Option 2: Create a New Directory and Add It to</strong> <code>PATH</code></h5>
<ol>
<li><p>Create a new directory where you want to store your batch files, for example, <code>C:\batch_files</code>.</p>
</li>
<li><p>Move your <code>docker.bat</code> file into this new directory.</p>
</li>
<li><p>To add this directory to your <code>PATH</code>, right-click on 'This PC' or 'Computer' on your desktop or File Explorer, and choose <code>Properties</code>.</p>
</li>
<li><p>On the left-hand side, click on <code>Advanced system settings</code>.</p>
</li>
<li><p>Click on the <code>Environment Variables</code> button near the bottom right.</p>
</li>
<li><p>Under the 'System variables' section, find and select the <code>Path</code> variable, then click on <code>Edit</code>.</p>
</li>
<li><p>Click on <code>New</code> and add the path to your new directory, <code>C:\batch_files</code>.</p>
</li>
<li><p>Click <code>OK</code> on all the dialog boxes to save your changes.</p>
</li>
</ol>
<h5 id="heading-verify-the-setup"><strong>Verify the Setup</strong></h5>
<p>To verify that the batch file is accessible:</p>
<ol>
<li><p>Open a new Command Prompt window (important, as existing windows won't pick up the change).</p>
</li>
<li><p>Type <code>docker</code> and hit Enter. If everything is set up correctly, this should now execute Podman due to the aliasing in your <code>docker.bat</code> file.</p>
</li>
</ol>
<p>By following these steps, you ensure that the batch file is in a directory listed in your <code>PATH</code>, making it accessible from any command prompt window.</p>
<h4 id="heading-step-3-restart-vs-code"><strong>Step 3: Restart VS Code</strong></h4>
<p>Close and reopen VS Code to apply the changes.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>There you have it—a 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.</p>
<p>So go ahead, give Podman a spin, and bring a little more freedom to your containerized development environments.</p>
<p>Bye!</p>
<p><img src="https://media0.giphy.com/media/Ru9sjtZ09XOEg/giphy.gif?cid=ecf05e47n583vcdkcv55z1ipo35ss6aq2m1n4mqsz3ia999o&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt="Video gif. Two young men stand together. One holds up his fingers as if saying peace out and vanishes." /></p>
]]></content:encoded></item><item><title><![CDATA[Mastering the Art of ESM and CJS Package Handling]]></title><description><![CDATA[Greetings, fellow devs and bundlejs aficionados! 🚀
I was closing out some long lived issues over on bundlejs, when issue #50 reminded me of the ongoing debate about how bundlejs should handle the ESM and CJS packages.
Lightbulbs flickered, coffee wa...]]></description><link>https://blog.okikio.dev/mastering-the-art-of-esm-and-cjs-package-handling</link><guid isPermaLink="true">https://blog.okikio.dev/mastering-the-art-of-esm-and-cjs-package-handling</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[bundlejs]]></category><category><![CDATA[esm]]></category><category><![CDATA[cjs]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Sat, 08 Jul 2023 07:40:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688801065300/4b5b5b52-a833-4029-8c2a-3f2d952eb941.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Greetings, fellow devs and bundlejs aficionados! 🚀</p>
<p>I was closing out some long lived issues over on <a target="_blank" href="http://bundlejs.com">bundlejs</a>, when issue <a target="_blank" href="https://github.com/okikio/bundlejs/issues/50">#50</a> reminded me of the ongoing debate about how bundlejs should handle the ESM and CJS packages.</p>
<p>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 <a target="_blank" href="https://github.com/okikio/bundlejs/discussions/53">GitHub Discussions</a> or issue <a target="_blank" href="https://github.com/okikio/bundlejs/issues/50">#50</a> directly.</p>
<h2 id="heading-the-issue-at-hand">The Issue at Hand 🧐</h2>
<p>The root of the problem lies in how bundlejs was handling CommonJS files.</p>
<blockquote>
<p><em>"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."</em></p>
</blockquote>
<p>But never fear! I've worked out a solution that is impartial to whether the module is ESM or CommonJS.</p>
<h2 id="heading-the-solution">The Solution 🧩</h2>
<p>Let's walk through this step by step, using our handy <a target="_blank" href="http://deno.bundlejs.com">deno.bundlejs.com</a> API for demonstration.</p>
<ol>
<li><p><strong>Single CJS File:</strong> Only export the default (no renaming involved). Check out this example using <code>postcss-easings</code>: <a target="_blank" href="https://deno.bundlejs.com/?file&amp;q=postcss-easings&amp;minify=false">https://deno.bundlejs.com/?file&amp;q=postcss-easings&amp;minify=false</a></p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// ...</span>
 <span class="hljs-comment">// virtual-filesystem:/index.ts</span>
 <span class="hljs-keyword">var</span> index_exports = {};
 __export(index_exports, {
   <span class="hljs-keyword">default</span>: <span class="hljs-function">() =&gt;</span> import_postcss_easings.default
 });
 __reExport(index_exports, __toESM(require_postcss_easings_4_0()));
 <span class="hljs-keyword">var</span> import_postcss_easings = __toESM(require_postcss_easings_4_0());
 <span class="hljs-keyword">var</span> export_default = import_postcss_easings.default;
 <span class="hljs-keyword">export</span> {
   export_default <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span>
 };
</code></pre>
</li>
<li><p><strong>Multiple CJS Files:</strong> Export the default but use the file URL as the import name. Also, append <code>...Default</code> to the end of it like so: <code>reactDefault</code> &amp; <code>reactDomDefault</code>. Take a peek here: <a target="_blank" href="https://deno.bundlejs.com/?file&amp;q=react,react-dom&amp;minify=false">https://deno.bundlejs.com/?file&amp;q=react,react-dom&amp;minify=false</a></p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// ...</span>
 <span class="hljs-comment">// virtual-filesystem:/index.ts</span>
 <span class="hljs-keyword">var</span> index_exports = {};
 __export(index_exports, {
   reactDefault: <span class="hljs-function">() =&gt;</span> import_react.default,
   reactDomDefault: <span class="hljs-function">() =&gt;</span> import_react_dom.default
 });
 __reExport(index_exports, __toESM(require_react_18_2()));
 <span class="hljs-keyword">var</span> import_react = __toESM(require_react_18_2());
 __reExport(index_exports, __toESM(require_react_dom_18_2()));
 <span class="hljs-keyword">var</span> import_react_dom = __toESM(require_react_dom_18_2());
 <span class="hljs-keyword">var</span> export_reactDefault = import_react.default;
 <span class="hljs-keyword">var</span> export_reactDomDefault = import_react_dom.default;
 <span class="hljs-keyword">export</span> {
   export_reactDefault <span class="hljs-keyword">as</span> reactDefault,
   export_reactDomDefault <span class="hljs-keyword">as</span> reactDomDefault
 };
</code></pre>
</li>
<li><p><strong>Single ESM File:</strong> Export everything, including the default. No renaming here, folks! <a target="_blank" href="https://deno.bundlejs.com/?file&amp;q=spring-easing&amp;minify=false">https://deno.bundlejs.com/?file&amp;q=spring-easing&amp;minify=false</a></p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// ...</span>
 <span class="hljs-keyword">export</span> {
   BatchSpringEasing,
   CSSSpringEasing,
   EaseInOut,
   EaseOut,
   EaseOutIn,
   EasingDurationCache,
   EasingFunctionKeys,
   EasingFunctions,
   EasingOptions,
   FramePtsCache,
   GenerateSpringFrames,
   INFINITE_LOOP_LIMIT,
   SpringEasing,
   SpringFrame,
   SpringInFrame,
   SpringInOutFrame,
   SpringOutFrame,
   SpringOutInFrame,
   batchInterpolateComplex,
   batchInterpolateNumber,
   batchInterpolateSequence,
   batchInterpolateString,
   batchInterpolateUsingIndex,
   SpringEasing <span class="hljs-keyword">as</span> <span class="hljs-keyword">default</span>, <span class="hljs-comment">// &lt;- The default export</span>
   getLinearSyntax,
   getOptimizedPoints,
   getSpringDuration,
   getUnit,
   interpolateComplex,
   interpolateNumber,
   interpolateSequence,
   interpolateString,
   interpolateUsingIndex,
   isNumberLike,
   limit,
   parseEasingParameters,
   ramerDouglasPeucker,
   registerEasingFunction,
   registerEasingFunctions,
   scale,
   squaredSegmentDistance,
   toAnimationFrames,
   toFixed
 };
</code></pre>
</li>
<li><p><strong>Multiple ESM Files:</strong> 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 <code>springEasingDefault</code> and <code>codepointIteratorDefault</code>: <a target="_blank" href="https://deno.bundlejs.com/?file&amp;q=spring-easing,codepoint-iterator&amp;minify=false">https://deno.bundlejs.com/?file&amp;q=spring-easing,codepoint-iterator&amp;minify=false</a></p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// ...</span>
 <span class="hljs-keyword">var</span> mod_default = asCodePointsIterator;
 <span class="hljs-keyword">export</span> {
   BITS_FOR_2B,
   BITS_FOR_3B,
   BITS_FOR_4B,
   BatchSpringEasing,
   CSSSpringEasing,
   EaseInOut,
   EaseOut,
   EaseOutIn,
   EasingDurationCache,
   EasingFunctionKeys,
   EasingFunctions,
   EasingOptions,
   FramePtsCache,
   GenerateSpringFrames,
   INFINITE_LOOP_LIMIT,
   LEAD_FOR_1B,
   LEAD_FOR_2B,
   LEAD_FOR_3B,
   LEAD_FOR_4B,
   LEAD_FOR_5B,
   MASK_FOR_1B,
   MASK_FOR_2B,
   MASK_FOR_3B,
   MASK_FOR_4B,
   SpringEasing,
   SpringFrame,
   SpringInFrame,
   SpringInOutFrame,
   SpringOutFrame,
   SpringOutInFrame,
   UTF8_MAX_BYTE_LENGTH,
   asCodePointsArray,
   asCodePointsCallback,
   asCodePointsIterator,
   batchInterpolateComplex,
   batchInterpolateNumber,
   batchInterpolateSequence,
   batchInterpolateString,
   batchInterpolateUsingIndex,
   bytesToCodePoint,
   bytesToCodePointFromBuffer,
   codePointAt,
   mod_default <span class="hljs-keyword">as</span> codepointIteratorDefault, <span class="hljs-comment">// &lt;- codepoint-iterator's default export</span>
   getByteLength,
   getIterableStream,
   getLinearSyntax,
   getOptimizedPoints,
   getSpringDuration,
   getUnit,
   interpolateComplex,
   interpolateNumber,
   interpolateSequence,
   interpolateString,
   interpolateUsingIndex,
   isNumberLike,
   limit,
   parseEasingParameters,
   ramerDouglasPeucker,
   registerEasingFunction,
   registerEasingFunctions,
   scale,
   SpringEasing <span class="hljs-keyword">as</span> springEasingDefault, <span class="hljs-comment">// &lt;- spring-easing's default export</span>
   squaredSegmentDistance,
   toAnimationFrames,
   toFixed
 };
</code></pre>
</li>
<li><p><strong>Treeshaking:</strong> 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. <a target="_blank" href="https://deno.bundlejs.com/?file&amp;q=spring-easing,react&amp;treeshake=%5B*%5D,%5B*%5D&amp;minify=false">https://deno.bundlejs.com/?file&amp;q=spring-easing,react&amp;treeshake=[*],[*]&amp;minify=false</a></p>
<pre><code class="lang-typescript"> <span class="hljs-comment">// ...</span>
 <span class="hljs-keyword">export</span> {
   BatchSpringEasing,
   CSSSpringEasing,
   EaseInOut,
   EaseOut,
   EaseOutIn,
   EasingDurationCache,
   EasingFunctionKeys,
   EasingFunctions,
   EasingOptions,
   FramePtsCache,
   GenerateSpringFrames,
   INFINITE_LOOP_LIMIT,
   SpringEasing,
   SpringFrame,
   SpringInFrame,
   SpringInOutFrame,
   SpringOutFrame,
   SpringOutInFrame,
   batchInterpolateComplex,
   batchInterpolateNumber,
   batchInterpolateSequence,
   batchInterpolateString,
   batchInterpolateUsingIndex,
   getLinearSyntax,
   getOptimizedPoints,
   getSpringDuration,
   getUnit,
   interpolateComplex,
   interpolateNumber,
   interpolateSequence,
   interpolateString,
   interpolateUsingIndex,
   isNumberLike,
   limit,
   parseEasingParameters,
   ramerDouglasPeucker,
   registerEasingFunction,
   registerEasingFunctions,
   scale,
   squaredSegmentDistance,
   toAnimationFrames,
   toFixed
 };
 <span class="hljs-comment">// ^ React isn't exported at all</span>
</code></pre>
</li>
</ol>
<blockquote>
<p>🚨 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!</p>
</blockquote>
<p>There you have it! A quick and dirty rundown of the latest updates to the way <a target="_blank" href="http://bundlejs.com">bundlejs.com</a> handles CJS and ESM packages.</p>
<p>So, go ahead and take the new system for a spin. Let me know what you think. Take it for a ride 🚗</p>
<p><img src="https://i.imgflip.com/7rwjsv.jpg" alt /></p>
<p>Photo by Marcin Jozwiak: <a target="_blank" href="https://www.pexels.com/photo/abstract-red-and-white-waves-background-subtle-gradients-flow-liquid-lines-design-element-13835514/">https://www.pexels.com/photo/abstract-red-and-white-waves-background-subtle-gradients-flow-liquid-lines-design-element-13835514/</a></p>
]]></content:encoded></item><item><title><![CDATA[Vercel + Support Center = 🚀]]></title><description><![CDATA[Support Center; my first major project at Vercel, it's been a blast working on it and it's now out. 🎉✨
Check it out vercel.com/changelog/support-center

BTW, I joined Vercel 🎉 ~6 months ago...


Well anyway...
Support Center is a modern tool for vi...]]></description><link>https://blog.okikio.dev/vercel-support-center</link><guid isPermaLink="true">https://blog.okikio.dev/vercel-support-center</guid><category><![CDATA[Vercel]]></category><category><![CDATA[UX]]></category><category><![CDATA[webhooks]]></category><category><![CDATA[salesforce webhook]]></category><category><![CDATA[support center]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Tue, 07 Feb 2023 04:31:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1675655857304/d00ce9da-74ab-4904-8c38-cd3b47c5b9c7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Support Center; my first major project at <a target="_blank" href="https://vercel.com"><strong>Vercel</strong></a><strong>,</strong> it's been a blast working on it and it's now out. 🎉✨</p>
<p>Check it out <a target="_blank" href="https://vercel.com/changelog/support-center">vercel.com/changelog/support-center</a></p>
<blockquote>
<p>BTW, I joined <strong>Vercel</strong> 🎉 ~6 months ago...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675656328514/3bcbb1fe-48bd-41b5-9ca0-b0740776eaab.gif" alt="Ta-da gif" /></p>
</blockquote>
<p>Well anyway...</p>
<p>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.</p>
<p>For some background. Before Support Center, customers had to email support before they could get any help 😬, it was clunky and prone to <em>"<strong>**email got sent to spam errors"</strong></em>, and <em>"<strong>**I didn't check my email problems"</strong></em>, 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.</p>
<p>The main hiccup we ran into during development was the excessively complex (and archaic) work of <strong>integrating with Salesforce</strong> and the performance of the support center, of which the solution is <strong>caching</strong> (the bane of all Software Engineers everywhere).</p>
<blockquote>
<p>📚 <strong>Fun fact</strong>: Salesforces uses a SQL-like syntax for querying data that's similar to SQL but yet very far away from SQL, it's called <a target="_blank" href="https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm">SOQL</a>, and working with it can be very frustrating</p>
</blockquote>
<p>The problems boiled down to:</p>
<ol>
<li><p>How do you keep the cache always up to date?</p>
</li>
<li><p>How to keep the support center fast, responsive, and reliable with the most up-to-date info?</p>
</li>
<li><p>How to make working with Salesforce easier?</p>
</li>
</ol>
<p>The solution to the first 2 problems... 🥁 are <strong>webhooks</strong> of course.  </p>
<p>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 🤯.  </p>
<p>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.  </p>
<p>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.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675661452180/09676e71-e3ad-4293-a8bd-17fced1c8473.webp" alt="Preview image of Support Center and a demo support case" class="image--center mx-auto" /></p>
<p>Working on this project was awesome, and I'm grateful for being able to work with such a capable team of designers &amp; developers on this project.  </p>
<p>✌️</p>
]]></content:encoded></item><item><title><![CDATA[Using FormData with Astro]]></title><description><![CDATA[Introduction
Someone recently asked me how to use FormData with Astro, to which I responded I'll create a small document for this (I'll create a pr to add this to the Astro docs a little later).
Getting started
I won't go into detail on what FormData...]]></description><link>https://blog.okikio.dev/using-formdata-with-astro</link><guid isPermaLink="true">https://blog.okikio.dev/using-formdata-with-astro</guid><category><![CDATA[Astro]]></category><category><![CDATA[webdev]]></category><category><![CDATA[backend]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[formdata]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Sun, 18 Sep 2022 05:23:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/R8p564Om44w/upload/v1663391187712/ajPSBWIXz.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>Someone recently asked me how to use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> with <a target="_blank" href="https://docs.astro.build">Astro</a>, to which I responded I'll create a small document for this (I'll create a pr to add this to the <a target="_blank" href="https://docs.astro.build">Astro docs</a> a little later).</p>
<h2 id="heading-getting-started">Getting started</h2>
<p>I won't go into detail on what <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> is, but here is a short summary</p>
<blockquote>
<p><code>FormData</code> [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 <code>fetch()</code> or <code>XMLHttpRequest.send()</code> method. It uses the same format a form would use if the encoding type were set to <code>"multipart/form-data"</code>.</p>
<p>Source: <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">MDN - FormData</a></p>
</blockquote>
<p>Basically instead of using JSON to send data to and from your server, you'd use <code>FormData</code>, except unlike JSON it supports files natively.</p>
<p>For example,</p>
<pre><code class="lang-ts"><span class="hljs-comment">// 1. Create or Get a File </span>
<span class="hljs-comment">/** Creating a File */</span>
<span class="hljs-keyword">const</span> fileContent = <span class="hljs-string">`Text content...Lorem Ipsium`</span>;
<span class="hljs-keyword">const</span> buffer = <span class="hljs-keyword">new</span> TextEncoder().encode(fileContent);
<span class="hljs-keyword">const</span> blob = <span class="hljs-keyword">new</span> Blob([buffer]);
<span class="hljs-keyword">const</span> file = <span class="hljs-keyword">new</span> File([blob], <span class="hljs-string">"text-file.txt"</span>, { <span class="hljs-keyword">type</span>: <span class="hljs-string">"text/plain"</span> });
<span class="hljs-comment">/** OR */</span>
<span class="hljs-comment">/** Getting a File */</span>
<span class="hljs-keyword">const</span> fileInput = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"#files"</span>); <span class="hljs-comment">// &lt;input id="files" type="file" multiple /&gt; </span>
<span class="hljs-keyword">const</span> file = fileInput.files.item(<span class="hljs-number">0</span>);

<span class="hljs-comment">// 2. Create FormData</span>
<span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> FormData();

<span class="hljs-comment">// 3. Add File to FormData through the `file` field</span>
formData.append(<span class="hljs-string">"file"</span>, file); <span class="hljs-comment">// FormData keys are called fields</span>
</code></pre>
<blockquote>
<pre><code class="lang-ts"><span class="hljs-keyword">const</span> file = fileInput.files.item(<span class="hljs-number">0</span>);
</code></pre>
<p>^ <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/files"><code>fileInput.files</code></a> is a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FileList">FileList</a>, which is similar but <strong>not</strong> an array, to work around this you can convert the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FileList">FileList</a> to an array of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/File">File</a>'s using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from">Array.from</a></p>
<p>For our use case, since we're only trying to upload one file, it'd be easier to select the first <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/File">File</a> in the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FileList">FileList</a></p>
<p>Learn more on <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/files">MDN - HTMLInputElement</a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/File">MDN - File</a></p>
<p><strong>Note:</strong> you can also just directly use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader">FileReader</a> instead of using an <code>&lt;input /&gt;</code> element</p>
</blockquote>
<h2 id="heading-usage">Usage</h2>
<p>There are 2 ways to support <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> in <a target="_blank" href="https://docs.astro.build">Astro</a>; the easy and the hard way, I'll show you both.</p>
<blockquote>
<p><strong>Note</strong>: both the easy and hard way require <a target="_blank" href="https://docs.astro.build">Astro</a> to be configured in <a target="_blank" href="https://docs.astro.build/en/guides/server-side-rendering/">server (SSR)</a> mode</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro/config'</span>;

<span class="hljs-comment">// https://astro.build/config</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
 output: <span class="hljs-string">'server'</span>,
});
</code></pre>
</blockquote>
<h3 id="heading-easy-way">Easy Way</h3>
<p>The <strong>easy way</strong> requires you to create a new <code>.ts</code> file that will act as your endpoint, for example, if you wanted a <code>/upload</code> endpoint, you would create a <code>.ts</code> file in <code>src/pages</code>.</p>
<blockquote>
<p>Read <a target="_blank" href="https://docs.astro.build">Astro</a>'s official docs on <a target="_blank" href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes">File Routes</a> to learn more</p>
</blockquote>
<p>Your basic file tree should look like this after creating your endpoint</p>
<pre><code class="lang-plaintext">src/
├─ pages/
│  ├─ upload.ts
│  ├─ index.astro
</code></pre>
<p>Inside your <code>index.astro</code> file follow the example I gave above in <a class="post-section-overview" href="#heading-getting-started">#getting-started</a>, on getting <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> up and running.</p>
<p>Once you've created an instance of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> and populated it with the files you'd like to upload, you then just setup a POST request to that endpoint.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/upload'</span>, {
  method: <span class="hljs-string">'POST'</span>,
  body: formData,
});
<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> res.json();
<span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">JSON</span>.stringify(result));
</code></pre>
<p>From the endpoint side you'd then need to export a post method to handle the POST request being sent,</p>
<blockquote>
<p>Here is where things get complex. I recommend going through <a target="_blank" href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes">Astro's File Routes Docs</a></p>
</blockquote>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { APIContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'astro'</span>;

<span class="hljs-comment">// File routes export a get() function, which gets called to generate the file.</span>
<span class="hljs-comment">// Return an object with `body` to save the file contents in your final build.</span>
<span class="hljs-comment">// If you export a post() function, you can catch post requests, and respond accordingly</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">post</span>(<span class="hljs-params">{ request }: APIContext</span>) </span>{
  <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">await</span> request.formData();
  <span class="hljs-keyword">return</span> {
    body: <span class="hljs-built_in">JSON</span>.stringify({
      fileNames: <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(
        formData.getAll(<span class="hljs-string">'files'</span>).map(<span class="hljs-keyword">async</span> (file: File) =&gt; {
          <span class="hljs-keyword">return</span> {
            webkitRelativePath: file.webkitRelativePath,
            lastModified: file.lastModified,
            name: file.name,
            size: file.size,
            <span class="hljs-keyword">type</span>: file.type,
            buffer: {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">'Buffer'</span>,
              value: <span class="hljs-built_in">Array</span>.from(
                <span class="hljs-keyword">new</span> <span class="hljs-built_in">Int8Array</span>(<span class="hljs-keyword">await</span> file.arrayBuffer()).values()
              ),
            },
          };
        })
      ),
    }),
  };
}
</code></pre>
<p>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.</p>
<p>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.</p>
<p><code>export async function post() { ... }</code> what?! Yeah, I too recently learned that <a target="_blank" href="https://docs.astro.build">Astro</a> supports this out of the box, which is awesome.</p>
<blockquote>
<p>W3Schools cover <a target="_blank" href="https://www.w3schools.com/tags/ref_httpmethods.asp">POST and GET</a> fairly well, take a look at their article if you're not familiar with POST and GET requests</p>
</blockquote>
<p>Let's first talk about the <code>request</code> parameter. As it's name suggests <code>request</code> is an instance of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Request">Request</a> class which includes all the methods that <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Request">Request</a> supports, including a method for transforming said request into <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> you can work with.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">post</span>(<span class="hljs-params">{ request }: APIContext</span>) </span>{
  <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">await</span> request.formData();
  <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">formData</a> you can get all the instances of a specific field (<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> keys are called fields), for example, get all <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/File">File</a>'s in the <code>file</code> field.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">post</span>(<span class="hljs-params">{ request }: APIContext</span>) </span>{
  <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">await</span> request.formData();
  <span class="hljs-keyword">return</span> {
    body: <span class="hljs-built_in">JSON</span>.stringify({
      <span class="hljs-comment">// getAll('file') will return an array of File classes</span>
      fileNames: formData.getAll(<span class="hljs-string">'file'</span>),
    }),
  };
}
</code></pre>
<p>The problem with this solution is that it will return <code>{"fileNames":[{}]}</code> due to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description">JSON.stringify</a> being unable to convert <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/File">File</a> classes to a string</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663304487180/KTbCjXRE4.png" alt="Result of JSON.stringify not being able to handle File classes" class="image--center mx-auto" /></p>
<p>To deal with this formatting issue we need to format the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/File">File</a>'s array properly,</p>
<pre><code class="lang-ts"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">post</span>(<span class="hljs-params">{ request }: APIContext</span>) </span>{
  <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">await</span> request.formData();
  <span class="hljs-keyword">return</span> {
    body: <span class="hljs-built_in">JSON</span>.stringify({
      <span class="hljs-comment">// getAll('files') will return an array of File classes</span>
      fileNames: formData.getAll(<span class="hljs-string">'files'</span>).map(<span class="hljs-keyword">async</span> (file: File) =&gt; {
          <span class="hljs-keyword">return</span> {
            webkitRelativePath: file.webkitRelativePath,
            lastModified: file.lastModified,
            name: file.name,
            size: file.size,
            <span class="hljs-keyword">type</span>: file.type,
            buffer: { <span class="hljs-comment">/* ... */</span> }
          };
        }),
    }),
  };
}
</code></pre>
<p>The last part is converting <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer">ArrayBuffers</a> 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,</p>
<pre><code class="lang-ts"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">post</span>(<span class="hljs-params">{ request }: APIContext</span>) </span>{
  <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">await</span> request.formData();
  <span class="hljs-keyword">return</span> {
    body: <span class="hljs-built_in">JSON</span>.stringify({
      <span class="hljs-comment">// getAll('file') will return an array of File classes</span>
      fileNames: formData.getAll(<span class="hljs-string">'file'</span>).map(<span class="hljs-keyword">async</span> (file: File) =&gt; {
          <span class="hljs-keyword">return</span> {
            <span class="hljs-comment">// ...</span>
            buffer: {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">'Buffer'</span>,
              value: <span class="hljs-built_in">Array</span>.from(
                <span class="hljs-keyword">new</span> <span class="hljs-built_in">Int8Array</span>(
                  <span class="hljs-keyword">await</span> file.arrayBuffer()
                ).values()
              ),
            },
          };
        }),
    }),
  };
}
</code></pre>
<p>That's the <strong>easy way</strong>. Using <a target="_blank" href="https://docs.astro.build">Astro's</a> baked in <a target="_blank" href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes">file routes</a> to act as an endpoint for your <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a>.</p>
<blockquote>
<p>To actually run <a target="_blank" href="https://docs.astro.build">Astro</a> with the <code>/upload</code> endpoint all you need is <code>npm run dev</code></p>
</blockquote>
<p>You can view a demo of the <strong>easy way</strong> on <a target="_blank" href="https://stackblitz.com/edit/github-a2gvve-izjjam?file=README.md,astro.config.mjs,src%2Findex.ts&amp;on=stackblitz">Stackblitz</a>, <a target="_blank" href="https://codesandbox.io/p/github/okikio/astro-form-data-easy-edition/csb-w3tpu0/draft/awesome-mahavira">CodeSandbox</a> and <a target="_blank" href="https://github.com/okikio/astro-form-data-easy-edition">GitHub</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/p/github/okikio/astro-form-data-easy-edition/csb-w3tpu0/draft/awesome-mahavira">https://codesandbox.io/p/github/okikio/astro-form-data-easy-edition/csb-w3tpu0/draft/awesome-mahavira</a></div>
<p> </p>
<h3 id="heading-hard-way">Hard Way</h3>
<p>The <strong>hard way</strong> requires you to use the <a target="_blank" href="https://github.com/expressjs/multer">multer</a> middleware together with <a target="_blank" href="https://expressjs.com">expressjs</a>, in order to make the <a target="_blank" href="https://docs.astro.build/en/guides/integrations-guide/node/">@astrojs/node</a> integration support <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> requests.</p>
<p>The <strong>hard way</strong> mostly builds on the <a class="post-section-overview" href="#heading-easy-way">#easy-way</a>, except instead of a <code>src/pages/upload.ts</code> file, you would instead use a <code>server.mjs</code> file in the root directory to define your endpoints, so, your file structure would look more like this,</p>
<pre><code class="lang-plaintext">src/
├─ pages/
│  ├─ index.astro
server.mjs
</code></pre>
<p>The core of the <strong>hard way</strong> occurs inside <code>server.mjs</code>. <code>server.mjs</code> should look like this by the end of this blog post</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { handler <span class="hljs-keyword">as</span> ssrHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dist/server/entry.mjs'</span>;
<span class="hljs-keyword">import</span> multer <span class="hljs-keyword">from</span> <span class="hljs-string">'multer'</span>;

<span class="hljs-keyword">const</span> app = express();
app.use(express.static(<span class="hljs-string">'dist/client/'</span>));
app.use(ssrHandler);

<span class="hljs-keyword">const</span> upload = multer();
app.post(<span class="hljs-string">'/upload'</span>, upload.array(<span class="hljs-string">'file'</span>), <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-comment">// req.files is an object (String -&gt; Array) where fieldname is the key, and the value is array of files</span>
  <span class="hljs-comment">//</span>
  <span class="hljs-comment">// e.g.</span>
  <span class="hljs-comment">//  req.files['avatar'][0] -&gt; File</span>
  <span class="hljs-comment">//  req.files['gallery'] -&gt; Array</span>
  <span class="hljs-comment">//</span>
  <span class="hljs-comment">// req.body will contain the text fields, if there were any</span>
  <span class="hljs-built_in">console</span>.log(req.files);
  res.json({ fileNames: req.files });
});

app.listen(<span class="hljs-number">8080</span>);
</code></pre>
<p>When you build an <a target="_blank" href="https://docs.astro.build">Astro</a> project in <a target="_blank" href="https://docs.astro.build/en/guides/server-side-rendering/">server (SSR)</a> mode (e.g. <code>npm run build</code>), <a target="_blank" href="https://docs.astro.build">Astro</a> will automatically generate a <code>dist/server/entry.mjs</code> file, it's this file that allows us to build our own custom nodejs server and then run <a target="_blank" href="https://docs.astro.build">Astro</a> off this server.</p>
<p>For this specific use case we are using <a target="_blank" href="https://expressjs.com">express</a> for the server, and to enable <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> support in <a target="_blank" href="https://expressjs.com">express</a> we need the <a target="_blank" href="https://github.com/expressjs/multer">multer</a> middleware, so if you're familiar with <a target="_blank" href="https://expressjs.com">express</a> at all this should look familiar,</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { handler <span class="hljs-keyword">as</span> ssrHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dist/server/entry.mjs'</span>;

<span class="hljs-keyword">const</span> app = express();
app.use(express.static(<span class="hljs-string">'dist/client/'</span>));
app.use(ssrHandler);

<span class="hljs-comment">// ...</span>
app.listen(<span class="hljs-number">8080</span>);
</code></pre>
<p>The <code>ssrHandler</code> enables <a target="_blank" href="https://docs.astro.build">Astro</a> to run on the <a target="_blank" href="https://expressjs.com">express</a> server, for the most part it can be treated like any other <a target="_blank" href="https://expressjs.com">express</a> middleware and ignored.</p>
<blockquote>
<p><strong>Note:</strong> If you're not familiar with the code snippet above, please go through <a target="_blank" href="https://expressjs.com">express' documentation</a>, it'll make the rest of the explanation easier to understand</p>
</blockquote>
<p>The real interesting part is where <a target="_blank" href="https://github.com/expressjs/multer">multer</a> and <a target="_blank" href="https://expressjs.com">express</a> meet.</p>
<p>By using a POST request handler we are able to recieve POST requests made to the <code>/upload</code> endpoint and respond back with the parsed <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> results, but unlike in the <a class="post-section-overview" href="#heading-easy-way">#easy-way</a>, <a target="_blank" href="https://expressjs.com">express</a> is able to handle all the formatting allowing <code>File</code> responses to be as expected.</p>
<pre><code class="lang-ts"><span class="hljs-comment">// ...</span>
<span class="hljs-keyword">import</span> multer <span class="hljs-keyword">from</span> <span class="hljs-string">'multer'</span>;

<span class="hljs-keyword">const</span> app = express();
<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">const</span> upload = multer();
app.post(<span class="hljs-string">'/upload'</span>, upload.array(<span class="hljs-string">'files'</span>), <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-comment">// req.files is an object (String -&gt; Array) where fieldname is the key, and the value is array of files</span>
  <span class="hljs-comment">//</span>
  <span class="hljs-comment">// e.g.</span>
  <span class="hljs-comment">//  req.files['avatar'][0] -&gt; File</span>
  <span class="hljs-comment">//  req.files['gallery'] -&gt; Array</span>
  <span class="hljs-comment">//</span>
  <span class="hljs-comment">// req.body will contain the text fields, if there were any</span>
  <span class="hljs-built_in">console</span>.log(req.files);
  res.json({ fileNames: req.files });
});

app.listen(<span class="hljs-number">8080</span>);
</code></pre>
<p>Response to <a target="_blank" href="https://expressjs.com">express</a> POST request</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1663395285407/YJS2K7xFw.png" alt="Response to express POST request when a button is clicked" class="image--center mx-auto" /></p>
<p>That's the <strong>hard way</strong>. Using <a target="_blank" href="https://docs.astro.build">Astro's</a> <a target="_blank" href="https://docs.astro.build/en/guides/server-side-rendering/">SSR</a> mode together with <a target="_blank" href="https://expressjs.com">express</a> and <a target="_blank" href="https://github.com/expressjs/multer">multer</a> to create the <code>/upload</code> endpoint which supports <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">formData</a>.</p>
<blockquote>
<p>To actually run <a target="_blank" href="https://docs.astro.build">Astro</a> you need to do a bit more than you'd need for the <a class="post-section-overview" href="#heading-easy-way">#easy-way</a></p>
<ol>
<li><p>Install <a target="_blank" href="https://expressjs.com">express</a> and <a target="_blank" href="https://github.com/expressjs/multer">multer</a> -&gt;<code>npm install express multer</code></p>
</li>
<li><p>Build <a target="_blank" href="https://docs.astro.build">Astro</a> <a target="_blank" href="https://docs.astro.build/en/guides/server-side-rendering/">SSR</a> handler -&gt;<code>npm run build</code></p>
</li>
<li><p>Run <code>server.mjs</code> -&gt; <code>node server.mjs</code></p>
</li>
</ol>
<p>The <strong>hard way</strong> may seem easier, but that is due to having done alot of the prep work in the <a class="post-section-overview" href="#heading-easy-way">#easy-way</a>, it is actually more overall work than the easy way.</p>
</blockquote>
<p>You can view a demo of the <strong>hard way</strong> on <a target="_blank" href="https://stackblitz.com/edit/github-a2gvve?file=server.mjs">Stackblitz</a>, <a target="_blank" href="https://codesandbox.io/p/github/okikio/astro-form-data">CodeSandbox</a> and <a target="_blank" href="https://github.com/okikio/astro-form-data">GitHub</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/embed/github/okikio/astro-form-data/main">https://codesandbox.io/embed/github/okikio/astro-form-data/main</a></div>
<p> </p>
<blockquote>
<p><strong><em>Note</em></strong>: If you'd like to save uploaded files just to disk, add this to <code>multer</code></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> server = multer({ dest: <span class="hljs-string">'./public/data/uploads/'</span> })
</code></pre>
<p>That tells <code>multer</code> to automatically save the file to disk, read more on the <a target="_blank" href="https://github.com/expressjs/multer#usage">multer docs</a>  </p>
<p>For Easy Mode here is a link to <a target="_blank" href="https://stackblitz.com/edit/github-a2gvve-izjjam?file=src/pages/cool-profile.ts">Stackblitz</a> on how to save files.</p>
</blockquote>
<h2 id="heading-conclusion">Conclusion</h2>
<p>There are 2 ways of using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> with <a target="_blank" href="https://docs.astro.build">Astro</a>, either the <strong>easy way</strong> or the <strong>hard way</strong>.</p>
<p>The <strong>easy way</strong> is to use <a target="_blank" href="https://docs.astro.build">Astro's</a> baked-in <a target="_blank" href="https://docs.astro.build/en/core-concepts/astro-pages/#file-routes">File Routes</a> to act as an endpoint for your <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a> POST requests.</p>
<p>The <strong>hard way</strong> is to use <a target="_blank" href="https://docs.astro.build">Astro's</a> <a target="_blank" href="https://docs.astro.build/en/guides/server-side-rendering/">SSR</a> mode together with <a target="_blank" href="https://expressjs.com">express</a> and <a target="_blank" href="https://github.com/expressjs/multer">multer</a> to create a <code>/upload</code> endpoint which supports <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData">FormData</a>.</p>
<p>There is no right way, but I will recommend the <strong>easy way</strong> as it is easier and less confusing to work with overall.</p>
<hr />
<p><a target="_blank" href="https://unsplash.com/photos/R8p564Om44w">Photo</a> by <a target="_blank" href="https://unsplash.com/@hitthetrailjack">Caleb Jack (@hitthetrailjack)</a> on <a target="_blank" href="https://unsplash.com/photos/R8p564Om44w">Unsplash</a>.</p>
<p>Also, published on <a target="_blank" href="https://dev.to/okikio/using-formdata-with-astro-5545">dev.to</a>, and <a target="_blank" href="https://hackernoon.com/github-codespaces-vs-gitpod-choosing-the-best-online-code-editor">Hackernoon</a></p>
]]></content:encoded></item><item><title><![CDATA[bundlejs: An online esbuild based bundler & npm bundle size checker]]></title><description><![CDATA[Introduction
bundlejs (pronounced bundle js) is a quick and easy way to treeshake, bundle, minify, and compress (in either gzip or brotli) your typescript, javascript, jsx and npm projects, while receiving the total bundles' file size.
bundlejs aims ...]]></description><link>https://blog.okikio.dev/documenting-an-online-bundler-bundlejs</link><guid isPermaLink="true">https://blog.okikio.dev/documenting-an-online-bundler-bundlejs</guid><category><![CDATA[Web Development]]></category><category><![CDATA[webapps]]></category><category><![CDATA[documentation]]></category><category><![CDATA[THW Web Apps]]></category><category><![CDATA[npm]]></category><category><![CDATA[JSR]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Wed, 18 May 2022 22:18:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1651642112339/5wjwCvhvq.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p><a target="_blank" href="https://bundlejs.com"><strong>bundlejs</strong></a> (pronounced <strong>bundle js</strong>) is a quick and easy way to treeshake, bundle, minify, and compress (in either <a target="_blank" href="https://en.wikipedia.org/wiki/Gzip">gzip</a> or <a target="_blank" href="https://en.wikipedia.org/wiki/Brotli">brotli</a>) your <a target="_blank" href="https://www.typescriptlang.org/">typescript</a>, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript">javascript</a>, <a target="_blank" href="https://reactjs.org/docs/introducing-jsx.html">jsx</a> and <a target="_blank" href="https://www.npmjs.com/">npm</a> projects, while receiving the total bundles' file size.</p>
<p><strong>bundlejs</strong> aims to generate more accurate bundle size estimates by following the same approach that bundlers use:</p>
<ul>
<li><p>Doing all bundling locally</p>
</li>
<li><p>Outputing the treeshaken bundled code</p>
</li>
<li><p>Getting the resulting bundle size</p>
</li>
</ul>
<p>The benefits of using <strong>bundlejs</strong> are:</p>
<ol>
<li><p>It's easier to debug errors</p>
</li>
<li><p>You can verify the resulting bundled code</p>
</li>
<li><p>The ability to configure your bundles</p>
</li>
<li><p>The ability to treeshake bundles</p>
</li>
<li><p>The ability view a visual analysis of bundles</p>
</li>
<li><p>You can bundle offline (so long as the module has been used before)</p>
</li>
<li><p>Supports different types of modules from varying <a class="post-section-overview" href="#heading-cdn-hosts">Content Delivery Networks (CDNs)</a>, e.g. CDNs ranging from deno modules, to npm modules, to random github scripts, etc...</p>
</li>
</ol>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://bundlejs.com">https://bundlejs.com</a></div>
<p> </p>
<p>This blog post is meant to highlight some of the most important changes as well as to give some insight into how <strong>bundlejs</strong> works in the background, and to act as the docs for bundlejs.</p>
<blockquote>
<p>📒<strong>Note</strong>: There will be a follow up article to this one, going into the technical nitty gritty on how <strong>bundlejs</strong> 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.</p>
<p>😅 <a target="_blank" href="https://www.dictionary.com/browse/tldr"><strong>TL;DR</strong></a>: this blog post is rather long, so take a look at the <a target="_blank" href="https://bundlejs.com">bundlejs.com</a> 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.</p>
</blockquote>
<h3 id="heading-quick-feature-run-down">Quick feature run down</h3>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=5FTricK1peg">https://www.youtube.com/watch?v=5FTricK1peg</a></div>
<p> </p>
<blockquote>
<p>This video runs through all the major features of <strong>bundlejs</strong> (there is audio but I don't have a good mic 😅)</p>
</blockquote>
<h2 id="heading-bundling-treeshaking-and-minification">Bundling, Treeshaking, and Minification</h2>
<blockquote>
<ul>
<li><p><strong>Bundling</strong> is the process of efficiently <a target="_blank" href="https://www.dictionary.com/browse/concatenate">concatenating</a> modules together into one file which we call a <a target="_blank" href="https://www.dictionary.com/browse/bundle">bundle</a>.</p>
</li>
<li><p><strong>Treeshaking</strong> is the process of a bundler traversing the modules to be bundled and removing unused code.</p>
</li>
<li><p><strong>Minification</strong> 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...</p>
</li>
</ul>
</blockquote>
<p><strong>bundlejs</strong> uses <a target="_blank" href="https://esbuild.github.io">esbuild</a> and it's incredible ability to bundle, transform, transpile, minify, treeshake and traverse files. Specifically, <strong>bundlejs</strong> uses <a target="_blank" href="https://esbuild.github.io">esbuild-wasm</a> which is able to access a subset of those features, with the key limitations being,</p>
<ol>
<li><p>npm only runs on node, so no package.json or <code>npm install</code> (a-la, a joke about using <a target="_blank" href="https://stackblitz.com/">StackBlitz</a><a target="_blank" href="https://blog.stackblitz.com/posts/introducing-webcontainers/"><code>WebContainers</code></a> to run node on the browser)</p>
</li>
<li><p>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</p>
</li>
<li><p>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 <code>http(s)://...</code> modules, so a different solution is required</p>
</li>
</ol>
<p>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,</p>
<ol>
<li><p><a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/http.ts"><code>HTTP</code> plugin</a> - Fetches and caches modules</p>
</li>
<li><p><a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/cdn.ts"><code>CDN</code> plugin</a> - Redirects npm package imports (sometimes referred to as bare imports) to <a target="_blank" href="https://en.wikipedia.org/wiki/Content_delivery_network">Content Delivery Network (CDN)</a> urls for fetching</p>
</li>
<li><p><a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/external.ts"><code>EXTERNALS</code> plugin</a> - Marks certain imports/exports as modules to exclude from bundling</p>
</li>
<li><p><a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/plugins/alias.ts"><code>ALIAS</code> plugin</a> - Aliases certain imports/exports to modules of a different name</p>
</li>
</ol>
<blockquote>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Content_delivery_network">Content Delivery Networks (CDNs)</a> are a great way to distribute code all over the world at fast speeds. In the context of <strong>bundlejs</strong>, CDNs represent online repositories of code that <strong>bundlejs</strong> can fetch from.</p>
<p>For example, <a target="_blank" href="https://unpkg.com">unpkg.com</a> 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: <code>https://unpkg.com/package-name@version/file.js</code>, a similar thing would apply for <a target="_blank" href="https://cdn.skypack.dev">skypack.dev</a>, <a target="_blank" href="https://cdn.esm.sh">esm.sh</a>, etc...</p>
</blockquote>
<p>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 <a target="_blank" href="https://esbuild.github.io">esbuild-wasm</a> to create javascript bundles.</p>
<details>
<summary>
<p><strong>View the impact of treeshaking on bundle size.</strong></p>
</summary>
<blockquote>
<p>ℹ️ <strong>Info</strong>: This is the impact treeshaking and minifying a bundle has on bundle size,</p>
<p><em>treeshaken</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651902776617/g4kWajhKL.png" alt="Image of a treeshaken bundle" /></p>
<p>vs.</p>
<p><strong><em>non</em></strong>-<em>treeshaken</em>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651902717041/nMhrl23Rh.png" alt="Image of a non-treeshaken bundle" /></p>
</blockquote>
</details>

<h2 id="heading-console">Console</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1646254447271/-0bZdtKpC.png" alt="Image of the bundlejs virtual console, just after esbuild-wasm has been initialized" /></p>
<p>In previous versions of <a target="_blank" href="https://bundlejs.com">bundlejs.com</a> I encouraged devs to use the devtools console for viewing console logs, and for a while I thought it was an <em>ok</em> 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 <a target="_blank" href="https://github.com/hyrious">@hyrious</a>'s <a target="_blank" href="https://hyrious.me/esbuild-repl/?mode=build">esbuild-repl</a> I finally did it 👏.</p>
<details>
<summary>
<p><strong>Console Results</strong></p>
</summary>
<p>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.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647666637464/Ev1-XeW_o.png" alt="Image of bundle results in the console. It shows the bundle time and the compressed/un-compressed bundle size" /></p>
</details>

<details>
<summary>
<p><strong>Fetching Packages</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647667343998/XaRcIjfKc.png" alt="Image of the fetch progress of bundlejs in the virtual console" /></p>
<p>By default the console will display the progress for fetching packages, it

</p><details>
<summary>
<p><strong>Console Errors &amp; Warnings</strong></p>
</summary>
<p>Errors look like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647666904526/x9A81vTTC.png" alt="Image of errors in the bundlejs virtual console" /></p>
<p>Warnings look like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647760262984/WuL1-idVG.png" alt="Image of warnings in the bundlejs virtual console" /></p>
</details>

<details>
<summary>
<p><strong>Console Buttons</strong></p>
</summary>
<p>The consoles were also given buttons to make them easier to navigate, they are these right here</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647667136057/r-RYGjJRC.png" alt="Image of the virtual console buttons" /></p>
<ul>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647667161452/goB6vUpjH.png" alt="Image of the console scroll to top button" />
This is the <strong>scroll to top</strong> 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.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647668907922/khbt0NiM7.png" alt="Image of the console scroll to bottom button" />
This is the <strong>scroll to bottom</strong> button. Basically a get to the bottom as quick as possible button, it

</p><details>
<summary>
<p><strong>Console Extras</strong></p>
</summary>
<p><em><strong>Sticky Console:</strong></em> Sticks console scroll position to the bottom, for new logs. If you scroll <code>~50px</code> away from the bottom this behavior no longer applies, if you scroll back to the bottom the behavior will apply again.</p>
<p><em><strong>Pet-peeve alignment:</strong></em> I get so annoyed when things that can align don

## Input and Output Tabs

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647665286142/xlshCSwhs.png" alt="Image of the input and output tabs, with the config button off to the side" />

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 <code>console</code> is always available.

## Configuration

In <code>v0.2</code> I added support for custom configurations (configs), it supports most of <a target="_blank" href="https://esbuild.github.io/api/#build-api">esbuild's build options</a>, as well as some added options to change the default CDN and the compression algorithm.

&gt; The default config is
&gt; 
&gt; <code>ts
&gt; {
&gt;   "cdn": "https://unpkg.com",
&gt;   "compression": "gzip",
&gt;   "esbuild": {
&gt;       "target": [ "esnext"],
&gt;       "format": "esm",
&gt;       "bundle": true,
&gt;       "minify": true,
&gt;       "treeShaking": true,
&gt;       "platform": "browser"
&gt;   }
&gt; }
&gt;</code>

&gt; When you click the share button, it will also share the custom config you've setup, e.g.
&gt; 
&gt; <code>ts
&gt; {
&gt;   "cdn": "https://unpkg.com",
&gt;   "compression": "lz4",
&gt;   "esbuild": {
&gt;       "target": [ "es2018" ],
&gt;       ...
&gt;   }
&gt; }
&gt;</code>
&gt; 
&gt; The config above would result in this share URL <a target="_blank" href="https://bundlejs.com/?q=@okikio/animate&amp;config=%7B%22compression%22:%22lz4%22,%22esbuild%22:%7B%22target%22:%5B%22es2018%22%5D%7D%7D">bundlejs.com/?q=@okikio/animate&amp;config={"compression":"lz4","esbuild":{"target":["es2018"]}}</a>.
&gt; 
&gt; Notice how <code>cdn</code> is missing from the share URL, that's because <strong>bundlejs</strong> 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.

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

### CDN Hosts

&gt; <a target="_blank" href="https://en.wikipedia.org/wiki/Content_delivery_network">Content Delivery Networks (CDNs)</a> are a great way to distribute code all over the world at fast speeds. In the context of <strong>bundlejs</strong>, CDNs represent online repositories of code that <strong>bundlejs</strong> can fetch from.
&gt; 
&gt; For example, <a target="_blank" href="https://unpkg.com">unpkg.com</a> 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: <code>https://unpkg.com/package-name@version/file.js</code>, a similar thing would apply for <a target="_blank" href="https://cdn.skypack.dev">skypack.dev</a>, <a target="_blank" href="https://cdn.esm.sh">esm.sh</a>, etc...

By default <strong>bundlejs</strong> lets you enter code like this,

<code>ts
export * from "@okikio/animate";</code>

But behind the scenes <strong>bundlejs</strong> auto fetches that specific package from a CDN namely, <a target="_blank" href="https://unpkg.com">unpkg</a>.

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

With later updates <strong>bundlejs</strong> recieved the ability to update the default <code>cdn</code> on a global or local scale.

</p><details>
<summary>
<p><strong>Technical details and more info...</strong></p>
</summary>
<p>You can choose CDNs by,</p>
<ol>
<li><p>(<strong>Global CDN</strong>) Setting the CDN config to a different CDN host, 
e.g.</p>
<pre><code class="lang-ts">{
    <span class="hljs-string">"cdn"</span>: <span class="hljs-string">"https://esm.sh"</span>,
    <span class="hljs-comment">// OR</span>
    <span class="hljs-string">"cdn"</span>: <span class="hljs-string">"skypack"</span>
}
</code></pre>
</li>
<li><p>(<strong>Local CDN</strong>) Using the CDN host as an inline url scheme, e.g.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> { animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"skypack:@okikio/animate"</span>; 
<span class="hljs-comment">//                       ^^^^^^^  https://cdn.skypack.dev/@okikio/animate</span>
</code></pre>
<p>There are a total of 8 supported inline CDN host url schemes:</p>
<ul>
<li><p><code>skypack:react</code> -&gt; <a href="https://cdn.skypack.dev/react">https://cdn.skypack.dev/react</a></p>
</li>
<li><p><code>unpkg:react</code> -&gt; <a href="https://unpkg.com/react">https://unpkg.com/react</a></p>
</li>
<li><p><code>esm.sh:react</code> or <code>esm:react</code> -&gt; <a href="https://esm.sh/react">https://esm.sh/react</a></p>
</li>
<li><p><code>jsr:@oxi/result</code> -&gt; <a href="https://esm.sh/jsr/@oxi/result">https://esm.sh/jsr/@oxi/result</a></p>
</li>
<li><p><code>deno:preact</code> -&gt; <a href="https://deno.land/x/preact">https://deno.land/x/preact</a></p>
</li>
<li><p><code>esm.run:react</code> -&gt; <a href="https://esm.run/react">https://esm.run/react</a></p>
</li>
<li><p><code>github:facebook/react/main/packages/react/index.js</code> -&gt; <a href="https://raw.githubusercontent.com/facebook/react/main/packages/react/index.js">https://raw.githubusercontent.com/facebook/react/main/packages/react/index.js</a></p>
</li>
<li><p><code>jsdelivr:react</code> -&gt; <a href="https://cdn.jsdelivr.net/npm/react">https://cdn.jsdelivr.net/npm/react</a></p>
</li>
<li><p><code>jsdelivr.gh:facebook/react/packages/react-dom/index.js</code> -&gt; <a href="https://cdn.jsdelivr.net/gh/facebook/react/packages/react-dom/index.js">https://cdn.jsdelivr.net/gh/facebook/react/packages/react-dom/index.js</a></p>
</li>
</ul>
</li>
</ol>
<p>After determining the CDN to use, the next step is to determine if the CDN host supports npm style modules, examples of which are <a href="https://unpkg.com">unpkg</a>, <a href="https://skypack.dev">skypack</a>, <a href="https://esm.sh">esm.sh</a>, etc...</p>
<p>The factors involved in determining that a CDN host supports npm style modules are that the CDN hosts supports:</p>
<ol>
<li><p>The CDN supports package versioning through the <code>@version</code> URL tag (e.g. <code>react@18</code>).</p>
</li>
<li><p>The CDN can load a node packages, <code>package.json</code> file.</p>
</li>
</ol>
<blockquote>
<p>📒 <strong>Note</strong>: Without the <code>package.json</code> you can</p></blockquote></details>

<h3 id="heading-compression-algorithms">Compression Algorithms</h3>
<p><strong>bundlejs</strong> offers the options of bundling using:</p>
<ol>
<li><p><code>brotli</code> - results in the smallest bundle size but it's the slowest</p>
</li>
<li><p><code>gzip</code> - results in the 2nd smallest bundle size but it's faster than <code>brotli</code> (<strong>default</strong>)</p>
</li>
<li><p><code>lz4</code> - results in the largest bundle size but it's the fastest bundle algorithm</p>
</li>
</ol>
<blockquote>
<p>📒 <strong>Note</strong>: Each compression algorithm has it's own story.</p>
</blockquote>
<h4 id="heading-the-brotli-problem">The Brotli Problem</h4>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Brotli">brotli</a> 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 <a target="_blank" href="https://twitter.com/lewisl9029/status/1498928788477857794?s=20&amp;t=iUL2EzC810kgGM6tn8nJeg">Lewis Liu</a> on Twitter I was able to use <a target="_blank" href="https://deno.land/x/brotli">deno-brotli</a> to include a WASM version of brotli in <strong>bundlejs</strong>.</p>
<details>
<summary>
<p><strong>Learn the story behind brotli support...</strong></p>
</summary>
<p>No <a href="https://www.urbandictionary.com/define.php?term=Shade">shade</a> to the original creators of <a href="https://github.com/httptoolkit/brotli-wasm">brotli-wasm</a> and <a href="https://github.com/dfrankland/wasm-brotli">wasm-brotli</a> (different packages, similar name) but the way both packages handle WASM forces devs to use webpack (which wasn

<code>deno-brotli</code> does 2 things right, they are,

1. It compresses the huge WASM file required for <code>deno-brotli</code> into an <code>lz4</code> compressed string, which can then be decompressed by <code>lz4</code> 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).

    &gt; For <code>lz4</code> support <strong>bundlejs</strong> is using <a target="_blank" href="https://deno.land/x/lz4">deno-lz4</a>, which also runs via WASM, but the way <a target="_blank" href="https://deno.land/x/lz4">deno-lz4</a> compress itself is slightly different than <code>deno-brotli</code>. I'd highly encourage you to take a look at the source code for <a target="_blank" href="https://deno.land/x/lz4">deno-lz4</a> it's really informative.

2. By having the WASM as a js file you can actually preload the WASM as a js module 🤯

    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647757979258/UHlRGGHHq.gif" alt="The Universe, Tim And Eric, Mind Blown GIF" />


You can read through this tweet thread to learn more,

%[https://twitter.com/okikio_dev/status/1498898909879422977?s=20&amp;t=iUL2EzC810kgGM6tn8nJeg] 

#### Default to Gzip

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

%[https://twitter.com/matthewcp/status/1503704061853450242?s=20&amp;t=CDGJmTts_m2A9H7cyZfmew] 

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

<p>I eventually chose to replace <a target="_blank" href="https://github.com/nodeca/pako">pako</a> with <a target="_blank" href="https://deno.land/x/denoflate">denoflate</a> as the default compression algorithm for gzip, it's a bit faster and smaller than <a target="_blank" href="https://github.com/nodeca/pako">pako</a>.</p>
<h4 id="heading-lz4-gotta-go-fast">LZ4, Gotta Go Fast</h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647759856958/r_R5mjwDu.gif" alt="Sanic The Hedgehob GIF" /></p>
<p>In order to use WASM in a portable way, <a target="_blank" href="https://deno.land/x/brotli">deno-brotli</a> would compress the WASM binary file into a base64 string using <code>lz4</code> as the compression algorithm. I saw the oppertunity to add another compression algorithm, so I used the <code>lz4</code> (<a target="_blank" href="https://deno.land/x/lz4">deno-lz4</a>) implementation used to compress the brotli WASM binary string (see <a class="post-section-overview" href="#heading-the-brotli-problem">#the-brotli-problem</a> for more info.), it was by far the easiest compression algorithm to add to <strong>bundlejs</strong> 🤣.</p>
<h3 id="heading-compression-quality">Compression Quality</h3>
<p>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,</p>
<p>{ "compression": { "type": "brotli", "quality": 11 } }</p>
<blockquote>
<p>You can check out a demo here, <a target="_blank" href="https://bundlejs.com/?config=%7B%22compression%22:%7B%22type%22:%22brotli%22,%22quality%22:11%7D%7D">bundlejs.com/?config={"compression":{"type":"brotli","quality":11}}</a>.</p>
</blockquote>
<h3 id="heading-aliases-and-externals">Aliases and Externals</h3>
<p>Aliases are a way to redirect certain packages to other packages, e.g. redirecting the <code>fs</code> to <code>memfs</code>, because <code>fs</code> isn't supported on the web, etc... This wasn't a direct feature request but I felt it would be a good addition.</p>
<p>Externals are a direct feature request <a target="_blank" href="https://github.com/okikio/bundle/issues/13">issue#13</a>, 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.</p>
<details>
<summary>
<p><strong>More details...</strong></p>
</summary>
<p>You use <code>aliases</code> it like this,</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"alias"</span>: {
    <span class="hljs-attr">"@okikio/animate"</span>: <span class="hljs-string">"@babel/core"</span>
  }
}
</code></pre>
<blockquote>
<p>You can try it out below, <a href="https://bundlejs.com/?config={">bundlejs.com/?config={"alias":{"@okikio/animate":"@babel/core"}}</a>.</p>
</blockquote>
<p>You use <code>externals</code> like this,</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"esbuild"</span>: {
    <span class="hljs-attr">"external"</span>: [<span class="hljs-string">"@okikio/animate"</span>]
  }
}
</code></pre>
<blockquote>
<p>You can try it out below, <a href="https://bundlejs.com/?config={">bundlejs.com/?config={"esbuild":{"external":["@okikio/animate"]}}</a>.</p>
<p>Check out a complex example of using the external config <a href="https://bundlejs.com/?q=@babel/core&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]}}">bundlejs.com/?q=@babel/core&amp;config={"esbuild":{"external":[...]}}</a></p>
</blockquote>
</details>

<blockquote>
<p>No one else can understand my pain...I'm adding more feature to <strong>bundlejs</strong> as I'm writing this blog post, so it's just getting longer and longer and longer, etc.... 😅</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1647756027794/eQrEWtghF.gif" alt="My Pain Is Greater Than Yours, Naruto GIF" /></p>
</blockquote>
<h3 id="heading-esbuild-config-options">Esbuild Config Options</h3>
<p><a target="_blank" href="https://esbuild.github.io/api">esbuild</a> 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.</p>
<p>The supported esbuild build options are</p>
<details>
<summary>
<p><strong>Simple options</strong></p>
</summary>
<ul>
<li><a href="https://esbuild.github.io/api/#bundle">Bundle</a></li>
<li><a href="https://esbuild.github.io/api/#define">Define</a></li>
<li><a href="https://esbuild.github.io/api/#external">External</a></li>
<li><a href="https://esbuild.github.io/api/#format">Format</a></li>
<li><a href="https://esbuild.github.io/api/#inject">Inject</a></li>
<li><a href="https://esbuild.github.io/api/#loader">Loader</a></li>
<li><a href="https://esbuild.github.io/api/#minify">Minify</a></li>
<li><a href="https://esbuild.github.io/api/#platform">Platform</a></li>
<li><a href="https://esbuild.github.io/api/#sourcemap">Sourcemap</a></li>
<li><a href="https://esbuild.github.io/api/#splitting">Splitting</a></li>
<li><a href="https://esbuild.github.io/api/#target">Target</a></li>
</ul>
</details>

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

## Editor Buttons + Extra Features...

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648183770577/_qysqdHxe.png" alt="Image of editor button panel with all the editor buttons listed" />

The editor buttons add extra functionality to the editor, they enable easy access to common editor tasks.

The current list of editor buttons are:

</p><details>
<summary>
<p><strong>Editor panel toggle</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648184042461/zh3vS9318.png" alt="Image of the editor panel toggle" /></p>
<p>Toggles on or off the editor buttons, leaving more space for the code editor. It looks like this when the editor buttons are hidden,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648184286699/36J0CLSen.png" alt="Image of hidden editor panel" /></p>
</details>

<details>
<summary>
<p><strong>Clear editor button</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648184715635/nFH3SAfpf.png" alt="Image of clear editor button" /></p>
<p>Clears the editor of all its contents.</p>
</details>

<details>
<summary>
<p><strong>Format code button</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648185108166/nkRNmKE7W.png" alt="Image of format editor button" /></p>
<p>Cleans up any messy code it finds. It uses <a href="https://dprint.dev/">dprint</a> to format the input and output editor code, but falls back to <a href="https://microsoft.github.io/monaco-editor/">monaco-editors</a> baked in formatter for the config editor.</p>
</details>

<details>
<summary>
<p><strong>Reset code button</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648184816235/jn_hBY6bs.png" alt="Image of reset editor button" /></p>
<p>Resets the editor to it

</p><details>
<summary>
<p><strong>Copy code button</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648185342561/zTkH_egb-.png" alt="Image of copy code button" /></p>
<p>Copies the editors code, it

</p><details>
<summary>
<p><strong>Wrap code button</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648185762593/5LGzxyhOc.png" alt="Image of wrap around editor button toggle" /></p>
<p>Toggles between <code>wrapped</code> and <code>unwrapped</code> 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.</p>
<p>This is how <code>wrapped</code> code looks,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648185907414/qeDXYH4OS.png" alt="image.png" /></p>
<p>This is how <code>unwrapped</code> code looks,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648186040929/QlQLV-yM5.png" alt="image.png" /></p>
</details>

<details>
<summary>
<p><strong>Bonus features</strong></p>
</summary>
<blockquote>
<p><strong>Bonus</strong>: You can access monaco

## Drag Handles...Interactive Fun

%[https://youtu.be/AMt01tFstik] 

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.

&gt; <strong>bundlejs</strong> 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 <strong>bundlejs</strong> while on a mobile or touch enabled device.

## JSX Support

JSX is now officially supported in <strong>bundlejs</strong> 🎉.

<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651644967823/BO--VVjCH.png" alt="Image of the preact JSX demo on bundlejs" />

&gt; Ignore the red error lines, for some reason the <a target="_blank" href="https://github.com/microsoft/monaco-editor">monaco code editor</a> doesn't want to work well with JSX 😅

To use JSX you need to set the <code>jsxFactory</code> and the <code>jsxFragment</code> config options according to the JSX based framework you are using.

e.g. for <a target="_blank" href="https://preactjs.com/">Preact</a> the following config would be used:

{ "esbuild": { "jsxFactory": "h", "jsxFragment": "Fragment" } }

&gt; Try out the <a target="_blank" href="https://bundlejs.com/?q=(import)preact,(import)preact/hooks&amp;treeshake=%5B%7Bh,Fragment%7D%5D,%5B%7BuseState%7D%5D&amp;share=KYDwDg9gTgLgBAE2AMwIYFcA29noHYDGMAlhHnAMIT4zBQAUA3nAQBbGYJTDkC+AlHEYAoAJAEyAZ3gBtCTQA0cScBhUaAXTgBeOOhUBlGKlr0ADPwDcYiXmlxUCBDrj1B2gHzLV6vDHr0xO5exHAA1HAAjFY2UvCS6ABGMFCoRC5uOl4qatR+AUFZcKEAtFExYtww6FDk9GKiADweDU0IxABuLJiokpLaAETyfnQDLaITTYnoMDBkcGQUmMQEANbajAnJqUS8HiWNAPTTs2Tjk41g3B6MwzC8R1fA5xONJ3Pki8trG44Ie2Eju8zq0ju0Oi9GuDur1+kM8rQoCUALbAPqoADmwDGt3YnG4eAeh3B5yO4xivCAA&amp;config=%7Besbuild:%7BjsxFactory:h,jsxFragment:Fragment%7D%7D">preact demo</a>

## Sharing Bundle Sessions

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.

&gt; *sessions are the specific state of <strong>bundlejs</strong> 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.

</p><details>
  <summary>
<p><strong>Technical details...</strong></p>
  </summary>
  <p>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.</p>
  <p>A <a href="https://tinyurl.com/3fptk973">sample session url</a> is,</p>
  <pre><code class="lang-ts">/?q=(import)@okikio/emitter,(import)@okikio/<span class="hljs-built_in">animate</span>,(import)@okikio/<span class="hljs-built_in">animate</span>,(import)@okikio/<span class="hljs-built_in">animate</span>,(import)@okikio/<span class="hljs-built_in">animate</span>,@okikio/<span class="hljs-built_in">animate</span>,@okikio/<span class="hljs-built_in">animate</span>,@okikio/<span class="hljs-built_in">animate</span>,@okikio/<span class="hljs-built_in">animate</span>&amp;treeshake=[T],[{ <span class="hljs-built_in">animate</span> }],[{ <span class="hljs-built_in">animate</span> as B }],[<em> as TR],[{ <span class="hljs-built_in">type</span> <span class="hljs-built_in">animate</span> }],[</em>],[{ <span class="hljs-built_in">animate</span> as A }],[<em> as PR],[{ <span class="hljs-built_in">animate</span> }]&amp;<span class="hljs-built_in">text</span>=<span class="hljs-string">"export </span></em> as PR18 from \"@okikio/<span class="hljs-built_in">animate</span>\<span class="hljs-string">";\nexport { animate as animate2 } from \"</span>@okikio/<span class="hljs-built_in">animate</span>\<span class="hljs-string">";"</span>&amp;share=MYewdgziA2CmB00QHMAUAiAwiG6CUQA&amp;config={<span class="hljs-string">"cdn"</span>:<span class="hljs-string">"skypack"</span>,<span class="hljs-string">"compression"</span>:<span class="hljs-string">"brotli"</span>,<span class="hljs-string">"esbuild"</span>:{<span class="hljs-string">"format"</span>:<span class="hljs-string">"cjs"</span>,<span class="hljs-string">"minify"</span>:<span class="hljs-literal">false</span>,<span class="hljs-string">"treeShaking"</span>:<span class="hljs-literal">false</span>}}&amp;bundle
  </code></pre>
  <p>The resulting input code of this bundle session url is this,</p>
  <pre><code class="lang-ts"><span class="hljs-comment">// Click Build for the Bundled, Minified &amp; Compressed package size</span>
  <span class="hljs-keyword">import</span> T <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/emitter"</span>;
  <span class="hljs-keyword">import</span> { animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">import</span> { animate <span class="hljs-keyword">as</span> B } <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">import</span> <em> <span class="hljs-keyword">as</span> TR <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">import</span> { type animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">export</span> </em> <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">export</span> { animate <span class="hljs-keyword">as</span> A } <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">export</span> <em> <span class="hljs-keyword">as</span> PR <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">export</span> { animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Cool"</span>)
  <span class="hljs-keyword">export</span> </em> <span class="hljs-keyword">as</span> PR18 <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  <span class="hljs-keyword">export</span> { animate <span class="hljs-keyword">as</span> animate2 } <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
  </code></pre>
  <p>with a config of,</p>
  <pre><code class="lang-json">{
      <span class="hljs-attr">"cdn"</span>: <span class="hljs-string">"skypack"</span>,
      <span class="hljs-attr">"compression"</span>: <span class="hljs-string">"brotli"</span>,
      <span class="hljs-attr">"esbuild"</span>: {
          <span class="hljs-attr">"target"</span>: [<span class="hljs-string">"esnext"</span>],
          <span class="hljs-attr">"format"</span>: <span class="hljs-string">"cjs"</span>,
          <span class="hljs-attr">"bundle"</span>: <span class="hljs-literal">true</span>,
          <span class="hljs-attr">"minify"</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-attr">"treeShaking"</span>: <span class="hljs-literal">false</span>,
          <span class="hljs-attr">"platform"</span>: <span class="hljs-string">"browser"</span>
      }
  }
  </code></pre>
</details>

<p>The URL breakdown is,</p>
<pre><code class="lang-ts">/?
q=(<span class="hljs-keyword">import</span>)<span class="hljs-meta">@okikio</span>/emitter,(<span class="hljs-keyword">import</span>)<span class="hljs-meta">@okikio</span>/animate,(<span class="hljs-keyword">import</span>)<span class="hljs-meta">@okikio</span>/animate,(<span class="hljs-keyword">import</span>)<span class="hljs-meta">@okikio</span>/animate,(<span class="hljs-keyword">import</span>)<span class="hljs-meta">@okikio</span>/animate,<span class="hljs-meta">@okikio</span>/animate,<span class="hljs-meta">@okikio</span>/animate,<span class="hljs-meta">@okikio</span>/animate,<span class="hljs-meta">@okikio</span>/animate&amp;
treeshake=[T],[{ animate }],[{ animate <span class="hljs-keyword">as</span> B }],[* <span class="hljs-keyword">as</span> TR],[{ <span class="hljs-keyword">type</span> animate }],[*],[{ animate <span class="hljs-keyword">as</span> A }],[* <span class="hljs-keyword">as</span> PR],[{ animate }]&amp;
text=<span class="hljs-string">"export * as PR18 from \"@okikio/animate\";\nexport { animate as animate2 } from \"@okikio/animate\";"</span>&amp;
share=MYewdgziA2CmB00QHMAUAiAwiG6CUQA&amp;
config={<span class="hljs-string">"cdn"</span>:<span class="hljs-string">"skypack"</span>,<span class="hljs-string">"compression"</span>:<span class="hljs-string">"brotli"</span>,<span class="hljs-string">"esbuild"</span>:{<span class="hljs-string">"format"</span>:<span class="hljs-string">"cjs"</span>,<span class="hljs-string">"minify"</span>:<span class="hljs-literal">false</span>,<span class="hljs-string">"treeShaking"</span>:<span class="hljs-literal">false</span>}}&amp;
bundle
</code></pre>
<ul>
<li><p><code>q</code> or <code>query</code> represents the module, e.g. <code>react</code>, <code>vue</code>, etc...</p>
<p>  You can add <code>(import)</code> in-front of a specific module to make it an import instead of an export</p>
</li>
<li><p><code>treeshake</code> represents the export/imports to treeshake.</p>
<p>  The treeshake syntax allows for specifying multiple exports per package, through this syntax</p>
<pre><code class="lang-ts">  <span class="hljs-string">"[{ x,y,z }],[*],[* as X],[{ type xyz }]"</span> 
  <span class="hljs-comment">// to</span>
  <span class="hljs-keyword">export</span> { x, y, z } <span class="hljs-keyword">from</span> <span class="hljs-string">"..."</span>;
  <span class="hljs-keyword">export</span> * <span class="hljs-keyword">from</span> <span class="hljs-string">"..."</span>;
  <span class="hljs-keyword">export</span> * <span class="hljs-keyword">as</span> X <span class="hljs-keyword">from</span> <span class="hljs-string">"..."</span>;
  <span class="hljs-keyword">export</span> { <span class="hljs-keyword">type</span> xyz } <span class="hljs-keyword">from</span> <span class="hljs-string">"..."</span>;
</code></pre>
<p>  The square brackets represent seperate packages, and everything inside the squarebrackets, are the exported methods, types, etc...</p>
</li>
<li><p><code>text</code> represents the input code as a string (it's used for short input code)</p>
</li>
<li><p><code>share</code> represents compressed string version of the input code (it's used for large input code)</p>
</li>
<li><p><code>config</code> represents the bundle configuration to use when building the bundle</p>
</li>
<li><p><code>bundle</code> tells <strong>bundlejs</strong> 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 <code>&amp;bundle</code> to the end of the url yourself.</p>
</li>
</ul>
<p>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.</p>
<h2 id="heading-bundle-analysis">Bundle Analysis</h2>
<p><strong>bundlejs</strong> can analyze and visually represent bundles as easy to navigate and easy to understand charts.</p>
<p>Using a port of <a target="_blank" href="https://github.com/btd/esbuild-visualizer">esbuild-visualizer</a> and <a target="_blank" href="https://github.com/btd/rollup-plugin-visualizer">rollup-plugin-visualizer</a> by <a target="_blank" href="https://twitter.com/bardadymchik">@bardadymchik</a> I added the ability to visualize bundles, this feature comes from a feature request by <a target="_blank" href="https://github.com/atomiks">@atomiks</a> on <a target="_blank" href="https://github.com/okikio/bundle/issues/22#:~:text=Further">issue#22</a>, the issue is still open you can make suggestions to improve this feature.</p>
<p>The bundle analysis charts are displayed right under the editor, like so,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652418797353/5hjxPIx2k.png" alt="Image of the bundle analysis panel under the bundlejs code editor" /></p>
<p>The charts displayed comes in 3 distinct flavours:</p>
<details>
<summary>
<p><strong>Treemap Chart</strong></p>
</summary>
<p>Treemap charts are the most memorable form of bundle analysis chart, the inspiration behind this chart is <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">webpack-bundle-analyzer</a>. <a href="https://github.com/webpack-contrib/webpack-bundle-analyzer">webpack-bundle-analyzer</a> is the <a href="https://www.dictionary.com/browse/progenitor">progenitor</a> of bundle analyzers, and a great inspiration to the approach <strong>bundlejs</strong> took to creating charts.</p>
<p>Treemap charts, </p>
<blockquote>
<ol>
<li>Help you realize what

<details>
<summary>
<p><strong>Network Chart</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652421280569/vLX3JE2ea.png" alt="Image of bundlejs

&lt;details&gt;
&lt;summary&gt;
&lt;p&gt;&lt;strong&gt;Sunburst Chart&lt;/strong&gt;&lt;/p&gt;
&lt;/summary&gt;
&lt;p&gt;&lt;img src=" />{ 
  <span class="hljs-attr">"compression"</span>: <span class="hljs-string">"gzip"</span> 
}

</p><p>will use gzip compression for the charts, resulting in,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652421047276/662E5qBrP.png" alt="Image of a generated treemap chart with gzip compression on bundlejs" /></p>
</details></li></ol></blockquote>
</details>

<h2 id="heading-analytics">Analytics</h2>
<p>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 <a target="_blank" href="https://umami.is">umami</a> as a privacy preserving, cookieless, open source, Google Analytics alternative, to which the analytics are public for anyone to view.</p>
<p></p><details><p></p>
<p><summary></summary></p>
<p></p><p><strong>Extra detail...</strong></p>
<p></p>
<p></p><p>For <strong>bundlejs</strong> a self-hosted version of <a href="https://umami.is">umami</a> 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 <a href="https://dev.to/jakobbouchard/setting-up-umami-with-vercel-and-supabase-3a73">Setting up Umami with Vercel and Supabase</a> by <a href="https://dev.to/jakobbouchard">Jakob Bouchard</a>, was a great help.</p><p></p>
<p></p><p>The analytics are publicly available, check them out at, <a href="https://analytics.bundlejs.com/share/bPZELB4V/bundle">https://analytics.bundlejs.com/share/bPZELB4V/bundle</a></p><p></p>
<p></p><p>Or click the page visit counter </p><p></p>
<p></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651899811300/_ycEW51EC.png" alt="Image of the page visit counter" /></p><p></p>
<p></p><blockquote><p></p>
<p></p><p>📒<strong>Note</strong>: <strong>bundlejs</strong> is still using a page view counter, the view counter is powered by <a href="https://countapi.xyz/">countapi</a> (to the best of my knowledge countapi is now deprecated, however, the servers for the project are still up and running so I</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://dev.to/jakobbouchard/setting-up-umami-with-vercel-and-supabase-3a73">https://dev.to/jakobbouchard/setting-up-umami-with-vercel-and-supabase-3a73</a></div>
<p> </p>
<h2 id="heading-discussions-and-support">Discussions and Support</h2>
<p>To encourage discussion, give support and to gain feedback, I added a comment section to <strong>bundlejs</strong>, I used <a target="_blank" href="https://github.com/giscus/giscus">giscus</a> for this.</p>
<p>Initialy, when I created the <strong>bundlejs</strong> project I also created a <a target="_blank" href="https://github.com/okikio/bundle/discussions">Github Discussion</a> 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 <strong>bundlejs</strong>. The problem is that no one really uses <a target="_blank" href="https://github.com/okikio/bundle/discussions">Github Discussions</a>, so I to integrated it right into the website itself via <a target="_blank" href="https://github.com/giscus/giscus">giscus</a>, this was so new users can easily interact with others, get support from me, and leave me feedback.</p>
<p></p><details><p></p>
<p><summary></summary></p>
<p></p><p><strong>Technical details...</strong></p>
<p></p>
<p></p><p><a href="https://github.com/giscus/giscus">giscus</a> is an open-source comments system powered by <a href="https://docs.github.com/en/discussions">GitHub Discussions</a>, it lets visitors leave comments and reactions on your website via GitHub! It was heavily inspired by <a href="https://github.com/utterance/utterances">utterances</a>.</p><p></p>
<p></p><p>For <strong>bundlejs</strong> I</p>
<p>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 <strong>bundlejs</strong>, I'll go through them and try to integrate your ideas into <strong>bundlejs</strong>.</p>
<h2 id="heading-security-and-performance">Security and Performance</h2>
<p>Security and performance are critical quality areas for <strong>bundlejs</strong>. In order to bundle modules together, <strong>bundlejs</strong> has to fetch multiple sets of modules from all over the internet, while ensuring that malicious actors don't get involved, and that <a target="_blank" href="https://esbuild.github.io">esbuild-wasm</a> isn't taken advantage of to crash other devices.</p>
<blockquote>
<p>Some <em>really</em>...<strong>really</strong> large modules can take up to <code>4+ GB</code> of memory to be bundled properly by <code>esbuild-wasm</code>.</p>
</blockquote>
<p>The security criterias I set for <strong>bundlejs</strong> were:</p>
<ol>
<li><p>Don't leak personal user info.</p>
</li>
<li><p>Don't go through a central server, e.g. the ability to get node modules from various sources</p>
</li>
<li><p>Ensure people always know what packages they are bundling</p>
</li>
<li><p>Ensure people can't use <strong>bundlejs</strong> to maliciously slowdown browsers</p>
</li>
</ol>
<p>To ensure I met the security criterias set,</p>
<ol>
<li><p>I use strict Content Security Policies (CSP) for <strong>bundlejs</strong>, ensuring no unintended 3rd party can get involved.</p>
</li>
<li><p>I self-host as many of the 3rd party scripts I can. By enclosing the number of hands involved in <a target="_blank" href="https://bundlejs.com">bundlejs.com</a> I reduce the chance that personal information is leaked.</p>
</li>
<li><p>I use sandboxing techniques e.g. <code>Web Workers</code> and <code>Shared Workers</code> to ensure the main thread runs at a smooth <code>60fps</code> while avoiding access to the DOM.</p>
<blockquote>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Web Workers</a> and <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker">Shared Workers</a> 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.</p>
<p>Most of the uses of Workers were <code>Shared Workers</code>. <code>Shared Workers</code> reduce the performance impact of multiple instances of the <strong>bundlejs</strong> site/web app running on the same device.</p>
</blockquote>
</li>
<li><p>To reduce the chance of <strong>bundlejs</strong> 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.</p>
</li>
</ol>
<p>The current browser landscape for <code>Shared Worker</code> support is spotty at best.</p>
<p>The support table looks like this,</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Browser</td><td>Shared Workers</td><td><a target="_blank" href="https://web.dev/module-workers/">Module Workers</a></td></tr>
</thead>
<tbody>
<tr>
<td>Firefox</td><td>Yes</td><td>No</td></tr>
<tr>
<td>Chrome</td><td>Yes</td><td>Yes</td></tr>
<tr>
<td>Safari</td><td><a target="_blank" href="https://twitter.com/webkit/status/1506787526937235464">Yes*</a></td><td>Yes</td></tr>
<tr>
<td>* Support is currently experimental, but should be coming in later versions</td><td></td></tr>
</tbody>
</table>
</div><blockquote>
<p><a target="_blank" href="https://web.dev/module-workers/">Module Workers</a> are esmodules that run in Workers.</p>
<p>📒 <strong>Note</strong>: I built a <code>Shared Worker</code> polyfill <a target="_blank" href="https://github.com/okikio/sharedworker">@okikio/sharedworker</a>. It's a small but simple polyfill that falls back to a regular <code>Web Worker</code> if <code>Shared Workers</code> are not supported. You can use it while waiting for the next version of Safari to support <code>Shared Workers</code>, or while supporting older versions of Safari.</p>
<p>⚠️ <strong>Warning</strong>: The <a target="_blank" href="https://github.com/okikio/sharedworker">Shared Worker polyfill</a> 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 <strong>bundlejs</strong> handles module workers in the <a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts">bundlejs source code</a>, you may also wish to view the <a target="_blank" href="https://github.com/withastro/astro-repl/blob/main/src/utils/WebWorker.ts">astro-repl source code</a> to see how it handles module workers.</p>
</blockquote>
<p>Most of the other security policies are passive in nature, e.g.</p>
<ul>
<li><p><strong>bundlejs</strong> only bundling on page load if the URL has <code>?bundle</code> in it.</p>
</li>
<li><p><strong>bundlejs</strong> enforcing <code>https://</code> for all requests, including for iframes, etc...</p>
</li>
<li><p>Only have properly vetted CDN hosts for <strong>bundlejs</strong> by default.</p>
</li>
<li><p>etc...</p>
</li>
</ul>
<h2 id="heading-tips-and-tricks">Tips and Tricks</h2>
<blockquote>
<p><strong>Top tier tip</strong>, follow me (<a target="_blank" href="https://twitter.com/okikio_dev">@okikio_dev</a>) and <strong>bundlejs</strong> (<a target="_blank" href="https://twitter.com/jsbundle">@jsbundle</a>) on twitter; <a target="_blank" href="https://www.urbandictionary.com/define.php?term=Shameless%20Plug">shameless plug</a> 🤣.</p>
<p>I do post announcments and updates on these accounts, as well as small tips and tricks that help in making the most use of <strong>bundlejs</strong>.</p>
</blockquote>
<ul>
<li><p>When bundling packages that also export CSS and other external files, <a target="_blank" href="https://bundlejs.com">bundlejs.com</a> 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.</p>
</li>
<li><p>Treeshaking is available, but not all CDNs support access to each packages <code>package.json</code> so there might be slight package version conflicts. The only verified CDN with access to the package.json is https://unpkg.com. 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. https://deno.land or https://raw.githubusercontent.com.</p>
</li>
<li><p>Check the full devtools console for error messages and warnings, if you are having trouble debuging an issue in <strong>bundlejs</strong>, or even better yet enable <code>esbuilds</code> verbose logging when trying to debug issues, e.g.</p>
<pre><code class="lang-ts">  {
      <span class="hljs-string">"esbuild"</span>: {
          <span class="hljs-string">"logLevel"</span>: <span class="hljs-string">"verbose"</span>
      }
  }
</code></pre>
<p>  Check out a <a target="_blank" href="https://bundlejs.com/?config=%7B%22esbuild%22:%7B%22logLevel%22:%22verbose%22%7D%7D">demo</a>.</p>
</li>
<li><p>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 <code>cdn</code> config option. I recommend using custom protocols instead of the <code>cdn</code> config option when trying to debug issues with a CDN:</p>
<p>  e.g.</p>
<pre><code class="lang-ts">  {
      <span class="hljs-string">"cdn"</span>: <span class="hljs-string">"unpkg"</span>
  }
</code></pre>
<p>  or</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">export</span> * <span class="hljs-keyword">from</span> <span class="hljs-string">"unpkg:typescript"</span>;
</code></pre>
<p>  Try using custom protocols to solve this <a target="_blank" href="https://bundlejs.com/?q=@babel/core&amp;config=%7B%22cdn%22:%22esm.run%22%7D">example issue</a> on <strong>bundlejs</strong>.</p>
</li>
<li><p>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,</p>
<pre><code class="lang-ts">  <span class="hljs-keyword">export</span> * <span class="hljs-keyword">from</span> <span class="hljs-string">"skypack:solid-dismiss"</span>;
  <span class="hljs-comment">// and</span>
  <span class="hljs-keyword">export</span> { <span class="hljs-keyword">default</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"skypack:solid-dismiss"</span>;
</code></pre>
</li>
</ul>
<blockquote>
<p>If you have a tip and trick you would like to share, post a comment below, or send me a <a target="_blank" href="https://twitter.com/okikio_dev">tweet</a>!</p>
</blockquote>
<h2 id="heading-contribute">Contribute</h2>
<p>The codebase is currently quite disorganized so, I'd suggest direct messaging me on <a target="_blank" href="https://twitter.com/okikio_dev">Twitter</a> or starting a <a target="_blank" href="https://github.com/okikio/bundle/discussions">GitHub Discussion</a> to discuss ways to contribute.</p>
<p>There is a lot of stuff happening on the <strong>bundlejs</strong> 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.</p>
<p>You can use a pre-made Gitpod dev environment to quickly get started with the project or to contribute quick changes to the project.</p>
<p><a target="_blank" href="https://gitpod.io/#https://github.com/okikio/bundle/blob/main/README.md"><img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open In Gitpod" /></a></p>
<p>If you love the project, I'd welcome if you'd spread the word, my goal is to make <strong>bundlejs</strong> a viable alternative/replacement for <a target="_blank" href="https://bundlephobia.com/">bundlephobia</a> 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.</p>
<p>Last note, <strong>bundlejs</strong> is now on <a target="_blank" href="https://opencollective.com/bundle">OpenCollective</a>, so if you'd like to contribute to it financially, it'd be appreciated.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p><a target="_blank" href="https://bundlejs.com"><strong>bundlejs</strong></a>, a quick and easy way to treeshake, bundle, minify, and compress (in either <a target="_blank" href="https://en.wikipedia.org/wiki/Gzip">gzip</a> or <a target="_blank" href="https://en.wikipedia.org/wiki/Brotli">brotli</a>) your <a target="_blank" href="https://www.typescriptlang.org/">typescript</a>, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript">javascript</a>, <a target="_blank" href="https://reactjs.org/docs/introducing-jsx.html">jsx</a> and <a target="_blank" href="https://www.npmjs.com/">npm</a> projects, while receiving the total bundles' file size.</p>
<p><strong>bundlejs</strong> aims to generate more accurate bundle size estimates by following the same approach that bundlers use:</p>
<ul>
<li><p>Doing all bundling locally</p>
</li>
<li><p>Outputing the treeshaken bundled code</p>
</li>
<li><p>Getting the resulting bundle size</p>
</li>
</ul>
<p>The benefits of using <strong>bundlejs</strong> are:</p>
<ol>
<li><p>It's easier to debug errors</p>
</li>
<li><p>You can verify the resulting bundled code</p>
</li>
<li><p>The ability to configure your bundles</p>
</li>
<li><p>The ability to treeshake bundles</p>
</li>
<li><p>The ability to view a visual analysis of bundles</p>
</li>
<li><p>You can bundle offline (so long as the module has been used before)</p>
</li>
<li><p>Supports different types of modules from varying <a class="post-section-overview" href="#heading-cdn-hosts">Content Delivery Networks (CDNs)</a>, e.g. CDNs ranging from deno modules, to npm modules, to random github scripts, etc...</p>
</li>
</ol>
<p>The next time you need to bundle a project or you need to know the bundle size of a project, give <a target="_blank" href="https://bundlejs.com">bundlejs.com</a> a try.</p>
<blockquote>
<p>📒<strong>Note</strong>: There will be a follow up article to this one, going into the technical nitty gritty on how <strong>bundlejs</strong> 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.</p>
</blockquote>
<hr />
<p>Photo by <a target="_blank" href="https://okiki.dev/">Okiki Ojo</a>, you can find the image on <a target="_blank" href="https://www.dropbox.com/sh/dmu48xw2dbfiyui/AAD-LGqX_zwpZYgyIgYpCelba?dl=0">Dropbox</a>.</p>
<p>Originally published on <a target="_blank" href="https://blog.okikio.dev/documenting-an-online-bundler-bundlejs">blog.okikio.dev</a></p>
<p>Also, published on <a target="_blank" href="https://hackernoon.com/bundlejs-an-online-esbuild-based-bundler">Hackernoon</a> and <a target="_blank" href="https://dev.to/okikio/documenting-an-online-esbuild-based-bundler-bundlejs-4m52">dev.to</a></p>
</details></blockquote></details></blockquote></details></details></details></details></details></details></li></ul></details></details>]]></content:encoded></item><item><title><![CDATA[@okikio/sharedworker]]></title><description><![CDATA[For bundlejs.com, and astro.build/play, I found that I needed a way to use SharedWorkers reliably on all browsers, so, I decided to make a miniature script that would act as a wrapper around the SharedWorker class, by default it would try to create a...]]></description><link>https://blog.okikio.dev/sharedworker</link><guid isPermaLink="true">https://blog.okikio.dev/sharedworker</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Browsers]]></category><category><![CDATA[Javascript library]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Fri, 15 Oct 2021 22:49:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1634335117596/Pkg3dHCzE.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For <a target="_blank" href="https://bundlejs.com">bundlejs.com</a>, and <a target="_blank" href="https://astro.build/play">astro.build/play</a>, I found that I needed a way to use <code>SharedWorkers</code> reliably on all browsers, so, I decided to make a <a target="_blank" href="https://gist.github.com/okikio/6809cfc0cdbf1df4c0573addaaf7e259">miniature script</a> that would act as a wrapper around the <code>SharedWorker</code> class, by default it would try to create a <code>SharedWorker</code> but if unssuported it would otherwise switch to normal web workers this makes, <code>SharedWorkers</code> a type of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement">progressive enhancement</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://bundlejs.com">https://bundlejs.com</a></div>
<p>When I realized that a polyfill/ponyfill doesn't exist for <code>SharedWorkers</code> 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 <a target="_blank" href="https://github.com/okikio/sharedworker#okikiosharedworker">@okikio/sharedworker</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/okikio/sharedworker#okikiosharedworker">https://github.com/okikio/sharedworker#okikiosharedworker</a></div>
<h2 id="heading-usage">Usage</h2>
<p><code>@okikio/sharedworker</code> is a small mostly spec. compliant polyfill/ponyfill for <code>SharedWorkers</code>, it acts as a drop in replacement for normal <code>Workers</code>, and supports a similar API surface that matches normal <code>Workers</code>.</p>
<p>You use it like this,</p>
<p><code>shared-worker.js</code></p>
<pre><code class="lang-ts"><span class="hljs-comment">/* 
 * All variables and values outside the `start(...)` function are shared between all pages, this behavior can cause unexpected bugs if you're not careful
 */</span>
<span class="hljs-keyword">const</span> start = <span class="hljs-function">(<span class="hljs-params">port</span>) =&gt;</span> {
    <span class="hljs-comment">// All your normal Worker and SharedWorker stuff should just work</span>
    <span class="hljs-comment">// With no more setup </span>

    <span class="hljs-comment">/** 
     * All variables and values inside the `start(...)` function are isolated to each page, and will be allocated seperately per page. 
     */</span>
    port.onmessage = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (data == <span class="hljs-string">"Hey"</span>)
            port.postMessage(<span class="hljs-string">"Hello, from the SharedWorker."</span>); 
    };
};

self.onconnect = <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
    <span class="hljs-keyword">let</span> [port] = e.ports;
    start(port);
};

<span class="hljs-comment">// This is the fallback, just in case the browser doesn't support SharedWorkers</span>
<span class="hljs-keyword">if</span> (<span class="hljs-string">"SharedWorkerGlobalScope"</span> <span class="hljs-keyword">in</span> self) 
    start(self);
</code></pre>
<p><code>main.js</code></p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> SharedWorker <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/sharedworker"</span>;

<span class="hljs-keyword">const</span> sharedworker = <span class="hljs-keyword">new</span> SharedWorker(<span class="hljs-keyword">new</span> URL(<span class="hljs-string">"shared-worker.js"</span>, <span class="hljs-keyword">import</span>.meta.url));
sharedworker.onmessage = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(data); <span class="hljs-comment">//= Hello, from SharedWorker</span>
};

sharedworker.postMessage(<span class="hljs-string">"Hey"</span>);
</code></pre>
<p>In the cases of <a target="_blank" href="https://bundlejs.com">bundlejs.com</a> and <a target="_blank" href="https://astro.build/play">astro.build/play</a>, <code>@okikio/sharedworker</code> was used for <a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/workers/esbuild.ts">esbuild</a> as well as the <a target="_blank" href="https://microsoft.github.io/monaco-editor/">monaco-editors</a> <a target="_blank" href="https://github.com/snowpackjs/astro-repl/blob/main/src/editor/workers/editor.ts">editor</a> and <a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/workers/typescript.ts">typescript</a> workers. <code>@okikio/sharedworker</code> was used as a separate <a target="_blank" href="https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts">utility file</a> for easy access.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://astro.build">https://astro.build</a></div>
<h2 id="heading-limitation">Limitation</h2>
<p>The major limitation with <code>@okikio/sharedworker</code> is that on browsers that don't natively support <code>SharedWorker</code>, you can't use <code>@okikio/sharedworker</code> as an across tab communication tool. But for everything else it's feature parity and spec. compliance should be great.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>So, will you use it? Tell me below, or say Hi on <a target="_blank" href="https://twitter.com/okikio_dev/status/1449083583684124679?s=20">twitter</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/okikio_dev/status/1449083583684124679?s=20">https://twitter.com/okikio_dev/status/1449083583684124679?s=20</a></div>
<hr />
<p><a target="_blank" href="https://unsplash.com/photos/_j9PDVy-WYg">Image</a> from <a target="_blank" href="https://unsplash.com/@tengyart">Tengyart</a> on <a target="_blank" href="https://unsplash.com/">Unsplash</a>. </p>
]]></content:encoded></item><item><title><![CDATA[Major updates for bundlejs.com v0.0.3]]></title><description><![CDATA[https://twitter.com/okikio_dev/status/1439825148769619972?s=20
I released major updates for https://bundlejs.com, they include 

add copy, wrap, format & reset btns
add PWA screenshots & sharing to PWA
detect offline/online mode
faster preloading & p...]]></description><link>https://blog.okikio.dev/major-updates-for-bundlejscom-v003</link><guid isPermaLink="true">https://blog.okikio.dev/major-updates-for-bundlejscom-v003</guid><category><![CDATA[bundling]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[ES6]]></category><category><![CDATA[npm]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Mon, 20 Sep 2021 05:46:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1632116644074/fRLvr4xPr.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/okikio_dev/status/1439825148769619972?s=20">https://twitter.com/okikio_dev/status/1439825148769619972?s=20</a></div>
<p>I released major updates for https://bundlejs.com, they include </p>
<ul>
<li>add copy, wrap, format &amp; reset btns</li>
<li>add PWA screenshots &amp; sharing to PWA</li>
<li>detect offline/online mode</li>
<li>faster preloading &amp; prefetching</li>
<li>prefetch workers</li>
<li>preload monaco.min.js for faster perf</li>
<li>and more....</li>
</ul>
<p>You can read more about bundlejs.com on the Github repo, </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/okikio/bundle#readme">https://github.com/okikio/bundle#readme</a></div>
]]></content:encoded></item><item><title><![CDATA[Github Codespaces vs. Gitpod: Choosing the Best Online Code Editor]]></title><description><![CDATA[Gitpod and Github Codespaces are both Visual Studio Code 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.

Note: Github Codes...]]></description><link>https://blog.okikio.dev/github-codespaces-vs-gitpod-choosing-the-best-online-code-editor</link><guid isPermaLink="true">https://blog.okikio.dev/github-codespaces-vs-gitpod-choosing-the-best-online-code-editor</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Visual Studio Code]]></category><category><![CDATA[IDEs]]></category><category><![CDATA[Docker]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Thu, 02 Sep 2021 13:44:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1630085777659/u98hmmCen.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.gitpod.io/">Gitpod</a> and <a target="_blank" href="https://github.com/features/codespaces">Github Codespaces</a> are both <a target="_blank" href="https://code.visualstudio.com/">Visual Studio Code</a> 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.</p>
<blockquote>
<p><em><strong>Note</strong>: 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 <a target="_blank" href="https://github.com/features/codespaces/signup">beta</a>.</em></p>
<p><em>If you have trouble signing up for Codespaces as a team or organisation the process is to email <a target="_blank" href="mailto:codespaces@github.com">codespaces@github.com</a></em></p>
</blockquote>
<p>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.</p>
<h2 id="heading-why-online-ides">Why online IDEs?</h2>
<p>I have more experience with <a target="_blank" href="http://gitpod.io">Gitpod</a>, as it's been around for quite a bit longer than <a target="_blank" href="https://github.com/features/codespaces">Github Codespaces</a>, however, my experience with online code editors and IDEs is longer than that. </p>
<p>My first experience with an online code editor was <a target="_blank" href="https://aws.amazon.com/cloud9/">Cloud9</a> 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 <a target="_blank" href="https://www.eclipse.org/che/">Eclipse Che</a>, <a target="_blank" href="https://developers.redhat.com/developer-sandbox/ide">Red Hat CodeReady Workspaces</a>, <a target="_blank" href="https://codeanywhere.com/">Codeanywhere</a> to <a target="_blank" href="https://stackblitz.com/">StackBlitz</a>, out of all the alternatives the most competent of them was <a target="_blank" href="http://gitpod.io">Gitpod</a>.</p>
<h2 id="heading-the-online-vscode-revolution">The online vscode revolution</h2>
<p>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, 🤷‍♂️. </p>
<h2 id="heading-gitpod-vs-codespaces">Gitpod vs. Codespaces</h2>
<p>Gitpod themselves already made an article about their benefits over Github Codespaces, you can find it here <a target="_blank" href="https://www.gitpod.io/gitpod-vs-github-codespaces">gitpod.io/gitpod-vs-github-codespaces</a>. 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.</p>
<p>The first point Gitpod makes is that it's <a target="_blank" href="https://www.gitpod.io/gitpod-vs-github-codespaces#:~:text=25%20Sep%202020.-,Ready%20in%20a%20flash,-Gitpod%20removes%20long">"Ready in a flash"</a></p>
<blockquote>
<p>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.</p>
</blockquote>
<p>This is technically correct, at least to a certain point. Gitpod's actual building process takes <em>slightly</em> 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 <a target="_blank" href="https://dev.to/lostintangent/10-awesome-things-you-can-do-with-github-dev-5fm7">github.dev</a>.</p>
<blockquote>
<p><em><strong>For your information:</strong> <a target="_blank" href="https://dev.to/lostintangent/10-awesome-things-you-can-do-with-github-dev-5fm7">github.dev</a> 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</em></p>
</blockquote>
<p>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.</p>
<p>Another point that Gitpod makes is that it has <a target="_blank" href="https://www.gitpod.io/gitpod-vs-github-codespaces#:~:text=Gitpod%2C%20GitHub%20Codespaces.-,3x%20more%20power,-By%20leveraging%20cloud">"3x more power"</a>,</p>
<blockquote>
<p>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.</p>
</blockquote>
<p>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. </p>
<blockquote>
<p><em><strong>Note:</strong> After a discussion with <a target="_blank" href="https://twitter.com/GeoffreyHuntley">@GeoffreyHuntley</a> from <a target="_blank" href="https://twitter.com/gitpod">@gitpod</a> I have confirmed that <strong>“[they] currently provision machines with 64gb of memory and 16vCPUs and workspaces timeshare the resources in a burstable manner. There are upper limitations to resource consumption on a per workspace basis”</strong>. Gitpod is working on sharing more information on their server side config in the coming months.</em></p>
</blockquote>
<p>As of August 30, 2021, Gitpod has 8 plans*, 2 of which are self-hosted options. They are,</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Plan</td><td>Price (Per User/Month)</td><td>Features</td></tr>
</thead>
<tbody>
<tr>
<td>Open Source</td><td>Free</td><td>50 hours/month + Private &amp; Public Repos</td></tr>
<tr>
<td><a target="_blank" href="https://www.gitpod.io/docs/professional-open-source">Professional Open Source</a></td><td>Free</td><td>No Time Limit + Private &amp; Public Repos</td></tr>
<tr>
<td>Personal</td><td>$9</td><td>100 hours/month + 4 Parallel Workspaces + 30min Timeout</td></tr>
<tr>
<td>Professional</td><td>$25</td><td>All in Personal + 8 Parallel Workspaces + Unlimited Hours + Teams</td></tr>
<tr>
<td>Unleashed</td><td>$39</td><td>All in Professional + 16 Parallel Workspaces + 1hr Timeout + 3hr Timeout boost</td></tr>
<tr>
<td><a target="_blank" href="https://www.gitpod.io/pricing">Student</a></td><td>$9</td><td>All in Unleashed, but for “For those still learning the ropes”</td></tr>
<tr>
<td><a target="_blank" href="https://www.gitpod.io/self-hosted">Self Hosted Open Source</a></td><td>Free</td><td>10 Registered Users + Public &amp; Private Repos + GitLab, GitHub and Bitbucket + Unlimited Prebuilds + Shared Workspaces + Snapshots + Admin Dashboard</td></tr>
<tr>
<td><a target="_blank" href="https://www.gitpod.io/self-hosted">Self Hosted Professional</a></td><td>Free</td><td>Everything in <a target="_blank" href="https://www.gitpod.io/self-hosted">Self Hosted Open Source</a> + Starts with the 11th user</td></tr>
</tbody>
</table>
</div><p>For Github Codespaces, the pricing* is, </p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Product</td><td>SKU</td><td>Unit of measure</td><td>Price</td></tr>
</thead>
<tbody>
<tr>
<td>Codespaces Compute</td><td>2 core</td><td>1 hour</td><td>$0.18</td></tr>
<tr>
<td></td><td>4 core</td><td>1 hour</td><td>$0.36</td></tr>
<tr>
<td></td><td>8 core</td><td>1 hour</td><td>$0.72</td></tr>
<tr>
<td></td><td>16 core</td><td>1 hour</td><td>$1.44</td></tr>
<tr>
<td></td><td>32 core</td><td>1 hour</td><td>$2.88</td></tr>
<tr>
<td>Codespaces Storage</td><td>Storage</td><td>1 GB-month</td><td>$0.07</td></tr>
</tbody>
</table>
</div><p>*Both services have more details included in their pricing scheme, I recommend going through those for detailed and up-to-date information.</p>
<blockquote>
<p><em><strong>Note:</strong> 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.</em></p>
<p>Sources: <a target="_blank" href="https://www.gitpod.io/gitpod-vs-github-codespaces">Gitpod</a>, <a target="_blank" href="https://docs.github.com/en/billing/managing-billing-for-github-codespaces/about-billing-for-codespaces#codespaces-pricing">GitHub Codespaces</a>.</p>
</blockquote>
<p>In the <a target="_blank" href="https://www.gitpod.io/gitpod-vs-github-codespaces#:~:text=MULTI-IDE%20SUPPORT-,GitHub,-Codespaces">final point</a> 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. </p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Gitpod</td><td>Github Codespaces</td><td>Details</td></tr>
</thead>
<tbody>
<tr>
<td>PRICING (HOSTED)</td><td>Free for Open-Source</td><td>$$$</td><td>Again, this is iffy, as it makes conclusions about Gitpod's prices being cheaper, that isn't quite accurate, and is rather misleading</td></tr>
<tr>
<td>LICENSE</td><td>Open Source</td><td>Proprietary</td><td>This is where Gitpod gets a win, their code is actually <a target="_blank" href="https://github.com/gitpod-io/gitpod">open source</a>, in-fact their extensions store uses the open-source <a target="_blank" href="https://www.eclipse.org/community/eclipse_newsletter/2020/march/1.php">OpenVSX extension store</a>, however, the OpenVSX store ends up being both a benefit and a detriment.</td></tr>
<tr>
<td>GITHUB INTEGRATION</td><td>Yes</td><td>Yes</td><td>Gitpod has good support for Github, but Codespaces has better integration. Gitpod requires an <a target="_blank" href="https://www.gitpod.io/docs/getting-started">Open in Gitpod link</a>, the <a target="_blank" href="https://www.gitpod.io/docs/browser-extension/">Gitpod extension</a>, or the <a target="_blank" href="https://www.gitpod.io/docs/browser-bookmarklet">bookmarklet</a>, but Github Codespaces just works right out of the gate, click on any green code dropdown on Github and it will just open Codespaces.</td></tr>
<tr>
<td>GITLAB INTEGRATION</td><td>Yes</td><td>No</td><td>Accurate. Gitpod is natively integrated into GitLab and gives each user an “Open in Gitpod” button, similar to what Codespaces does for Github.</td></tr>
<tr>
<td>BITBUCKET INTEGRATION</td><td>Yes</td><td>No</td><td>Accurate</td></tr>
<tr>
<td>SELF-HOST ON GCP</td><td>Yes</td><td>No</td><td>Accurate</td></tr>
<tr>
<td>SELF-HOST ON AWS</td><td>Yes</td><td>No</td><td>Accurate</td></tr>
<tr>
<td>SELF-HOST ON KUBERNETES</td><td>Yes</td><td>No</td><td>Accurate</td></tr>
<tr>
<td>PREBUILDS</td><td>Yes</td><td>No</td><td>Accurate</td></tr>
<tr>
<td>SNAPSHOTS</td><td>Yes</td><td>No</td><td>As far as I know 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.</td></tr>
<tr>
<td>VS CODE EXTENSIONS</td><td>Yes*</td><td>Yes</td><td>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, Gitpod (back when it was incubated within TypeFox) created the <a target="_blank" href="https://open-vsx.org/">Open VSX store</a> (including the <a target="_blank" href="https://theia-ide.org/">Theia IDE</a>) and gifted it to the <a target="_blank" href="https://www.eclipse.org/community/eclipse_newsletter/2020/march/1.php">Eclipse Foundation</a>, an open-source alternative, the problem is that a bunch of extensions are missing from the OpenVSX store, ranging from <a target="_blank" href="https://copilot.github.com/">Github Copilot</a> to <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsliveshare">Live Share</a> and even some open-source extensions that you would expect to be available, however, devs can now automatically <a target="_blank" href="https://github.com/open-vsx/publish-extensions#how-to-add-an-extension">deploy their extensions to the OpenVSX</a> with little effort. In this case, I think Github Codespaces has the better extension store, as it directly uses the same proprietary extension store that the local installation of VS Code would use.</td></tr>
<tr>
<td>IPAD SUPPORT</td><td>Yes</td><td>Yes</td><td>Accurate</td></tr>
<tr>
<td>VIRTUAL DESKTOP (VNC)</td><td>Yes</td><td>Yes</td><td>Accurate</td></tr>
<tr>
<td>MULTI-IDE SUPPORT</td><td>Yes*</td><td>No</td><td>This is accurate. Gitpod allows you to change the base of their service from VS Code to <a target="_blank" href="https://theia-ide.org/">Theia</a> (a deprecated fully open-source variant of VS Code), <a target="_blank" href="https://www.gitpod.io/docs/integrations/jetbrains">JetBrains</a>, and <a target="_blank" href="https://www.reddit.com/r/emacs/comments/pi0mvg/ann_lspmode_800_released/">E-macs</a>. Personally I have only used the VS Code IDE, so I can’t speak of the experience using the other IDE’s but the option is available for use.</td></tr>
</tbody>
</table>
</div><h2 id="heading-workspace-setup">Workspace Setup</h2>
<p>Both Gitpod and Github Codespaces have config files based on Docker that configures your whole env. On Gitpod their config system uses a <code>.gitpod.yml</code> file which stores your workspace config info and a <code>.gitpod.Dockerfile</code> file which sets up a docker image that you can use to run your workspace. By default, Gitpod uses a <a target="_blank" href="https://github.com/gitpod-io/workspace-images/blob/master/full/Dockerfile">standard docker image</a> 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.</p>
<p>The <code>.gitpod.yml</code> files store basic configuration information, ranging from open ports to post-install scripts. Your basic <code>.gitpod.yml</code> files look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># Commands to start on workspace startup</span>
<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">init:</span> <span class="hljs-string">yarn</span> <span class="hljs-string">install</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">yarn</span> <span class="hljs-string">build</span>
<span class="hljs-comment"># Ports to expose on workspace startup</span>
<span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8000</span>
    <span class="hljs-attr">onOpen:</span> <span class="hljs-string">open-preview</span>
</code></pre>
<p>For most of the projects I use Gitpod for, I set up a <code>.gitpod.yml</code> file like this</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># .gitpod.yml</span>
<span class="hljs-attr">image:</span>
  <span class="hljs-attr">file:</span> <span class="hljs-string">.gitpod.Dockerfile</span>

<span class="hljs-comment"># List the ports you want to expose and what to do when they are served. See https://www.gitpod.io/docs/43_config_ports/</span>
<span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">3000</span>
    <span class="hljs-attr">onOpen:</span> <span class="hljs-string">open-preview</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">3001</span>
    <span class="hljs-attr">onOpen:</span> <span class="hljs-string">ignore</span>

<span class="hljs-attr">github:</span>
  <span class="hljs-attr">prebuilds:</span>
    <span class="hljs-comment"># enable for the master/default branch (defaults to true)</span>
    <span class="hljs-attr">master:</span> <span class="hljs-literal">true</span>
    <span class="hljs-comment"># enable for all branches in this repo (defaults to false)</span>
    <span class="hljs-attr">branches:</span> <span class="hljs-literal">true</span>
    <span class="hljs-comment"># enable for pull requests coming from this repo (defaults to true)</span>
    <span class="hljs-attr">pullRequests:</span> <span class="hljs-literal">true</span>
    <span class="hljs-comment"># enable for pull requests coming from forks (defaults to false)</span>
    <span class="hljs-attr">pullRequestsFromForks:</span> <span class="hljs-literal">true</span>
    <span class="hljs-comment"># add a "Review in Gitpod" button as a comment to pull requests (defaults to true)</span>
    <span class="hljs-attr">addComment:</span> <span class="hljs-literal">true</span>
    <span class="hljs-comment"># add a "Review in Gitpod" button to pull requests (defaults to false)</span>
    <span class="hljs-attr">addBadge:</span> <span class="hljs-literal">false</span>
    <span class="hljs-comment"># add a label once the prebuild is ready to pull requests (defaults to false)</span>
    <span class="hljs-attr">addLabel:</span> <span class="hljs-string">prebuilt-in-gitpod</span>

<span class="hljs-comment"># List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/44_config_start_tasks/</span>
<span class="hljs-attr">tasks:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">init:</span> <span class="hljs-string">&gt;
      npm install -g pnpm &amp;&amp;
      pnpm install -g ultra-runner &amp;&amp;
      pnpm install
</span>    <span class="hljs-attr">command:</span> <span class="hljs-string">&gt;
      npm install -g pnpm &amp;&amp;
      pnpm install -g ultra-runner &amp;&amp;
      pnpm build</span>
</code></pre>
<p>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 <a target="_blank" href="https://www.gitpod.io/docs/prebuilds">docs for Gitpod prebuilds</a> to learn more.</p>
<p>However, where things get interesting is in the tasks section. The <code>init</code> task is run once on workspace startup, and the <code>command</code> task is run on workspace startup, and then on every workspace restart. </p>
<p>The real problem is that the <code>init</code> task even though it runs on startup, doesn't store the environmental variable of nor does it link 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 that is supplied. If no docker image is specified in the gitpod.yml file then the standard "workspace-full" image is used instead. I recommend reading through <a target="_blank" href="https://www.gitpod.io/docs/config-start-tasks#execution-order">Gitpod's docs</a> on the matter. </p>
<blockquote>
<p><em><strong>Note</strong>: From what I have been told by <a target="_blank" href="https://twitter.com/GeoffreyHuntley">@GeoffreyHuntley</a> from <a target="_blank" href="https://twitter.com/gitpod">@gitpod</a> <strong>“[It’s] recommend [that] you build your own docker image and install any software you need as part of building your docker image once your project complexity merits it. In addition to this [they] are also making changes to implement full-workspace backups which will snapshot the entire environment removing this friction”</strong>.</em></p>
</blockquote>
<p>The <code>.gitpod.Dockerfile</code>, 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 here, except for, maybe <a class="post-section-overview" href="#vnc">VNC</a> purposes, and even then, the <a target="_blank" href="https://www.gitpod.io/docs/config-docker">docs</a> are very clear.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># .gitpod.Dockerfile</span>
FROM gitpod/workspace-full:latest

<span class="hljs-comment"># Install custom tools, runtime, etc. using apt-get</span>
<span class="hljs-comment"># For example, the command below would install "bastet" - a command-line tetris clone:</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># RUN sudo apt-get -q update &amp;&amp; </span>
<span class="hljs-comment">#     sudo apt-get install -yq bastet &amp;&amp; </span>
<span class="hljs-comment">#     sudo rm -rf /var/lib/apt/lists/*</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># More information: https://www.gitpod.io/docs/config-docker/</span>
</code></pre>
<p>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 <a target="_blank" href="https://code.visualstudio.com/docs/remote/containers">docs on VS Code</a>, the real problem is that the setup for Github Codespaces is very overwhelming.</p>
<p>For Codespaces you need to create a <code>.devcontainer.json</code> file and store it in the <code>.devcontainer/</code> folder. The <code>.devcontainer.json</code> file is a json file that contains the information needed to set up your workspace. The <code>.devcontainer.json</code> file looks like this:</p>
<pre><code class="lang-json"><span class="hljs-comment">// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:</span>
<span class="hljs-comment">// https://github.com/microsoft/vscode-dev-containers/tree/v0.162.0/containers/typescript-node</span>
{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Node.js &amp; TypeScript"</span>,
  <span class="hljs-attr">"build"</span>: {
    <span class="hljs-attr">"dockerfile"</span>: <span class="hljs-string">"Dockerfile"</span>,
    <span class="hljs-comment">// Update 'VARIANT' to pick a Node version: 10, 12, 14</span>
    <span class="hljs-attr">"args"</span>: {
      <span class="hljs-attr">"VARIANT"</span>: <span class="hljs-string">"16"</span>
    }
  },

  <span class="hljs-comment">// Set *default* container specific settings.json values on container create.</span>
  <span class="hljs-attr">"settings"</span>: {
    <span class="hljs-attr">"npm.packageManager"</span>: <span class="hljs-string">"pnpm"</span>
  },

  <span class="hljs-comment">// Add the IDs of extensions you want to be installed when the container is created.</span>
  <span class="hljs-attr">"extensions"</span>: [
    <span class="hljs-string">"bierner.jsdoc-markdown-highlighting"</span>,
    <span class="hljs-string">"yzhang.markdown-all-in-one"</span>,
    <span class="hljs-string">"shd101wyy.markdown-preview-enhanced"</span>,
    <span class="hljs-string">"visualstudioexptteam.vscodeintellicode"</span>
  ],

  <span class="hljs-comment">// Use 'forwardPorts' to make a list of ports inside the container available locally.</span>
  <span class="hljs-attr">"forwardPorts"</span>: [<span class="hljs-number">3000</span>],

  <span class="hljs-comment">// Use 'postCreateCommand' to run commands after the container is created.</span>
  <span class="hljs-attr">"postCreateCommand"</span>: <span class="hljs-string">"pnpm install"</span>,

  <span class="hljs-comment">// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.</span>
  <span class="hljs-attr">"remoteUser"</span>: <span class="hljs-string">"node"</span>
}
</code></pre>
<p>You also need to create a <code>Dockerfile</code> stored inside the <code>.devcontainer/</code> folder. The <code>Dockerfile</code> contains docker info, so any configs, you require for your workspace can be set here, like this:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/typescript-node/.devcontainer/base.Dockerfile</span>

<span class="hljs-comment"># [Choice] Node.js version: 16, 14, 12</span>
ARG VARIANT=<span class="hljs-string">"16-buster"</span>
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-<span class="hljs-variable">${VARIANT}</span>

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

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

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

RUN su node -c <span class="hljs-string">"npm install -g pnpm"</span>
</code></pre>
<blockquote>
<p>This example is based on one of my Github projects <a target="_blank" href="https://github.com/okikio/native/blob/master/.devcontainer/">okikio/native</a>, it's a good starting point for setting up a workspace for your own project.</p>
<p>_<strong>Note</strong>: 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 <code>node_modules</code>) 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._</p>
<p><em><strong>For your information:</strong> The Gitpod team is planning to add support for the Github Codespaces <a target="_blank" href="https://github.com/gitpod-io/roadmap/issues/16"><code>devcontainer.json</code> format</a>, so, in the future, you might be able to easily switch between both Github Codespaces and Gitpod without breaking a sweat.</em></p>
</blockquote>
<h2 id="heading-collaboration">Collaboration</h2>
<p>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 both running workspaces and snapshots (copies of workspaces) with others, and for the other, Github Codespaces allows you to use Live Share to collaborate on the same project.</p>
<p>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.</p>
<h2 id="heading-documentation">Documentation</h2>
<p>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 and includes docs and short screencasts. It's a great way to get started with a cloud based dev environment. Github Codespaces basically assumes you're already very experienced, while Gitpod assumes you're new to the world of online dev environments and slowly builds on top.</p>
<h2 id="heading-vnc">VNC</h2>
<p>Gitpod and Github Codespaces both have <strong>VNC</strong> support. From my experiences, both are about equal in terms of how VNC works, however, Gitpod is easier to set up with VNC. </p>
<blockquote>
<p><strong>Virtual Network Computing (VNC)</strong> 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 <a target="_blank" href="https://en.wikipedia.org/wiki/Virtual_Network_Computing">Wikipedia</a></p>
</blockquote>
<p>A little while back I tried experimenting to see if I could set up <a target="_blank" href="https://webkit.org/">Webkit</a> (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 <em><strong>"cloud"</strong></em>, it too failed, but interestingly enough, for a vastly different reason. </p>
<p>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, <a target="_blank" href="https://playwright.dev/">Playwright</a>. </p>
<blockquote>
<p><a target="_blank" href="https://playwright.dev/">Playwright</a> is an end-to-end framework for testing web apps on multiple browsers,  it's a great tool for testing web apps. </p>
</blockquote>
<p>While trying out Playwright, I found it would only work on Windows (this was because of Webkit &amp; Docker's library issue). </p>
<p>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.</p>
<blockquote>
<p>For <strong>VNC on Gitpod</strong> check out <a target="_blank" href="https://www.gitpod.io/blog/native-ui-with-vnc">gitpod.io/blog/native-ui-with-vnc</a>.</p>
<p>For <strong>VNC on Github Codespaces</strong> check out <a target="_blank" href="https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/desktop-lite.md">github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/desktop-lite.md</a>.</p>
</blockquote>
<h2 id="heading-annoyances">Annoyances</h2>
<p>In Gitpod workspaces every terminal is built from the Gitpod docker image given or if non can be found the Gitpod standard image "<a target="_blank" href="https://github.com/gitpod-io/workspace-images/tree/master/full">workspace-full</a>", 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 nvm (node version manager) to install a new version of node, let's say v16 to replace the Gitpod standard images node, v14, 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. As stated in <a class="post-section-overview" href="#workspace-setup">workspace-setup section</a>, this issue can be mitigated by building your own docker image or by typing into your <code>.gitpod.yml</code> file,</p>
<pre><code class="lang-yml"><span class="hljs-attr">tasks:</span>
  <span class="hljs-comment"># Replace version with version you want installed</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">init:</span> <span class="hljs-string">&gt;
      nvm install v16 &amp;&amp;
      'nvm alias default v16' &gt;&gt; /home/gitpod/.bashrc
      nvm list</span>
</code></pre>
<p>See more on <a target="_blank" href="https://stackoverflow.com/questions/24585261/nvm-keeps-forgetting-node-in-new-terminal-session">Stackoverflow</a></p>
<h2 id="heading-the-internet-problem">The Internet Problem</h2>
<p>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... </p>
<p>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.</p>
<p>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.</p>
<p>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. </p>
<blockquote>
<p><em><strong>Warning</strong>: One thing you should pay attention to is, if you lose internet access for a long time while using Gitpod, you have to pin your workspace in their dashboard, otherwise you may lose your workspace after 14 days, 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. If you can't find a workspace, and the last time said workspace was used was less than 14 days, you will have click the <code>View All Workspaces</code> button</em></p>
</blockquote>
<h2 id="heading-miscellaneous">Miscellaneous</h2>
<p>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 <a target="_blank" href="https://docs.github.com/en/codespaces/developing-in-codespaces/using-codespaces-in-visual-studio-code">Github's docs</a> to learn more. </p>
<p>Gitpod supports something similar, if you install the Gitpod app as a PWA (from what I know, only Edge allows you to forcefully <a target="_blank" href="https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/#pwas-on-microsoft-edge-chromium">install websites as apps</a>), you can then forward the remote ports on the server to your computer's localhost ports, read more about this on <a target="_blank" href="https://www.gitpod.io/blog/local-app">Gitpod's docs</a>.</p>
<blockquote>
<p><em><strong>For your information:</strong> Gitpod is working on support for <a target="_blank" href="https://github.com/gitpod-io/roadmap/issues/19">local VS Code</a>, so, by the time you are reading this article it might already be live.</em></p>
</blockquote>
<p>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 <a target="_blank" href="https://github.com/gitpod-io/gitpod/issues/3733#issuecomment-813917147">issue opened by Gitpod</a> to learn more.</p>
<h2 id="heading-when-to-use-github-codespaces">When to use Github Codespaces?</h2>
<p>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. </p>
<p>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. </p>
<h2 id="heading-when-to-use-gitpod">When to use Gitpod?</h2>
<p>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’s just that Gitpod has a little less Github integration which makes Github Codespaces a nicer experience to use with Github.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>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. </p>
<p>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 <a target="_blank" href="https://copilot.github.com/">Github Copilot</a> in Github Codespaces).</p>
<blockquote>
<p><em><strong>Note:</strong> I actually used Github Codespaces together with <a target="_blank" href="https://copilot.github.com/">Github Copilot</a> 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.</em></p>
<p><em>An alternative to <a target="_blank" href="https://copilot.github.com/">Github Copilot</a> is <a target="_blank" href="https://www.tabnine.com/">Tabnine</a>. I haven’t actually used it, but I feel it’s worth mentioning as an alternative, since it works on Gitpod.</em></p>
</blockquote>
<p>For a more neutral and objective comparison between both services, I suggest reading through <a target="_blank" href="https://www.freecodecamp.org/news/github-codespaces-vs-gitpod-cloud-based-dev-environments/">Nader Dabit's article</a> on FreeCodeCamp comparing the two.</p>
<hr />
<p><strong><em>Update Sept. 09, 2021:</em></strong> Add missing selfhost and opensource Gitpod plans; get quotes from <a target="_blank" href="https://twitter.com/GeoffreyHuntley">@GeoffreyHuntley</a> from <a target="_blank" href="https://twitter.com/gitpod">@gitpod</a> concerning server config; add Tabnine as an alternative to Github Copilot; add possible Docker or <code>.gitpod.yml</code> fix to Gitpod’s terminal node version issue; fix spelling and grammar issues</p>
<hr />
<p>The idea for this article came from a <a target="_blank" href="https://twitter.com/nikmd23/status/1431024698754732033?s=20">tweet</a> by <a target="_blank" href="https://twitter.com/nikmd23">Nik Molnar @nikmd23</a> and <a target="_blank" href="https://twitter.com/meijer_s">Stephan Meijer @meijer_s</a>.</p>
<p>Photo by <a target="_blank" href="https://blog.okiki.dev/">Okiki Ojo</a>, you can find the image on <a target="_blank" href="https://www.dropbox.com/sh/dmu48xw2dbfiyui/AAD-LGqX_zwpZYgyIgYpCelba?dl=0">Dropbox</a>.</p>
<p>Also, published on <a target="_blank" href="https://hackernoon.com/github-codespaces-vs-gitpod-choosing-the-best-online-code-editor">Hackernoon</a> and <a target="_blank" href="https://dev.to/okikio/github-codespaces-vs-gitpod-an-in-depth-look-20lg">dev.to</a></p>
]]></content:encoded></item><item><title><![CDATA[GZIP on the Browser]]></title><description><![CDATA[Out of curiosity I created a small demo detailing how you can implement GZIP in the browser, I used fflate for the GZIP compression, and pretty-bytes to convert bytes into human readable format, the demo is really small but as an example it should be...]]></description><link>https://blog.okikio.dev/gzip-on-the-browser</link><guid isPermaLink="true">https://blog.okikio.dev/gzip-on-the-browser</guid><category><![CDATA[CodePen]]></category><category><![CDATA[webdev]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[snippets]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Tue, 27 Jul 2021 15:50:12 GMT</pubDate><content:encoded><![CDATA[<p>Out of curiosity I created a small demo detailing how you can implement GZIP in the browser, I used <a target="_blank" href="https://npmjs.com/fflate"><code>fflate</code></a> for the GZIP compression, and <a target="_blank" href="https://npmjs.com/pretty-bytes"><code>pretty-bytes</code></a> 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 <a target="_blank" href="https://bundlejs.com">bundlejs.com</a>. 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.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/okikio/pen/ExmQwPM">https://codepen.io/okikio/pen/ExmQwPM</a></div>
<p>Codepen demo:  https://codepen.io/okikio/pen/ExmQwPM</p>
<p>I suggest checking out <a target="_blank" href="bundlejs.com">bundlejs.com</a> it's a specialized for bundling, minifying and compressing js, all locally, right on your browser. </p>
<p>Check out the product hunt page for <a target="_blank" href="bundlejs.com">bundlejs.com</a>,</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.producthunt.com/posts/bundle-6">https://www.producthunt.com/posts/bundle-6</a></div>
]]></content:encoded></item><item><title><![CDATA[PSA: Add dark mode to your sites, or at least let the browsers do it for you]]></title><description><![CDATA[I have a simple message for web developers, start adding the color-scheme property to your webpages.
<!--
  The page supports both dark and light color schemes,
  and the page author prefers dark.
-->
<meta name="color-scheme" content="dark light">

...]]></description><link>https://blog.okikio.dev/psa-add-dark-mode-to-your-sites-or-at-least-let-the-browsers-do-it-for-you</link><guid isPermaLink="true">https://blog.okikio.dev/psa-add-dark-mode-to-your-sites-or-at-least-let-the-browsers-do-it-for-you</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[CSS]]></category><category><![CDATA[user experience]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Design]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Tue, 22 Jun 2021 05:47:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624312666234/v_FaNR_hS.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have a simple message for web developers, start adding the <code>color-scheme</code> property to your webpages.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!--
  The page supports both dark and light color schemes,
  and the page author prefers dark.
--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"color-scheme"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"dark light"</span>&gt;</span>
</code></pre>
<p>or you can even add it using css</p>
<pre><code class="lang-css"><span class="hljs-comment">/*
  The page supports both dark and light color schemes,
  and the page author prefers dark.
*/</span>
<span class="hljs-selector-pseudo">:root</span> {
  <span class="hljs-attribute">color-scheme</span>: dark light;
}
</code></pre>
<p>I absolutely detest sites that <em>"have a dark mode, <strong>BUT DON'T MAKE THE SCROLLBAR DARK!</strong>"</em>, a great example of this is <a target="_blank" href="https://docusaurus.io/">docusaurus</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624330996244/OTPgvYJHa.png" alt="docusaurus-scrollbar" /></p>
<p>Docusarus Why??? </p>
<p><img src="https://media.giphy.com/media/KGbqQQoVN43cY/giphy.gif" alt="burning-eyes" /></p>
<p>I actually tweeted at them asking why the scrollbar isn't dark as well, 🤣</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/okikio_dev/status/1407156597642641408?s=20">https://twitter.com/okikio_dev/status/1407156597642641408?s=20</a></div>
<p>The light mode scrollbar hurts the eyes, and ruins the look of the site, so, for the sake of everyone who has eyes and likes dark mode, please use <code>color-scheme</code>, you can even use it together with your dark mode toggle by using css, for example, one of the sites I made for a client <a target="_blank" href="https://josephojo.com">josephojo.com</a></p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/lLRPLPSppzA">https://youtu.be/lLRPLPSppzA</a></div>
<p>When using the <code>color-scheme</code> property you can turn form elements, webpage background, text color, and scrollbars dark, a more famous example would be, <a target="_blank" href="https://github.com/okikio/native/blob/master/packages/animate/README.md">Github</a>,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624323074476/aXweAKUtW.png" alt="github-scrollbar" /></p>
<p>Notice, how the scrollbar is dark, and doesn't burn the eyes, they're able to do it, by using the meta tag.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1624326485815/AfJIHRJ-M.png" alt="github dark mode" /></p>
<p>For josephojo.com I used the <code>color-scheme</code> css property together with <code>@media (prefers-color-scheme: dark) {}</code> and the <code>.dark</code> class, the final result is </p>
<pre><code class="lang-css"><span class="hljs-selector-tag">html</span> {
    <span class="hljs-attribute">color-scheme</span>: light;
}

<span class="hljs-selector-tag">html</span><span class="hljs-selector-class">.dark</span> {
    <span class="hljs-attribute">color-scheme</span>: dark;
}

<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-color-scheme:</span> dark) {
    <span class="hljs-selector-tag">html</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-attr">[data-theme]</span>) {
        <span class="hljs-attribute">color-scheme</span>: dark;
    }
}
</code></pre>
<p>When creating the site I used <a target="_blank" href="https://tailwindcss.com/">tailwindcss</a>, with the dark mode set to "class", my tailwind config looked like this,</p>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">darkMode</span>: <span class="hljs-string">'class'</span>,
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>For those who haven't used <code>tailwindcss</code> before, it's basically the same as defining a class that when added to the html element will signal that the site is in dark mode.</p>
<p>Or in simpler terms it's,</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"dark"</span>&gt;</span> 
    <span class="hljs-comment">&lt;!-- ... --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<blockquote>
<p><strong>You:</strong>  Wait, but, how did you handle the theme toggle button?</p>
<p><strong>Me:</strong>  I'm glad you asked. </p>
</blockquote>
<p>Now that we have some boilerplate code, all you really need to do, is setup a toggle that will remember our current theme state.</p>
<p>While developing josephojo.com, I found that you have to set your theming system to support the native media theme before anything else, it's generally less painful to the user, that's why I set <code>html:not([data-theme])</code> in the <code>prefers-color-scheme: dark</code> media query,</p>
<pre><code class="lang-css"><span class="hljs-comment">/* ... */</span>
<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">prefers-color-scheme:</span> dark) {
    <span class="hljs-selector-tag">html</span><span class="hljs-selector-pseudo">:not(</span><span class="hljs-selector-attr">[data-theme]</span>) {
        <span class="hljs-attribute">color-scheme</span>: dark;
    }
}
<span class="hljs-comment">/* ... */</span>
</code></pre>
<p><code>html.dark</code> represents the dark theme applied by <code>tailwind</code> and <code>[data-theme]</code> represents the currently applied theme, if <code>data-theme</code> is different from the local storage, then the theme was manually toggled and the page should use the new theme in <code>data-theme</code> as well as update the local storage theme, otherwise, it should use the local storage theme as <code>data-theme</code>, but because <code>data-theme</code> is only applied to the <code>html</code> element after javascript is loaded we can tell our css to use the default dark theme if <code>prefers-color-scheme: dark</code> and the html element doesn't have the <code>data-theme</code> attribute. </p>
<p>The result you get is this,</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/gVm6E186XWo">https://youtu.be/gVm6E186XWo</a></div>
<p>As you saw at the end there, changing the actual browser theme won't permanently change the theme set in local storage, with the idea being, if a user a manually changes the theme they must want to use that theme permanently, otherwise use the system theme.</p>
<p>Here is the code for the theme toggle,</p>
<pre><code class="lang-js"><span class="hljs-comment">// Based on [joshwcomeau.com/gatsby/dark-mode/]</span>
<span class="hljs-keyword">let</span> getSavedTheme = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> theme = <span class="hljs-built_in">window</span>.localStorage.getItem(<span class="hljs-string">"theme"</span>);
  <span class="hljs-comment">// If the user has explicitly chosen light or dark,</span>
  <span class="hljs-comment">// let's use it. Otherwise, this value will be null.</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> theme === <span class="hljs-string">"string"</span>) <span class="hljs-keyword">return</span> theme;

  <span class="hljs-comment">// If they are using a browser/OS that doesn't support</span>
  <span class="hljs-comment">// color themes, let's not do anything.</span>
  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
};

<span class="hljs-keyword">let</span> saveTheme = <span class="hljs-function">(<span class="hljs-params">theme</span>) =&gt;</span> {
  <span class="hljs-comment">// If the user has explicitly chosen light or dark, store the default theme</span>
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> theme === <span class="hljs-string">"string"</span>)
    <span class="hljs-built_in">window</span>.localStorage.setItem(<span class="hljs-string">"theme"</span>, theme);
};

<span class="hljs-keyword">let</span> mediaTheme = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// If they haven't been explicitly set, let's check the media query</span>
  <span class="hljs-keyword">const</span> mql = matchMedia(<span class="hljs-string">"(prefers-color-scheme: dark)"</span>);
  <span class="hljs-keyword">const</span> hasMediaQueryPreference = <span class="hljs-keyword">typeof</span> mql.matches === <span class="hljs-string">"boolean"</span>;
  <span class="hljs-keyword">if</span> (hasMediaQueryPreference) <span class="hljs-keyword">return</span> mql.matches ? <span class="hljs-string">"dark"</span> : <span class="hljs-string">"light"</span>;
};

<span class="hljs-keyword">const</span> html = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">"html"</span>);

<span class="hljs-comment">// Get theme from html tag, if it has a theme or get it from localStorage</span>
<span class="hljs-keyword">let</span> checkCurrentTheme = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> themeAttr = html.getAttribute(<span class="hljs-string">"data-theme"</span>);
  <span class="hljs-keyword">if</span> (themeAttr) <span class="hljs-keyword">return</span> themeAttr;

  <span class="hljs-keyword">return</span> getSavedTheme();
};

<span class="hljs-comment">// Set theme in localStorage, as well as in the html tag</span>
<span class="hljs-keyword">let</span> applyTheme = <span class="hljs-function">(<span class="hljs-params">theme</span>) =&gt;</span> {
  html.className = theme;
  html.setAttribute(<span class="hljs-string">"data-theme"</span>, theme);
};

<span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// if there is a saved theme in local storage use that,</span>
  <span class="hljs-comment">// otherwise use `prefer-color-scheme` to set the theme </span>
  <span class="hljs-keyword">let</span> theme = getSavedTheme();
  <span class="hljs-keyword">if</span> (theme == <span class="hljs-literal">null</span>) theme = mediaTheme();

  <span class="hljs-comment">// set the initial theme</span>
  html.setAttribute(<span class="hljs-string">"data-theme"</span>, theme);
  html.classList.add(theme);

  <span class="hljs-comment">// If a user changes the system/browser/OS theme, update the site theme as well,</span>
  <span class="hljs-comment">// but don't save the change in local storage</span>
  <span class="hljs-built_in">window</span>
    .matchMedia(<span class="hljs-string">"(prefers-color-scheme: dark)"</span>)
    .addEventListener(<span class="hljs-string">"change"</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
      applyTheme(e.matches ? <span class="hljs-string">"dark"</span> : <span class="hljs-string">"light"</span>);
    });

  <span class="hljs-comment">// On theme toggle button click, toggle the page theme between dark and light mode,</span>
  <span class="hljs-comment">// then save the theme in local storage</span>
  <span class="hljs-built_in">document</span>
    .querySelector(<span class="hljs-string">"#theme-toggle"</span>)
    .addEventListener(<span class="hljs-string">"click"</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">let</span> theme = checkCurrentTheme() === <span class="hljs-string">"dark"</span> ? <span class="hljs-string">"light"</span> : <span class="hljs-string">"dark"</span>;
      applyTheme(theme);
      saveTheme(theme);
    });
} <span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"Theming isn't available on this browser."</span>, e);
}
</code></pre>
<p>You can view the demo below, but you may need to open the demo in a new tab for it to work properly.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codesandbox.io/s/mystifying-khayyam-48pl0?file=/index.html:2413-2516">https://codesandbox.io/s/mystifying-khayyam-48pl0?file=/index.html:2413-2516</a></div>
<p>Also, notice, how I never set the text color, background color, scrollbar color or button styles, that's part of the magic of setting <code>color-scheme</code>.</p>
<p>You can read more about <code>color-scheme</code> on web.dev,</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://web.dev/color-scheme/">https://web.dev/color-scheme/</a></div>
<p>Please tell me what you think about <code>color-scheme</code> in the comments below.</p>
<p><strong>Update:</strong> Another cool part feature of the <code>color-scheme</code> meta tag is that Samsung Internet won't force dark mode on your site if it uses the <code>color-scheme</code> meta tag, from what I can tell Chrome might implement a similar feature in the future. I tweeted about it </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/okikio_dev/status/1415211214888738818?s=20">https://twitter.com/okikio_dev/status/1415211214888738818?s=20</a></div>
<p>You can learn more about this on the Samsung Developers site, </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://developer.samsung.com/internet/blog/en-us/2020/12/15/dark-mode-in-samsung-internet">https://developer.samsung.com/internet/blog/en-us/2020/12/15/dark-mode-in-samsung-internet</a></div>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@alex_andrews">Alexander Andrews</a> on <a target="_blank" href="https://unsplash.com/photos/vGCErDhrc3E">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Adding Custom Easing to the Web Animation API]]></title><description><![CDATA[I recently posted about @okikio/animate, a new Animation library that uses the Web Animation API (WAAPI) to create smooth, and highly performant animation.
https://blog.okikio.dev/okikioanimate-the-animation-library-built-using-web-animations-api-waa...]]></description><link>https://blog.okikio.dev/adding-custom-easing-to-the-web-animation-api</link><guid isPermaLink="true">https://blog.okikio.dev/adding-custom-easing-to-the-web-animation-api</guid><category><![CDATA[animation]]></category><category><![CDATA[webdev]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[CSS Animation]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Sun, 20 Jun 2021 03:41:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1624146980236/An3IQyaku.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently posted about <a target="_blank" href="https://npmjs.com/@okikio/animate">@okikio/animate</a>, a new Animation library that uses the Web Animation API (WAAPI) to create smooth, and highly performant animation.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://blog.okikio.dev/okikioanimate-the-animation-library-built-using-web-animations-api-waapi">https://blog.okikio.dev/okikioanimate-the-animation-library-built-using-web-animations-api-waapi</a></div>
<p>Well a major limitation of the Web Animation API and consequently <a target="_blank" href="https://npmjs.com/@okikio/animate">@okikio/animate</a>, was that it didn't support custom easing, this limitation is on the verge of being removed.</p>
<p>Based on a comment by jakearchibald on Github and an article by <a target="_blank" href="https://www.kirillvasiltsov.com/writing/how-to-create-a-spring-animation-with-web-animation-api/">kirillvasiltsov</a></p>
<p><a target="_blank" href="https://github.com/w3c/csswg-drafts/issues/229#issuecomment-860778689">https://github.com/w3c/csswg-drafts/issues/229#issuecomment-860778689</a></p>
<p>Using the fact that WAAPI allows for linear easing, I created a small function that generates a set of arrays that create custom easing effects like bounce, elastic, and spring. As of right now it builds on top of <a target="_blank" href="https://npmjs.com/@okikio/animate">@okikio/animate</a> but that isn't absolutely necessary, it just may not be as comfortable as using it with <a target="_blank" href="https://npmjs.com/@okikio/animate">@okikio/animate</a>. </p>
<p>You can view a demo below,</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/okikio/pen/abJMWNy">https://codepen.io/okikio/pen/abJMWNy</a></div>
<p>As of right now if I add this to the <a target="_blank" href="https://npmjs.com/@okikio/animate">@okikio/animate</a> library it will more than double the size of the library (<em><strong>Note</strong>: treeshaking the actual package <code>@okikio/animate</code> while custom easing isn't in use will cause it to excluded from the final build reducing the size down to <code>~5.79 KB</code></em>).</p>
<p>I'm not sure if I should make Custom Easing a separate package or build it directly into <a target="_blank" href="https://npmjs.com/@okikio/animate">@okikio/animate</a>, please tell me what you think in the comments below.</p>
<p>You can also tweet at me </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/okikio_dev/status/1406396750668210180?s=20">https://twitter.com/okikio_dev/status/1406396750668210180?s=20</a></div>
<hr />
<p>This article also appeared on <a target="_blank" href="https://dev.to/okikio/adding-custom-easing-to-the-web-animation-api-2c68">dev.to</a>.</p>
<p>Photo by <a target="_blank" href="https://unsplash.com/@ellenqin">Ellen Qin</a> on <a target="_blank" href="https://unsplash.com/photos/fIMqGvVaATk">Unsplash</a>.</p>
]]></content:encoded></item><item><title><![CDATA[@okikio/animate - the animation library built using Web Animations API  (WAAPI)]]></title><description><![CDATA[Introduction
The Web Animations API lets us construct animations and control their playback with JavaScript. The API opens the browser’s animation engine to developers and was designed to underlie implementations of both CSS animations and transition...]]></description><link>https://blog.okikio.dev/okikioanimate-the-animation-library-built-using-web-animations-api-waapi</link><guid isPermaLink="true">https://blog.okikio.dev/okikioanimate-the-animation-library-built-using-web-animations-api-waapi</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[animation]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Okiki Ojo]]></dc:creator><pubDate>Fri, 11 Jun 2021 21:28:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1623446545759/8EquyuMoy.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[
<h3 id="heading-introduction">Introduction</h3>
<p>The Web Animations API lets us construct animations and control their playback with JavaScript. The API opens the browser’s animation engine to developers and was designed to underlie implementations of both CSS animations and transitions, leaving the door open to future animation effects. It is one of the most performant ways to animate on the Web, letting the browser make its own internal optimizations without hacks, coercion, or <code>window.requestAnimationFrame()</code>.</p>
<p>With the Web Animations API, we can move interactive animations from stylesheets to JavaScript, separating presentation from behavior. We no longer need to rely on DOM-heavy techniques such as writing CSS properties and scoping classes onto elements to control playback direction. And unlike pure, declarative CSS, JavaScript also lets us dynamically set values from properties to durations. For building custom animation libraries and creating interactive animations, the Web Animations API might be the perfect tool for the job. Let’s see what it can do!</p>
<p>For the rest of this article, I will sometimes refer to the Web Animation API as WAAPI. When searching for resources on the Web Animation API, you might be led astray by searching “Web Animation API” so, to make it easy to find resources, I feel we should adopt the term WAAPI; tell me what you think in the comments below.</p>
<h3 id="heading-this-is-the-library-i-made-with-the-waapi">This is the library I made with the WAAPI</h3>
<p><a target="_blank" href="https://www.npmjs.com/package/@okikio/animate">@okikio/animate</a> is an animation library for the modern web. It was inspired by <a target="_blank" href="https://github.com/bendc/animateplus">animateplus</a>, and <a target="_blank" href="https://animejs.com/">animejs</a>; it is focused on performance and developer experience, and utilizes the Web Animation API to deliver butter-smooth animations at a small size, weighing in at <strong>~5.79 KB</strong> (minified and gzipped).</p>
<h4 id="heading-the-story-behind-okikioanimate">The story behind <code>@okikio/animate</code></h4>
<p>In 2020, I decided to make a more efficient PJAX library, similar to <a target="_blank" href="https://www.rezo-zero.com/">Rezo Zero’s</a> - <a target="_blank" href="https://github.com/rezozero/starting-blocks">Starting Blocks</a> project, but with the ease of use of <a target="_blank" href="https://barba.js.org/">barbajs</a>. I felt starting blocks was easier to extend with custom functionality, and could be made smoother, faster, and easier to use.</p>
<p><strong>Note:</strong> if you don’t know what a PJAX library is I suggest checking out <a target="_blank" href="https://github.com/MoOx/pjax">MoOx/pjax</a>; in short, PJAX allows for smooth transitions between pages using fetch requests and switching out DOM Elements.</p>
<p>Over time my intent shifted, and I started noticing how often sites from <a target="_blank" href="https://www.awwwards.com/">awwwards.com</a> used PJAX, but often butchered the natural experience of the site and browser . Many of the sites looked cool at first glance, but the actual usage often told a different story — scrollbars were often overridden, prefetching was often too eager, and a lack of preparation for people without powerful internet connections, CPUs and/or GPUs. So, I decided to <a target="_blank" href="https://en.wikipedia.org/wiki/Progressive_enhancement">progressively enhance</a> the library I was going to build. I started what I call the “native initiative” stored in the GitHub repo <a target="_blank" href="https://github.com/okikio/native">okikio/native</a>; a means of introducing all the cool and modern features in a highly performant, compliant, and lightweight way.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/okikio/native">https://github.com/okikio/native</a></div>
<p>For the native initiative I designed the PJAX library <a target="_blank" href="https://www.npmjs.com/package/@okikio/native">@okikio/native</a>; while testing on an actual project, I ran into the Web Animation API, and realized there were no libraries that took advantage of it, so, I developed <a target="_blank" href="https://www.npmjs.com/package/@okikio/animate">@okikio/animate</a>, to create a browser compliant animation library. (<strong>Note</strong>: this was in 2020, around the same time <a target="_blank" href="https://www.npmjs.com/package/@wellyshen/use-web-animations">use-web-animations</a> by wellyshen was being developed. If you are using react and need some quick animate.css like effects, <a target="_blank" href="https://www.npmjs.com/package/@wellyshen/use-web-animations">use-web-animations</a> is a good fit.) At first, it was supposed to be simple wrapper but, little by little, I built on it and it’s now at <strong>80%</strong> feature parity with more mature animation libraries.</p>
<p><strong>Note:</strong> you can read more on the native initiative as well as the <code>@okikio/native</code> library on the <a target="_blank" href="https://github.com/okikio/native">Github repo okikio/native</a>. Also, okikio/native, is a <a target="_blank" href="https://lerna.js.org/">monorepo</a> with <code>@okikio/native</code> and <code>@okikio/animate</code> being sub-packages within it.</p>
<h4 id="heading-where-okikioanimate-fits-into-this-article">Where <code>@okikio/animate</code> fits into this article</h4>
<p>The Web Animation API is very open in design. It is functional on its own but it’s not the most developer-friendly or intuitive API, so I developed <code>@okikio/animate</code> to act as a wrapper around the WAAPI and introduce the features you know and love from other more mature animation libraries (with some new features included) to the high-performance nature of the Web Animation API. <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#readme">Give the project’s README a read</a> for much more context.</p>
<h3 id="heading-now-lets-get-started">Now, let’s get started</h3>
<p><code>@okikio/animate</code> creates animations by creating new instances of Animate (a class that acts as a wrapper around the Web Animation API).</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { Animate } <span class="hljs-keyword">from</span><span class="hljs-string">"@okikio/animate"</span>;

<span class="hljs-keyword">new</span> Animate({
    target: [<span class="hljs-comment">/* ... */</span>],
    duration: <span class="hljs-number">2000</span>,
    <span class="hljs-comment">// ... </span>
});
</code></pre>
<p>The <code>Animate</code> class receives a set of targets to animate, it then creates a list of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Animation">WAAPI Animation instances</a>, alongside a <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#mainanimation-animation">main animation</a> (the main animation is a small Animation instance that is set to animate over a non-visible element, it exists as a way of tracking the progress of the animations of the various target elements), the <code>Animate</code> class then plays each target elements Animation instance, including the main animation, to create smooth animations.</p>
<p>The main animation is there to ensure accuracy in different browser vendor implementations of WAAPI. The main animation is stored in <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#mainanimation-animation">Animate.prototype.mainAnimation</a>, while the target element’s Animation instances are stored in a <code>WeakMap</code>, with the key being its <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/KeyframeEffect">KeyframeEffect</a>. You can access the animation for a specific target using the <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#the-long-list-of-get-methods">Animate.prototype.getAnimation(el)</a>.</p>
<p>You don‘t need to fully understand the prior sentences, but they will aid your understanding of what <code>@okikio/animate</code> does. If you want to learn more about how WAAPI works, check out <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Animation">MDN</a>, or if you would like to learn more about the <code>@okikio/animate</code> library, I’d suggest checking out the <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#readme">okikio/native</a> project on GitHub.</p>
<h3 id="heading-usage-examples-and-demos">Usage, examples and demos</h3>
<p>By default, creating a new instance of Animate is very annoying, so, I created the <code>animate</code> function, which creates new Animate instances every time it’s called.</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> animate <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;
<span class="hljs-comment">// or</span>
<span class="hljs-keyword">import</span> { animate } <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;

animate({ 
    target: [<span class="hljs-comment">/* ... */</span>],
    duration: <span class="hljs-number">2000</span>,
    <span class="hljs-comment">// ... </span>
});
</code></pre>
<p>When using the <code>@okikio/animate</code> library to create animations you can do this:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> animate <span class="hljs-keyword">from</span> <span class="hljs-string">"@okikio/animate"</span>;

<span class="hljs-comment">// Do this if you installed it via the script tag: const { animate } = window.animate;</span>

(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">let</span> [options] = <span class="hljs-keyword">await</span> animate({
    target: <span class="hljs-string">".div"</span>,

    <span class="hljs-comment">// Units are added automatically for transform CSS properties</span>
    translateX: [<span class="hljs-number">0</span>, <span class="hljs-number">300</span>],
    duration: <span class="hljs-number">2000</span>, <span class="hljs-comment">// In milliseconds</span>
    speed: <span class="hljs-number">2</span>,
    });

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"The Animation is done..."</span>);
})();
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/okikio/pen/mdPwNbJ">https://codepen.io/okikio/pen/mdPwNbJ</a></div>
<p>You can also play with a demo with playback controls:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/okikio/pen/QWpjGaY">https://codepen.io/okikio/pen/QWpjGaY</a></div>
<p>Try out Motion Path:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/okikio/pen/mdWEMjg">https://codepen.io/okikio/pen/mdWEMjg</a></div>
<p>Try different types of Motion by changing the Animation Options:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/okikio/pen/JjWYRWz">https://codepen.io/okikio/pen/JjWYRWz</a></div>
<p>I also created a complex demo page with polyfills:</p>
<p><a target="_blank" href="https://okikio.github.io/native/demo/animate.html">View demo</a></p>
<p>You can find the source code for this demo in the <a target="_blank" href="https://github.com/okikio/native/blob/master/build/ts/modules/animate.ts">animate.ts</a> and <a target="_blank" href="https://github.com/okikio/native/blob/master/build/pug/animate.pug">animate.pug</a> files in the GitHub repo. And, yes, the demo uses Pug, and is a fairly complex setup. I highly suggest <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#demo">looking at the README as a primer for getting started</a>.</p>
<p>The native initiative uses <a target="_blank" href="https://www.gitpod.io/">Gitpod</a>, so if you want to play with the demo, I recommend clicking the <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#okikioanimate">“Open in Gitpod”</a> link since the entire environment is already set up for you — there’s nothing to configure.</p>
<p>You can also check out some <a target="_blank" href="https://codepen.io/collection/rxOEBO">more examples in this CodePen collection</a> I put together. For the most part, you can port your code from animejs to <code>@okikio/animate</code> with few-to-no issues.</p>
<p>I should probably mention that <code>@okikio/animate</code> supports both the <code>target</code> and <code>targets</code> keywords for settings animation targets. <code>@okikio/animate</code> will merge both list of targets into one list and use <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set">Set</a>s to remove any repeated targets. <code>@okikio/animate</code> supports functions as animation options, so you can use staggering similar to <a target="_blank" href="https://animejs.com/documentation/#functionBasedParameters">animejs</a>. (<strong>Note</strong>: the order of arguments are different, read more in the <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#animation-options--css-properties-as-methods">“Animation Options &amp; CSS Properties as Methods” section</a> of the README file.)</p>
<h3 id="heading-restrictions-and-limitations">Restrictions and limitations</h3>
<p><code>@okikio/animate</code> isn’t perfect; nothing really is, and seeing as the Web Animation API is a living standard constantly being improved, <code>@okikio/animate</code> itself still has lots of space to grow. That said, I am constantly trying to improve it and would love your input so please open a new issue, create a pull request or we can have a discussion <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#readme">over at the GitHub project</a>.</p>
<p>The first limitation is that it doesn’t really have a built-in timeline. There are a few reasons for this:</p>
<ol>
<li>I ran out of time. I am still only a student and don’t have lots of time to develop all the projects I want to.</li>
<li>I didn’t think a formal timeline was needed, as async/await programming was supported. Also, I added <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#timelineoffset">timelineOffset</a> as an animation option, should anyone ever need to create something similar to the timeline in animejs.</li>
<li>I wanted to make <code>@okikio/animate</code> as small as possible.</li>
<li>With <a target="_blank" href="https://drafts.csswg.org/web-animations-2/#group-effect">group effects</a> and <a target="_blank" href="https://drafts.csswg.org/web-animations-2/#sequence-effects">sequence effects</a> coming soon, I thought it would be best to leave the package small until an actual need comes up. On that note, I highly suggest reading <a target="_blank" href="https://danielcwilson.com/tags/web-animations-api/">Daniel C. Wilson’s series on the WAAPI</a>, particularly <a target="_blank" href="https://danielcwilson.com/blog/2015/09/animations-part-4/">the fourth installment</a> that covers group effects and sequence effects.</li>
</ol>
<p>Another limitation of <code>@okikio/animate</code> is that it lacks support for custom easings, like spring, elastic, etc. But check out <a target="_blank" href="https://github.com/jakearchibald/easing-worklet">Jake Archibald’s proposal for an easing worklet</a>. He discusses multiple standards that are currently in discussion. I prefer his proposal, as it’s the easiest to implement, not to mention the most elegant of the bunch. In the meanwhile, I'm taking inspiration from <a target="_blank" href="https://www.kirillvasiltsov.com/">Kirill Vasiltsov</a> article on <a target="_blank" href="https://www.kirillvasiltsov.com/writing/how-to-create-a-spring-animation-with-web-animation-api/">Spring animations with WAAPI</a> and I am planning to build something similar into the library.</p>
<p>The last limitation is that <code>@okikio/animate</code> only supports automatic units on transform functions e.g. <code>translateX</code>, <code>translate</code>, <code>scale</code>, <code>skew</code>, etc. This is no longer the case as of <code>@okikio/animate@2.2.0</code>, but there are still some limitations on CSS properties that support color. Check the <a target="_blank" href="https://github.com/okikio/native/releases/tag/%40okikio%2Fanimate%402.2.0">GitHub release</a> for more detail.</p>
<p>For example:</p>
<pre><code class="lang-ts">animate({
    targets: [<span class="hljs-string">".div"</span>, <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">".el"</span>)],

    <span class="hljs-comment">// By default "px", will be applied</span>
    translateX: <span class="hljs-number">300</span>,
    left: <span class="hljs-number">500</span>,
    margin: <span class="hljs-string">"56 70 8em 70%"</span>,

    <span class="hljs-comment">// "deg" will be applied to rotate instead of px</span>
    rotate: <span class="hljs-number">120</span>, 

    <span class="hljs-comment">// No units will be auto applied</span>
    color: <span class="hljs-string">"rgb(25, 25, 25)"</span>,
    <span class="hljs-string">"text-shadow"</span>: <span class="hljs-string">"25px 5px 15px rgb(25, 25, 25)"</span>
});
</code></pre>
<h3 id="heading-looking-to-the-future">Looking to the future</h3>
<p>Some future features, like <a target="_blank" href="https://drafts.csswg.org/scroll-animations-1/">ScrollTimeline</a>, are right around the corner. I don’t think anyone actually knows when it will release but since the ScrollTimeline in Chrome Canary 92, I think it’s safe to say the chances of a release in the near future look pretty good.</p>
<p>I built the <a target="_blank" href="https://github.com/okikio/native/tree/master/packages/animate#timeline">timeline</a> animation option into <code>@okikio/animate</code> to future-proof it. Here’s an example:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/okikio/pen/zYZEWJo">https://codepen.io/okikio/pen/zYZEWJo</a></div>
<p>Thanks to Bramus for <a target="_blank" href="https://codepen.io/bramus/pen/GRjPggQ">the demo inspiration</a>! Also, you may need the Canary version of Chrome or need to turn on Experimental Web Platform features in Chrome Flags to view this demo. It seems to work just fine on Firefox, though, so… 🤣.</p>
<p>If you want to read more on the ScrollTimeline, Bramus wrote <a target="_blank" href="https://www.bram.us/2021/02/23/the-future-of-css-scroll-linked-animations-part-1/">an excellent article on it</a>. I would also suggest reading the Google Developers <a target="_blank" href="https://developers.google.com/web/updates/2018/10/animation-worklet">article on Animation Worklets</a>.</p>
<p>My hope is to make the library smaller. It's currently <strong>~5.79 KB</strong> which seems high, at least to me. Normally, I would use a <a target="_blank" href="https://bundlephobia.com/package/@okikio/animate">bundlephobia</a> embed but that has trouble bundling the project, so if you want to verify the size, I suggest using <a target="_blank" href="https://bundlejs.com">bundlejs.com</a> because it actually bundles the code locally on your browser. I specifically built it for checking the bundle size of <code>@okikio/animate</code>, but note it's not as accurate as bundlephobia.</p>
<h3 id="heading-polyfills">Polyfills</h3>
<p><a target="_blank" href="https://okikio.github.io/native/demo/animate.html">One of the earlier demos</a> shows polyfills in action. You are going to need <a target="_blank" href="https://github.com/web-animations/web-animations-js">web-animations-next.min.js</a> from web-animations-js to support timelines. Other modern features the <code>KeyframeEffect</code> constructor is required.</p>
<p>The polyfill uses JavaScript to test if the <code>KeyframeEffect</code> is supported and, if it isn’t, the polyfill loads and does its thing. Just avoid adding async/defer to the polyfill, or it will not work the way you expect. You’ll also want to polyfill <code>Map</code>, <code>Set</code>, and <code>Promise</code>.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- Async --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.polyfill.io/v3/polyfill.min.js?features=default,es2015,es2018,Array.prototype.includes,Map,Set,Promise"</span> <span class="hljs-attr">async</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

        <span class="hljs-comment">&lt;!-- NO Async/Defer --&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"./js/webanimation-polyfill.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- Content --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>And if you’re building for ES6+, I highly recommend using <a target="_blank" href="https://esbuild.github.io/">esbuild</a> for transpiling, bundling, and minifying. For ES5, I suggest using esbuild (with minify off), <a target="_blank" href="https://www.typescriptlang.org/">Typescript</a> (with target of ES5), and <a target="_blank" href="http://terser.org/">terser</a>; as of now, this is the fastest setup to transpile to ES5, it's faster and more reliable than babel. See the <a target="_blank" href="https://github.com/okikio/native/blob/1ac1a166aed63c128ed9ca875fb8b817b9c9fb08/gulpfile.js#L143">Gulpfile</a> from the demo for more details.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p><code>@okikio/animate</code> is a wrapper around the Web Animation API (WAAPI) that allows you to use all the features you love from animejs and other animation libraries, in a small and concise package. So, what are your thoughts after reading about it? Is it something you think you’ll reach for when you need to craft complex animations? Or, even more important, is there something that would hold you back from using it? Leave a comment below or join the discussion on <a target="_blank" href="https://github.com/okikio/native/discussions/5">Github Discussions</a>.</p>
<p><strong>TLDR</strong> <code>@okikio/animate</code> is basically <code>animejs</code> but built on the Web Animation API.</p>
<hr />
<p>This article was originally published on <a target="_blank" href="https://dev.to/okikio/okikio-animate-animejs-but-built-on-the-web-animation-api-nin">dev.to</a> but also <a target="_blank" href="https://hackernoon.com/introducing-the-web-animations-api-and-okikioanimate-qx4e35mx">appeared on hackernoon</a> and on <a target="_blank" href="https://css-tricks.com/how-i-used-the-waapi-to-build-an-animation-library/">CSS-Tricks</a>.<br />Photo by <a target="_blank" href="https://unsplash.com/@pankajpatel">Pankaj Patel</a> on <a target="_blank" href="https://unsplash.com/">Unsplash</a>.</p>
]]></content:encoded></item></channel></rss>