# bundlejs: An online esbuild based bundler & npm bundle size checker

## Introduction

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

**bundlejs** aims to generate more accurate bundle size estimates by following the same approach that bundlers use:

* Doing all bundling locally
    
* Outputing the treeshaken bundled code
    
* Getting the resulting bundle size
    

The benefits of using **bundlejs** are:

1. It's easier to debug errors
    
2. You can verify the resulting bundled code
    
3. The ability to configure your bundles
    
4. The ability to treeshake bundles
    
5. The ability view a visual analysis of bundles
    
6. You can bundle offline (so long as the module has been used before)
    
7. Supports different types of modules from varying [Content Delivery Networks (CDNs)](#heading-cdn-hosts), e.g. CDNs ranging from deno modules, to npm modules, to random github scripts, etc...
    

%[https://bundlejs.com] 

This blog post is meant to highlight some of the most important changes as well as to give some insight into how **bundlejs** works in the background, and to act as the docs for bundlejs.

> 📒**Note**: There will be a follow up article to this one, going into the technical nitty gritty on how **bundlejs** 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.

> 😅 [**TL;DR**](https://www.dictionary.com/browse/tldr): this blog post is rather long, so take a look at the [bundlejs.com](https://bundlejs.com) 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.

### Quick feature run down

%[https://www.youtube.com/watch?v=5FTricK1peg] 

> This video runs through all the major features of **bundlejs** (there is audio but I don't have a good mic 😅)

## Bundling, Treeshaking, and Minification

> * **Bundling** is the process of efficiently [concatenating](https://www.dictionary.com/browse/concatenate) modules together into one file which we call a [bundle](https://www.dictionary.com/browse/bundle).
>     
> * **Treeshaking** is the process of a bundler traversing the modules to be bundled and removing unused code.
>     
> * **Minification** 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...
>     

**bundlejs** uses [esbuild](https://esbuild.github.io) and it's incredible ability to bundle, transform, transpile, minify, treeshake and traverse files. Specifically, **bundlejs** uses [esbuild-wasm](https://esbuild.github.io) which is able to access a subset of those features, with the key limitations being,

1. npm only runs on node, so no package.json or `npm install` (a-la, a joke about using [StackBlitz](https://stackblitz.com/)[`WebContainers`](https://blog.stackblitz.com/posts/introducing-webcontainers/) to run node on the browser)
    
2. 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
    
3. 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 `http(s)://...` modules, so a different solution is required
    

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,

1. [`HTTP` plugin](https://github.com/okikio/bundle/blob/main/src/ts/plugins/http.ts) - Fetches and caches modules
    
2. [`CDN` plugin](https://github.com/okikio/bundle/blob/main/src/ts/plugins/cdn.ts) - Redirects npm package imports (sometimes referred to as bare imports) to [Content Delivery Network (CDN)](https://en.wikipedia.org/wiki/Content_delivery_network) urls for fetching
    
3. [`EXTERNALS` plugin](https://github.com/okikio/bundle/blob/main/src/ts/plugins/external.ts) - Marks certain imports/exports as modules to exclude from bundling
    
4. [`ALIAS` plugin](https://github.com/okikio/bundle/blob/main/src/ts/plugins/alias.ts) - Aliases certain imports/exports to modules of a different name
    

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

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 [esbuild-wasm](https://esbuild.github.io) to create javascript bundles.

<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>

## Console

![Image of the bundlejs virtual console, just after esbuild-wasm has been initialized](https://cdn.hashnode.com/res/hashnode/image/upload/v1646254447271/-0bZdtKpC.png align="left")

In previous versions of [bundlejs.com](https://bundlejs.com) I encouraged devs to use the devtools console for viewing console logs, and for a while I thought it was an *ok* 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 [@hyrious](https://github.com/hyrious)'s [esbuild-repl](https://hyrious.me/esbuild-repl/?mode=build) I finally did it 👏.

<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

<details>
<summary>
<p><strong>Console Errors & 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

<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

![Image of the input and output tabs, with the config button off to the side](https://cdn.hashnode.com/res/hashnode/image/upload/v1647665286142/xlshCSwhs.png align="left")

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

## Configuration

In `v0.2` I added support for custom configurations (configs), it supports most of [esbuild's build options](https://esbuild.github.io/api/#build-api), as well as some added options to change the default CDN and the compression algorithm.

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

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

> 📒 **Note**: There are 3 available compression algorithms, `brotli`, `gzip`, and `lz4`.

### CDN Hosts

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

By default **bundlejs** lets you enter code like this,

```ts
export * from "@okikio/animate";
```

But behind the scenes **bundlejs** auto fetches that specific package from a CDN namely, [unpkg](https://unpkg.com).

> In older versions of **bundlejs** the default CDN used to be [skypack](https://cdn.skypack.dev) but because skypack doesn't have easy access to the `package.json` of node packages, I switched to using unpkg as the default CDN.

With later updates **bundlejs** recieved the ability to update the default `cdn` on a global or local scale.

<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>

### Compression Algorithms

**bundlejs** offers the options of bundling using:

1. `brotli` - results in the smallest bundle size but it's the slowest
    
2. `gzip` - results in the 2nd smallest bundle size but it's faster than `brotli` (**default**)
    
3. `lz4` - results in the largest bundle size but it's the fastest bundle algorithm
    

> 📒 **Note**: Each compression algorithm has it's own story.

#### The Brotli Problem

[brotli](https://en.wikipedia.org/wiki/Brotli) 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 [Lewis Liu](https://twitter.com/lewisl9029/status/1498928788477857794?s=20&t=iUL2EzC810kgGM6tn8nJeg) on Twitter I was able to use [deno-brotli](https://deno.land/x/brotli) to include a WASM version of brotli in **bundlejs**.

<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

`deno-brotli` does 2 things right, they are,

1. It compresses the huge WASM file required for `deno-brotli` into an `lz4` compressed string, which can then be decompressed by `lz4` 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).
    
    > For `lz4` support **bundlejs** is using [deno-lz4](https://deno.land/x/lz4), which also runs via WASM, but the way [deno-lz4](https://deno.land/x/lz4) compress itself is slightly different than `deno-brotli`. I'd highly encourage you to take a look at the source code for [deno-lz4](https://deno.land/x/lz4) it's really informative.
    
2. By having the WASM as a js file you can actually preload the WASM as a js module 🤯
    
    ![The Universe, Tim And Eric, Mind Blown GIF](https://cdn.hashnode.com/res/hashnode/image/upload/v1647757979258/UHlRGGHHq.gif align="left")
    

You can read through this tweet thread to learn more,

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

#### Default to Gzip

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

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

<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&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>

I eventually chose to replace [pako](https://github.com/nodeca/pako) with [denoflate](https://deno.land/x/denoflate) as the default compression algorithm for gzip, it's a bit faster and smaller than [pako](https://github.com/nodeca/pako).

#### LZ4, Gotta Go Fast

![Sanic The Hedgehob GIF](https://cdn.hashnode.com/res/hashnode/image/upload/v1647759856958/r_R5mjwDu.gif align="left")

In order to use WASM in a portable way, [deno-brotli](https://deno.land/x/brotli) would compress the WASM binary file into a base64 string using `lz4` as the compression algorithm. I saw the oppertunity to add another compression algorithm, so I used the `lz4` ([deno-lz4](https://deno.land/x/lz4)) implementation used to compress the brotli WASM binary string (see [#the-brotli-problem](#heading-the-brotli-problem) for more info.), it was by far the easiest compression algorithm to add to **bundlejs** 🤣.

### Compression Quality

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,

{ "compression": { "type": "brotli", "quality": 11 } }

> You can check out a demo here, [bundlejs.com/?config={"compression":{"type":"brotli","quality":11}}](https://bundlejs.com/?config=%7B%22compression%22:%7B%22type%22:%22brotli%22,%22quality%22:11%7D%7D).

### Aliases and Externals

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

Externals are a direct feature request [issue#13](https://github.com/okikio/bundle/issues/13), 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.

<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={"alias":{"@okikio/animate":"@babel/core"}}">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={"esbuild":{"external":["@okikio/animate"]}}">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&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&config={"esbuild":{"external":[...]}}</a></p>
</blockquote>
</details>

> No one else can understand my pain...I'm adding more feature to **bundlejs** as I'm writing this blog post, so it's just getting longer and longer and longer, etc.... 😅
> 
> ![My Pain Is Greater Than Yours, Naruto GIF](https://cdn.hashnode.com/res/hashnode/image/upload/v1647756027794/eQrEWtghF.gif align="left")

### Esbuild Config Options

[esbuild](https://esbuild.github.io/api) 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.

The supported esbuild build options are

<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...

![Image of editor button panel with all the editor buttons listed](https://cdn.hashnode.com/res/hashnode/image/upload/v1648183770577/_qysqdHxe.png align="left")

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

The current list of editor buttons are:

<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

<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

<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.

> **bundlejs** 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 **bundlejs** while on a mobile or touch enabled device.

## JSX Support

JSX is now officially supported in **bundlejs** 🎉.

![Image of the preact JSX demo on bundlejs](https://cdn.hashnode.com/res/hashnode/image/upload/v1651644967823/BO--VVjCH.png align="left")

> Ignore the red error lines, for some reason the [monaco code editor](https://github.com/microsoft/monaco-editor) doesn't want to work well with JSX 😅

To use JSX you need to set the `jsxFactory` and the `jsxFragment` config options according to the JSX based framework you are using.

e.g. for [Preact](https://preactjs.com/) the following config would be used:

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

> Try out the [preact demo](https://bundlejs.com/?q=(import)preact,(import)preact/hooks&treeshake=%5B%7Bh,Fragment%7D%5D,%5B%7BuseState%7D%5D&share=KYDwDg9gTgLgBAE2AMwIYFcA29noHYDGMAlhHnAMIT4zBQAUA3nAQBbGYJTDkC+AlHEYAoAJAEyAZ3gBtCTQA0cScBhUaAXTgBeOOhUBlGKlr0ADPwDcYiXmlxUCBDrj1B2gHzLV6vDHr0xO5exHAA1HAAjFY2UvCS6ABGMFCoRC5uOl4qatR+AUFZcKEAtFExYtww6FDk9GKiADweDU0IxABuLJiokpLaAETyfnQDLaITTYnoMDBkcGQUmMQEANbajAnJqUS8HiWNAPTTs2Tjk41g3B6MwzC8R1fA5xONJ3Pki8trG44Ie2Eju8zq0ju0Oi9GuDur1+kM8rQoCUALbAPqoADmwDGt3YnG4eAeh3B5yO4xivCAA&config=%7Besbuild:%7BjsxFactory:h,jsxFragment:Fragment%7D%7D)

## 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.

> \*sessions are the specific state of **bundlejs** 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.

<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>&treeshake=[T],[{ <span class="hljs-built_in">animate</span> }],[{ <span class="hljs-built_in">animate</span> as B }],[* as TR],[{ <span class="hljs-built_in">type</span> <span class="hljs-built_in">animate</span> }],[*],[{ <span class="hljs-built_in">animate</span> as A }],[* as PR],[{ <span class="hljs-built_in">animate</span> }]&<span class="hljs-built_in">text</span>=<span class="hljs-string">"export * as PR18 from \"</span>@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>&share=MYewdgziA2CmB00QHMAUAiAwiG6CUQA&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>}}&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 & 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> * <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> * <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> * <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> * <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>

The URL breakdown is,

```ts
/?
q=(import)@okikio/emitter,(import)@okikio/animate,(import)@okikio/animate,(import)@okikio/animate,(import)@okikio/animate,@okikio/animate,@okikio/animate,@okikio/animate,@okikio/animate&
treeshake=[T],[{ animate }],[{ animate as B }],[* as TR],[{ type animate }],[*],[{ animate as A }],[* as PR],[{ animate }]&
text="export * as PR18 from \"@okikio/animate\";\nexport { animate as animate2 } from \"@okikio/animate\";"&
share=MYewdgziA2CmB00QHMAUAiAwiG6CUQA&
config={"cdn":"skypack","compression":"brotli","esbuild":{"format":"cjs","minify":false,"treeShaking":false}}&
bundle
```

* `q` or `query` represents the module, e.g. `react`, `vue`, etc...
    
    You can add `(import)` in-front of a specific module to make it an import instead of an export
    
* `treeshake` represents the export/imports to treeshake.
    
    The treeshake syntax allows for specifying multiple exports per package, through this syntax
    
    ```ts
    "[{ x,y,z }],[*],[* as X],[{ type xyz }]" 
    // to
    export { x, y, z } from "...";
    export * from "...";
    export * as X from "...";
    export { type xyz } from "...";
    ```
    
    The square brackets represent seperate packages, and everything inside the squarebrackets, are the exported methods, types, etc...
    
* `text` represents the input code as a string (it's used for short input code)
    
* `share` represents compressed string version of the input code (it's used for large input code)
    
* `config` represents the bundle configuration to use when building the bundle
    
* `bundle` tells **bundlejs** 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 `&bundle` to the end of the url yourself.
    

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.

## Bundle Analysis

**bundlejs** can analyze and visually represent bundles as easy to navigate and easy to understand charts.

Using a port of [esbuild-visualizer](https://github.com/btd/esbuild-visualizer) and [rollup-plugin-visualizer](https://github.com/btd/rollup-plugin-visualizer) by [@bardadymchik](https://twitter.com/bardadymchik) I added the ability to visualize bundles, this feature comes from a feature request by [@atomiks](https://github.com/atomiks) on [issue#22](https://github.com/okikio/bundle/issues/22#:~:text=Further), the issue is still open you can make suggestions to improve this feature.

The bundle analysis charts are displayed right under the editor, like so,

![Image of the bundle analysis panel under the bundlejs code editor](https://cdn.hashnode.com/res/hashnode/image/upload/v1652418797353/5hjxPIx2k.png align="left")

The charts displayed comes in 3 distinct flavours:

<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

<details>
<summary>
<p><strong>Sunburst Chart</strong></p>
</summary>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652421487422/4KJKsqgLN.png" alt="Image of bundlejs

  

<details>
<summary>
<p><strong>Technical details...</strong></p>
</summary>
<blockquote>
<p>📒 <strong>Note</strong>: All analysis charts support the gzipped and brotli compressed sizes of bundles! When analyzing a bundle it will choose either <code>gzip</code> or <code>brotli</code> based on the compression type.</p>
<p>e.g. 
A config of,</p>
<pre><code class="lang-ts">{ 
  <span class="hljs-attr">"compression"</span>: <span class="hljs-string">"gzip"</span> 
}
</code></pre>
<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>
</blockquote>
</details>

## Analytics

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 [umami](https://umami.is) as a privacy preserving, cookieless, open source, Google Analytics alternative, to which the analytics are public for anyone to view.

<details>
<summary>
<p><strong>Extra detail...</strong></p>
</summary>
<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>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>Or click the page visit counter </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651899811300/_ycEW51EC.png" alt="Image of the page visit counter" /></p>
<blockquote>
<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

%[https://dev.to/jakobbouchard/setting-up-umami-with-vercel-and-supabase-3a73] 

## Discussions and Support

To encourage discussion, give support and to gain feedback, I added a comment section to **bundlejs**, I used [giscus](https://github.com/giscus/giscus) for this.

Initialy, when I created the **bundlejs** project I also created a [Github Discussion](https://github.com/okikio/bundle/discussions) 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 **bundlejs**. The problem is that no one really uses [Github Discussions](https://github.com/okikio/bundle/discussions), so I to integrated it right into the website itself via [giscus](https://github.com/giscus/giscus), this was so new users can easily interact with others, get support from me, and leave me feedback.

<details>
<summary>
<p><strong>Technical details...</strong></p>
</summary>
<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>For <strong>bundlejs</strong> I

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 **bundlejs**, I'll go through them and try to integrate your ideas into **bundlejs**.

## Security and Performance

Security and performance are critical quality areas for **bundlejs**. In order to bundle modules together, **bundlejs** has to fetch multiple sets of modules from all over the internet, while ensuring that malicious actors don't get involved, and that [esbuild-wasm](https://esbuild.github.io) isn't taken advantage of to crash other devices.

> Some *really*...**really** large modules can take up to `4+ GB` of memory to be bundled properly by `esbuild-wasm`.

The security criterias I set for **bundlejs** were:

1. Don't leak personal user info.
    
2. Don't go through a central server, e.g. the ability to get node modules from various sources
    
3. Ensure people always know what packages they are bundling
    
4. Ensure people can't use **bundlejs** to maliciously slowdown browsers
    

To ensure I met the security criterias set,

1. I use strict Content Security Policies (CSP) for **bundlejs**, ensuring no unintended 3rd party can get involved.
    
2. I self-host as many of the 3rd party scripts I can. By enclosing the number of hands involved in [bundlejs.com](https://bundlejs.com) I reduce the chance that personal information is leaked.
    
3. I use sandboxing techniques e.g. `Web Workers` and `Shared Workers` to ensure the main thread runs at a smooth `60fps` while avoiding access to the DOM.
    
    > [Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) and [Shared Workers](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) 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.
    > 
    > Most of the uses of Workers were `Shared Workers`. `Shared Workers` reduce the performance impact of multiple instances of the **bundlejs** site/web app running on the same device.
    
4. To reduce the chance of **bundlejs** 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.
    

The current browser landscape for `Shared Worker` support is spotty at best.

The support table looks like this,

| Browser | Shared Workers | [Module Workers](https://web.dev/module-workers/) |
| --- | --- | --- |
| Firefox | Yes | No |
| Chrome | Yes | Yes |
| Safari | [Yes\*](https://twitter.com/webkit/status/1506787526937235464) | Yes |
| \* Support is currently experimental, but should be coming in later versions |  |  |

> [Module Workers](https://web.dev/module-workers/) are esmodules that run in Workers.

> 📒 **Note**: I built a `Shared Worker` polyfill [@okikio/sharedworker](https://github.com/okikio/sharedworker). It's a small but simple polyfill that falls back to a regular `Web Worker` if `Shared Workers` are not supported. You can use it while waiting for the next version of Safari to support `Shared Workers`, or while supporting older versions of Safari.

> ⚠️ **Warning**: The [Shared Worker polyfill](https://github.com/okikio/sharedworker) 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 **bundlejs** handles module workers in the [bundlejs source code](https://github.com/okikio/bundle/blob/main/src/ts/util/WebWorker.ts), you may also wish to view the [astro-repl source code](https://github.com/withastro/astro-repl/blob/main/src/utils/WebWorker.ts) to see how it handles module workers.

Most of the other security policies are passive in nature, e.g.

* **bundlejs** only bundling on page load if the URL has `?bundle` in it.
    
* **bundlejs** enforcing `https://` for all requests, including for iframes, etc...
    
* Only have properly vetted CDN hosts for **bundlejs** by default.
    
* etc...
    

## Tips and Tricks

> **Top tier tip**, follow me ([@okikio\_dev](https://twitter.com/okikio_dev)) and **bundlejs** ([@jsbundle](https://twitter.com/jsbundle)) on twitter; [shameless plug](https://www.urbandictionary.com/define.php?term=Shameless%20Plug) 🤣.
> 
> I do post announcments and updates on these accounts, as well as small tips and tricks that help in making the most use of **bundlejs**.

* When bundling packages that also export CSS and other external files, [bundlejs.com](https://bundlejs.com) 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.
    
* Treeshaking is available, but not all CDNs support access to each packages `package.json` 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.
    
* Check the full devtools console for error messages and warnings, if you are having trouble debuging an issue in **bundlejs**, or even better yet enable `esbuilds` verbose logging when trying to debug issues, e.g.
    
    ```ts
    {
        "esbuild": {
            "logLevel": "verbose"
        }
    }
    ```
    
    Check out a [demo](https://bundlejs.com/?config=%7B%22esbuild%22:%7B%22logLevel%22:%22verbose%22%7D%7D).
    
* 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 `cdn` config option. I recommend using custom protocols instead of the `cdn` config option when trying to debug issues with a CDN:
    
    e.g.
    
    ```ts
    {
        "cdn": "unpkg"
    }
    ```
    
    or
    
    ```ts
    export * from "unpkg:typescript";
    ```
    
    Try using custom protocols to solve this [example issue](https://bundlejs.com/?q=@babel/core&config=%7B%22cdn%22:%22esm.run%22%7D) on **bundlejs**.
    
* 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,
    
    ```ts
    export * from "skypack:solid-dismiss";
    // and
    export { default } from "skypack:solid-dismiss";
    ```
    

> If you have a tip and trick you would like to share, post a comment below, or send me a [tweet](https://twitter.com/okikio_dev)!

## Contribute

The codebase is currently quite disorganized so, I'd suggest direct messaging me on [Twitter](https://twitter.com/okikio_dev) or starting a [GitHub Discussion](https://github.com/okikio/bundle/discussions) to discuss ways to contribute.

There is a lot of stuff happening on the **bundlejs** 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.

You can use a pre-made Gitpod dev environment to quickly get started with the project or to contribute quick changes to the project.

[![Open In Gitpod](https://gitpod.io/button/open-in-gitpod.svg align="left")](https://gitpod.io/#https://github.com/okikio/bundle/blob/main/README.md)

If you love the project, I'd welcome if you'd spread the word, my goal is to make **bundlejs** a viable alternative/replacement for [bundlephobia](https://bundlephobia.com/) 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.

Last note, **bundlejs** is now on [OpenCollective](https://opencollective.com/bundle), so if you'd like to contribute to it financially, it'd be appreciated.

## Conclusion

[**bundlejs**](https://bundlejs.com), a quick and easy way to treeshake, bundle, minify, and compress (in either [gzip](https://en.wikipedia.org/wiki/Gzip) or [brotli](https://en.wikipedia.org/wiki/Brotli)) your [typescript](https://www.typescriptlang.org/), [javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript), [jsx](https://reactjs.org/docs/introducing-jsx.html) and [npm](https://www.npmjs.com/) projects, while receiving the total bundles' file size.

**bundlejs** aims to generate more accurate bundle size estimates by following the same approach that bundlers use:

* Doing all bundling locally
    
* Outputing the treeshaken bundled code
    
* Getting the resulting bundle size
    

The benefits of using **bundlejs** are:

1. It's easier to debug errors
    
2. You can verify the resulting bundled code
    
3. The ability to configure your bundles
    
4. The ability to treeshake bundles
    
5. The ability to view a visual analysis of bundles
    
6. You can bundle offline (so long as the module has been used before)
    
7. Supports different types of modules from varying [Content Delivery Networks (CDNs)](#heading-cdn-hosts), e.g. CDNs ranging from deno modules, to npm modules, to random github scripts, etc...
    

The next time you need to bundle a project or you need to know the bundle size of a project, give [bundlejs.com](https://bundlejs.com) a try.

> 📒**Note**: There will be a follow up article to this one, going into the technical nitty gritty on how **bundlejs** 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.

---

Photo by [Okiki Ojo](https://okiki.dev/), you can find the image on [Dropbox](https://www.dropbox.com/sh/dmu48xw2dbfiyui/AAD-LGqX_zwpZYgyIgYpCelba?dl=0).

Originally published on [blog.okikio.dev](https://blog.okikio.dev/documenting-an-online-bundler-bundlejs)

Also, published on [Hackernoon](https://hackernoon.com/bundlejs-an-online-esbuild-based-bundler) and [dev.to](https://dev.to/okikio/documenting-an-online-esbuild-based-bundler-bundlejs-4m52)
