FuseBox - TypeScript-centric code bundler introduction
Tags: typescript, javascript, webdev, productivity
Published: 2019-04-25 19:26:46

**This post is taken from [my blog](https://areknawo.com), so be sure to check it out for more up-to-date content 😉**

With JavaScript capabilities and complexity of written code on-growing, a great switch was made towards more  **modular architecture**. CommonJS, AMD, and finally standardized  **ES6 modules** (ESM) clearly show that. But, with such trends, it's still much better for today's browsers to provide a singular code bundle, rather than multiple smaller modules. And that's why  **code bundling**  📦 became a really popular and common task.

There are a lot of bundling tools available in the JS ecosystem. The main ones being, arguably,  [**Webpack**](https://webpack.js.org/),  [**Rollup**](https://rollupjs.org/)  and  [**Parcel**](https://parceljs.org/). All of which serve the same purpose, but with certain goals of its own. E.g. Webpack is used mostly to bundle  **web apps**, Rollup for  **libraries**  and Parcel for  **prototyping**  or getting stuff done fast. But, one thing that many of such tools have in common, is their  **extendability**. The amount of available plugins, loaders and etc. is just stunning! On the other hand, some might enjoy tools like Parcel, that doesn't require much configuration. Including me. Personally, I prefer to use tools like  [**Microbundle**](https://github.com/developit/microbundle),  [**Bili**](https://bili.egoist.sh/#/)  or  [**Poi**](https://poi.js.org/)  - based on highly-configurable and stable options, but with much  **less configuration**  needed. Some of them prefer a minimalistic approach, some just include as many functionalities out-of-the-box as possible. Still - I don't care as it just improves my experience and ultimately doesn't affect the output bundle size (hopefully). 🙃

Anyway, today I'd like to share with you a bundler that I only recently learned about -  [**FuseBox**](http://fuse-box.org/). Maybe you know about it already? But, for me, this is really something new. And, from a while of usage, I can really say that it's a worth-considering option for my future projects...

# What's FuseBox?

Just like I said before, it's a module bundler. But a special one (every tool is special in its own way). It's not really a that young project, it's pretty solid and arguably quite popular  **~ 3700 stars**on  [GitHub](https://github.com/fuse-box/fuse-box)  (if you use this kind of measure 😅). So much so, that it even has a special, icon in my  [VS Code icon pack](https://marketplace.visualstudio.com/items?itemName=PKief.material-icon-theme)  of choice! Anyway, let's make a quick overview of its features.

First, some that many other bundlers have. Customization,  **incremental builds**  and  **caching**  - standard stuff. There's also support for  **ESM dynamic imports**  and nice  **plugin system**.

From special stuff, FuseBox comes with automatic CSS splitting, code optimizations,  **HMR**, and... first-class  **TypeScript support**! That's right! There's no further configuration needed - just swap your  _.js_  to  _.ts_  extensions (I'm talking about changing entry file) and that's all!

Now, such built-in features may remind you of Parcel (if you've used it before). Similarly, it comes with TS support, HMR, caching and even more stuff! But, and correct me if I'm wrong, Parcel feels to be targeted towards web app development (not libraries) and even then it's relatively good for prototyping mostly. It's still an awesome tool tho, it's just that I think FuseBox, with its a bit more complex config and stability, is just a much better choice. 👍

# What I love and what I hate

Right now, I know I seem completely biased about FuseBox. It just provided me with some really good first impressions. But, with that said,  **FuseBox isn't perfect**  - no tool is. There's still a lot of room to improve. So, to give you a bigger picture of this tool, let me talk about some pros and cons.

## Pros

There's definitely a lot to love about FuseBox, that makes it stand out from the rest of bundlers. TypeScript support super fast builds, and very easy configuration are my top picks. FuseBox uses  **CommonJS**  as main resolve method under-the-hood to allow your builds to be  **blazing-fast**. ⚡ I mean like milliseconds fast! Oh, and you can still use ESM the way you want. As for the config - it's really, really intuitive! Instead of standard configuration object most tools usually accept, FuseBox is run with Node.js itself, by executing its  **fuse.js**  "config" file. It's more of a Node.js program. Inside such file, you can use FuseBox  **chainable**, very much [**pleasing API**](https://fuse-box.org/docs/development/configuration), supported by TypeScript autocompletion. Also, there's even built-in task runner called  [**Sparky**](https://fuse-box.org/docs/task-runner/sparky)  for additional functionalities!

But, yeah, there are some downsides too. For me, the main one is that FuseBox cannot generate TypeScript declaration files on its own. In fact, FuseBox often  **ignores tsconfig.json**  to some high extents. Sure you can use TSC separately or Sparky for such things, but with many bundlers having this option built-in, it's just something that I'd like to have.

## Cons

Next, there's its  **documentation**. It's nice, easy-to-navigate and understandable, but, when it comes to more complex things, I have a feeling that it  **doesn't cover it all**. These are my main picks. There's also the fact that you have to manually install tools that FuseBox will utilize (why not all-in-one? - it's a development tool, right?). And lastly, there's the  **plugins collection**. It's surely not as big as one of Webpack or Rollup. There are only some third-party plugins and the rest is provided with the FuseBox package. Those have a fair amount of [**documentation**](https://fuse-box.org/docs/plugins/babel-plugin)  of the FuseBox official site and are pretty comfortable to use. But, there's one plugin that's arguably the most important -  [**QuantumPlugin**](https://fuse-box.org/docs/production-builds/quantum). It's just kind-of all-in-one plugin for creating  **production-ready builds**. It's still nice to use, but it's quite bloated and really complex. So much so, that it's still big documentation doesn't explain all things clearly. And, to makes matters worse, QuantumPlugin doesn't work in all cases (more on that later). 😕

Anyway,  [**FuseBox v4**](https://github.com/fuse-box/fuse-box/tree/v4)  is on its way, featuring improvements to  **tsconfig.json support**, HMR, and other stuff, so I hope that this tool will only get better. Now, after all, these complaints of mine, it's time to finally get our hands dirty and do some  **FuseBox setup**  on our own!

# FuseBox usage

## Setup

### Basics

Let's start with an already prepared package, by installing FuseBox itself with TypeScript and  [**Terser**](https://github.com/terser-js/terser)  for future use.

```bash
yarn add --dev fuse-box typescript terser
```

Now, it's time to plan our project's structure. We'll use TypeScript and place our entry file (index.ts) in the  **src**  folder. For development, we would like to have  **hot reloading**  and  **incremental builds**  enabled. As for the building process, we'll output our bundle file to build folder, and  **TS declarations**  and processed files to  **typings**  and  **lib**  folders respectively. The somewhat standard structure for a library. 📖

### Config

First, create the  **fuse.js**  file and analyze its basic boilerplate.

```javascript
// fuse.js
const { FuseBox } = require("fuse-box");
const fuse = FuseBox.init({
  homeDir: "src",
  target: "browser@es5",
  output: "build/$name.js",
});
fuse
  .bundle("app")
  .instructions(" > index.ts")
fuse.run();
```

Here, we're initiating FuseBox with  [`FuseBox.init()`](https://fuse-box.org/docs/development/configuration#initialisation)  method. There, we pass our basic  [**configuration object**](https://fuse-box.org/docs/development/configuration)  with some required properties.  [`homeDir`](https://fuse-box.org/docs/development/configuration#home-directory)  indicates the main directory of our input files,  [`target`](https://fuse-box.org/docs/development/configuration#target), in form of  `"[env]@[lang]"`  where env can be  `"browser"`,  `"server"`  or  `"electron"`  and lang having a form of language level string (e.g.  `"es6"`  or  `"esnext"`). Finally, we specify the output location of our bundle with  [`output`](https://fuse-box.org/docs/development/configuration#output)  property and a useful  `$name`  macro, matching our bundle's name.

Then, we make use of FuseBox chainable API and name our bundle with  `.bundle()`  method, and provide proper  [**running instruction**](https://fuse-box.org/docs/development/instructions)  (input file) with  `.instructions()`  method. Finishing the job with simple  `.run()`  call.

### Usage

You can execute such a prepared file, just like any other Node.js program - with  `node fuse.js`command.  **So convenient**! 😉

```bash
node fuse.js
```

Now, to be clear, with the above config we should already have nice TS support included and... pretty  **big bundle**. By default, FuseBox uses  **CommonJS under-the-hood**  (that's why it's so fast). It just wraps different modules inside these  **CJS wrappers**  that can quickly be bundled. But, this additional code (and some metadata) results in your final bundle gaining additional  **5 KB** (unminified) in size. Ouch!

## Task runner

Leaving the bundle size aside for a moment, we also wanted to have an automatic generation of TS declaration files and output (for modular environments) within our FuseBox pipeline. And, as mentioned before, there's no built-in option for that. So, we'll have to use Sparky instead.

### Context

```javascript
const { task, context, tsc } = require("fuse-box/sparky");
// ...

context({
    getConfig() {
      return FuseBox.init({
          homeDir: "src",
          target: "browser@es5",
          output: "build/$name.js",
      });
    }
});
// ...
```

First, we'll have to change the basic structure of our fuse.js file. As the official documentation suggests, before using Sparky, we should first set up a so-called  [**context**](https://fuse-box.org/docs/3.5.0/task-runner/getting-started-with-sparky#task-context), which later will be accessible by all our tasks. And, although it indeed brings some clarity to our config, we sadly lose TS autocompletion along the way. 😔

### Build

After our context is set up, it's only a matter of creating our tasks. In our case, we'll use only two. The first for building process and second for development. Let's see how it's done...

```javascript
// ...
task("build", async ctx => {
    const fuse = ctx.getConfig();
    fuse
        .bundle("app")
        .instructions(" > index.ts");
    await fuse.run();
    await tsc("src", {
        target: "esnext",
        outDir: "lib/",
        declaration: true,
        declarationDir: "typings/"
    });
});
```

Within the above  `"build"`  task, aside from its declaration (  [`task()`](https://fuse-box.org/docs/3.5.0/task-runner/sparky#task)  function with the provided name and  **asynchronous function**), we pretty much follow the same pattern as in our first version of the file. It's only after that that we use new  [`tsc()`](https://fuse-box.org/docs/3.5.0/task-runner/sparky#tsc)  function that Sparky kindly provided to us among other methods of its API. As the name indicates, this functions allows you to run TSC with provided configuration, and local tsconfig.json file. Sadly, it requires TypeScript to be  **installed globally**  in order to run. Now that's some serious disadvantage! 😠 Here, we're providing it with a minimal setup to just output our built, modular files and TS declaration files.

Oh, and about this tsconfig.json of ours...

```json
{
  "include": ["src/**/*"],
  "exclude": ["node_modules"],
  "compilerOptions": {
    "target": "es5"
  }
}
```

If tsconfig.json is not present, FuseBox will generate one with its  [**default configuration**](https://fuse-box.org/docs/3.5.0/getting-started/typescript-project#typescript-configuration). And, aside from that, FuseBox ignores  `compilarOptions.module`  property anyway, and sets it to its own CommonJS format, so it doesn't make sense to even set that option. Just remember that, in your code, you should use ESM only.

### Watch

As for our  `"watch"`  task, we're doing pretty much the same thing here. Only this time instead of just running FuseBox once, we use its  [chainable](https://fuse-box.org/docs/3.5.0/development/watching)  [API](https://fuse-box.org/docs/3.5.0/development/hmr)  and make FuseBox automatically enable HMR and  **files watching**  for us. That's the convenience I was talking about!

```javascript
// ...
task("watch", async ctx => {
    const fuse = ctx.getConfig();
    fuse
        .bundle("app")
        .instructions(" > index.ts")
        .hmr()
        .watch();
    fuse.run();
});
```

Of course, we won't be running TSC here. I'd only slow down our sweet, almost-instantaneous rebuild times.

And, to run any of your tasks, just supply its name after the main command, like this:

```bash
node fuse.js build
```

When you run FuseBox without any task name, and there are some Sparky tasks defined, the  `"default"`  task is run. You can use this fact to create your own custom  [**default task**](https://fuse-box.org/docs/3.5.0/task-runner/getting-started-with-sparky#default-task)  with available, vast API of Sparky. If no such task is provided, FuseBox execution won't do anything.

## Production

### Paths

It's time for some final  **production tips**! ✨ When you e.g. want to use one config for  **multiple directories**  (like in the monorepo setup from  [the previous post](https://areknawo.com/full-blown-monorepo-setup-walkthrough/)) you have to know that FuseBox has some different resolve system. That's why simple  `./`  won't do the job. With FuseBox taking into account  `homeDir`, tsconfig.json  `baseUrl`, or config file's location, it's easy to get lost (at least IMHO). That's why, if you want to make sure that you're referencing the  **directory currently worked-on**, just use  [`process.cwd()`](https://nodejs.org/api/process.html#process_process_cwd)  and  [**path module**](https://nodejs.org/api/path.html). That's just the magic of using a full-fledged Node.js program!

```javascript
// ...
const fuse = FuseBox.init({
    homeDir: process.cwd(),
    // ...
});
// ...
```

### Globals

Next -  **globals**. Especially when creating libraries for  **browser environments**, creating builds that expose certain properties on global objects (`window`  in browser and  `exports`  in Node.js) is often really useful. For this purpose, FuseBox provides special  [`globals`](https://fuse-box.org/docs/3.5.0/development/configuration#global-variables)  field in its config object (or a  `.globals()`  chainable method). With this property, you can expose multiple packages (refer to docs) or, more often, just the ones exported from your entry file. In this case, just provide your globals object with the chosen name and assign it to  `default`  property.

```javascript
// ...
const fuse = FuseBox.init({
    globals: {
        default: "NameToExposeToWindow"
    }
});
```

### Minification

Lastly, we have our bundle size. It's here where things start to get a little... complicated. In theory, we should just be able to drop the  [**TerserPlugin**](https://fuse-box.org/docs/plugins/terser-plugin),  **QuantumPlugin**, in a way just like any other and call it a day. 🙌

```javascript
//...
const { FuseBox, TerserPlugin, QuantumPlugin } = require("fuse-box");

const isProduction = process.env.NODE_ENV === "production";

const fuse = FuseBox.init({
    // ...
    plugins: [
        isProduction && QuantumPlugin({
            bakeApiIntoBundle: true
            treeshake: true
        }),
        isProduction && TerserPlugin(),
    ]
    // ...
});
```

Here, we use a straightforward method of applying plugins in FuseBox. First, we apply the QuantumPlugin only if we're in  **production mode**. It's just a simple check to save some time during development (then set with  `NODE_ENV=production`). Then we initiated our plugin with a simple call and configuration - just like any other. QuantumPlugin uses a  [**different API**](https://fuse-box.org/docs/production-builds/quantum#features)  from standard CJS wrappers mentioned earlier. It's significantly smaller, but not yet fully compatible with the original one - that's why it's not used by default. It also applies several optimizations, like e.g.  [**tree-shaking**](https://fuse-box.org/docs/production-builds/quantum-configuration#treeshake). Our configuration object basically enables mentioned tree-shaking feature and puts this API  [**into our bundle**](https://fuse-box.org/docs/production-builds/quantum-configuration#bakeapiintobundle)  (by default it's located in a separate file).

Then, we drop in the TerserPlugin to minify our bundle. As a note, Terser, in contrast to standard Uglify, supports  **modern ES-Next syntax**  minification out-of-the-box.

The configuration above should significantly shrink the size of our output. There still will be some boilerplate there, but we're talking only 300~400 bytes. It seems perfect! So, what's the problem? 🤔

Well, I previously said that FuseBox has good support for web apps and libraries bundling... yeah, that's how far the second goes. The QuantumPlugin  **doesn't work well**  with all features available in standard resolution method... and support for  **globals**  is one of them. And, while it's not a big deal for web apps of any kind, libraries development takes a big hit. The only solution, for now, is just not to use QuantumPlugin in certain scenarios and stay with TerserPlugin or  [UglifyJSPlugin](https://fuse-box.org/docs/3.5.0/plugins/uglifyjs-plugin) only. This, however, leaves us still with some KB of CommonJS wrappers... even if minified. I just hope that this will be fixed with the  **v4**  release. It seems to be a big one.

# Thoughts?

So, as you can clearly see, FuseBox is a  **nice bundler**... and surely one that deserves to be known. With some  **additional tweaks**, I really have big hopes for this tool. Feature-set is mostly complete, the configuration process is extremely compelling... There are just some things that need to be worked on. And, after it's done, who knows? Maybe it'll even rival the position of both Webpack and Rollup? 🤔

Anyway, thank you for reading this post, and, as always, let me know  **what do you think**  of it and FuseBox down  **in the comments**! Also, drop  **a reaction**  here and  [**a star there**](https://github.com/fuse-box/fuse-box/stargazers)  to show your support! For more  **up-to-date content**  from this blog, consider  **following me**  [**on Twitter**](https://twitter.com/areknawo),  [**on my Facebook page**](https://www.facebook.com/areknawoblog)  and checking out [**my personal blog**](https://areknawo.com). Again, thank you for reading and I'll see you in the next one! 🔥✌