Importing modules in JavaScript, are we doing it right?
Tags: javascript, typescript, es6, treeshaking
Published: 2021-08-31 21:39:43

We, as Javascript developers need to use libraries in our day to day, it simplifies our work a lot. Nowadays we mostly do it in this way:

```javascript
import * as lib from 'amazing-lib'
import { func1 } from 'amazing-lib'
```

If we quickly analyze it, in the first statement we are importing everything and putting it on a variable called `lib`, on the second, we are importing everything again and destructuring only what we need; what about all the other code that I'm not using?

<br>

> ## _Will not all the unused library's code end up on the final bundle making my application unnecessary heavier?_

<br>

Today you will learn how to improve your bundle size just by changing the way you import. After this, you are going to be able to detect a simple optimization opportunity for your bundle!

---
<br>

## TL; DR

Verify if the library has support for ES6 and you can import freely, you will get always the best result 🙆‍♂️. If it doesn't ⚠️, you need to import using cherry-picking.

<br>

---

## Can we import as we want without consequences?

When we compile our front-end apps, there is a process that Webpack applies called [Tree Shaking](https://webpack.js.org/guides/tree-shaking/). Basically, is code elimination, the code that is not being used by anyone. This process prevents having dead code in our final bundle, making it lighter and the application is going to load faster for our users!

Let's analyse this:

```javascript
import * as lib from 'amazing-lib'
import { foo } from 'amazing-lib'
```

In both cases all the library content is being imported, the first place is the easiest to spot, all the library's content is being assigned to the variable `lib`, in the second case we are just applying destructuring to the library's content to get what we need. Thanks to Tree Shaking all the unused code doesn't end up on our bundles.

So, thanks to Tree Shaking I'm excused and I can import however I want and all the unused code imported from the library will be removed automagically?

**Is not always the case**

There is a scenario when Tree Shaking is not going to be able to detect what is dead code having as consequence to remove nothing.

## Scenarios

### ES6

ECMAScript 2015(aka ES6) Module Syntax; it sounds complex, but it's something really popular nowadays. It's just a syntax to import a JS module, it looks like this:

```javascript
import { foo } from 'awesome-lib'
import { bar } from '../utils'
import * as lib from '../utils'

export const justAConst = 'foobar'
```

When you are using a library that has ES6 Module Syntax compatibility, you don't need to worry, import as will, Tree Shaking have you covered 😉. In fact, is the only module syntax that Tree Shaking supports, let's take a look at the [documentation](https://webpack.js.org/guides/tree-shaking/):

> *Tree shaking* is a term commonly used in the JavaScript context for dead-code elimination. It relies on the static structure of ES2015 module syntax, i.e. `import` and `export`...
>
> The webpack 2 release came with built-in support for ES2015 modules (alias harmony modules) as well as unused module export detection...

###### If you are completely new about 👉 [JS Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)

### No ES6 module syntax

A library can be delivered (packaged) with other module systems different than ES6, even though if its source code uses ES6 module syntax, a compilation process could be implemented to only support [CommonJS](https://nodejs.org/api/modules.html#modules_modules_commonjs_modules) for example. The projects written using pure JS (with no transpilation process (Babel, TypeScript)) that uses the CommonJs module system is another example.

So, no ES6 module syntax present = no Tree Shaking. The only way to have a healthy bundle when dealing with libraries with no ES6 is importing using a technique called cherry-picking, you need to specify the absolute path to the file that contains the info needed.

```javascript
import { small } from 'common-js-lib/small';
```

#### Downsides of cherry-picking

- You need to know the path to the module needed. (Your IDE could help in this quest)
- You need to specify each one of the imports that you need, ex:

    ```javascript
    import has from 'lodash/has';
    import capitalize from 'lodash/capitalize';
    import lastIndexOf from 'lodash/lastIndexOf';
    ```

- As a maintainer, you may want to have a nice and easy-to-use scaffolding design to detect with ease something in your lib. This needs to be designed, implemented, and maintained.
- You may forget to do it, making your bundle unnecessary heavier. [ESLint](https://eslint.org/docs/rules/no-restricted-imports) could help you to import correctly.

## Performance Test

Having the theory learned I decided to prove all this. What I did was create some libraries with different module support, create several front-end apps on React and Angular[^1] to test if Tree Shaking really does its job.

The libraries created were simple, they export two variables `small` and `big`. `small` contains one dog 🐕 (`small = '🐕'`), but `big` has 1646400 dogs (`big = '🐕🐕🐕🐕🐕🐕🐕🐕🐕...'`). This is going to make `big` to be 6.3 megabytes of weight.

Only `small` is going to be use at all time, so if `big` sneaks into the final bundle we are going to notice it on sight!.

### Healthy Bundle

This how a healthy bundle looks like:
![a healthy, bundle of 211.78KB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0kar9uiffp7oi7pot3w.png)

### Smelly Bundle 🤢

The smelly one! You can notice a big white box that represents `big` and represents 96.7% of the application size:
![Smelly Bundle, bundle of 6.49MB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpvw6wwvwfjpbjhf8fjp.png)

### The Results

The results were as expected, if your library has to offer ES6 module syntax, Tree Shaking will do its job. If not, cherry-picking was the only way to get a healthy bundle.

Here is the repo if you are curious [dianjuar/how-to-import](https://github.com/dianjuar/how-to-import). All this was created in a Monorepo using [Nx](https://nx.dev/), the library's npm publishing was mocked using [yalc](https://github.com/wclr/yalc). The bundle analysis was made using [source-map-explorer](https://www.npmjs.com/package/source-map-explorer).

Also, I wanted to make this test with popular libraries, so this is what I got, importing as `import { whatINeed } from 'popular-lib'`

| Library                                                                                       | Healthy Bundle |
| :-------------------------------------------------------------------------------------------- | :------------: |
| [lodash](https://www.npmjs.com/package/lodash)                                                |       ❌        |
| [moment](https://www.npmjs.com/package/moment)                                                |       ❌        |
| [rxjs](https://www.npmjs.com/package/rxjs)                                                    |       ✅        |
| [lodash-es](https://www.npmjs.com/package/lodash-es)                                          |       ✅        |
| [date-fns](https://www.npmjs.com/package/date-fns)                                            |       ✅        |
| [@angular/core](https://www.npmjs.com/package/@angular/core)                                  |       ✅        |
| [@angular/material](https://www.n-pmjs.com/package/@angular/material)                         |       ✅        |
| [react](http://www.npmjs.com/package/react)                                                   |       ✅        |
| [react-dom](http://www.npmjs.com/package/react-dom)                                           |       ✅        |
| [@material-ui/core](http://www.npmjs.com/package/@material-ui/core)                           |       ✅        |
| [@fortawesome/react-fontawesome](http://www.npmjs.com/package/@fortawesome/react-fontawesome) |       ✅        |

## Useful Tools

Along with this experiment, I was using the VsCode extension [Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost) and was precise along with the result. With the extension, you will be able to see right away how much an import will cost to the bundle. It will not tell you when you are getting a healthy or smelly bundle, but you can tell when an import cost is sus.

![Import Cost Live](https://camo.githubusercontent.com/08af5550483d9e9ccee846ebdbcb74332bdb523a738be4cb5aec8e4ba7df49e8/68747470733a2f2f636974772e6465762f5f6e6578742f696d6167653f75726c3d253246706f737473253246696d706f72742d636f73742532463171756f76335446706747327572376d79434c477473412e67696626773d3130383026713d3735)
> ###### Gif extracted from the [README](https://github.com/wix/import-cost#import-cost--) of Cost Import

## Conclusion

Tree Shaking have you covered! You can import however you want and you will be getting a healthy bundle **if and only if** the library has support for ES6 Module Syntax (`import` and `export`).

You can make your bundle unnecessary heavy if you don't cherry-pick the imports on libraries without ES6 module support, like [lodash](https://www.npmjs.com/package/lodash).

[Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost) can help you to spot an import that needs some refinement.

---

[^1]: The experiment is agnostic to the Framework or Library used, if webpack is involved in the bundling it would take the same effect.