DEV Community

Cover image for Importing modules in JavaScript, are we doing it right?
Diego Juliao
Diego Juliao

Posted on

Importing modules in JavaScript, are we doing it right?

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:

import * as lib from 'amazing-lib'
import { func1 } from 'amazing-lib'
Enter fullscreen mode Exit fullscreen mode

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?

Will not all the unused library's code end up on the final bundle making my application unnecessary heavier?

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!


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.


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

import * as lib from 'amazing-lib'
import { foo } from 'amazing-lib'
Enter fullscreen mode Exit fullscreen mode

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:

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

export const justAConst = 'foobar'
Enter fullscreen mode Exit fullscreen mode

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:

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

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

import { small } from 'common-js-lib/small';
Enter fullscreen mode Exit fullscreen mode

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:

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

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

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. All this was created in a Monorepo using Nx, the library's npm publishing was mocked using yalc. The bundle analysis was made using 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 ❌
moment ❌
rxjs βœ…
lodash-es βœ…
date-fns βœ…
@angular/core βœ…
@angular/material βœ…
react βœ…
react-dom βœ…
@material-ui/core βœ…
@fortawesome/react-fontawesome βœ…

Useful Tools

Along with this experiment, I was using the VsCode extension 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

Gif extracted from the README 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.

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

Discussion (2)

Collapse
ahmedghazi profile image
Ahmed

what about lazy import?

Collapse
dianjuar profile image
Diego Juliao Author

Definetly could help to load your app faster but if you don't apply this advices to your modules lazy loaded they could being heavier than they should be.