Javascript Modules

Whats is a module ?

Basically modules are pieces of code that can be imported anywhere in your application. A module is really easily recognizable, as soon as you see the import or export keyword, you're dealing with a module.

  • So... pretty much everything is a module ?
  • What about the require keyword I've seen here and here ?
  • How do I use them ?

We'll cover everything from this point onward.

Grab a coffee, and let's get started.

A bit of history

As we can't really understand something without the knowledge of why it exist, we'll take a quick historical detour.

Waaaaay back in the days, nothing known as modules existed. But also, Javascript wasn't as widely used as it is today, it's only purpose was pretty much to add interactivity on your website.
Time goes on, and the use-cases grown as well. Javascript is more and more used, up to a point where JS was well-know for his monstruous single page codebase.

And nobody like 2000 lines long of code within a single file, right ?

So we needed a way to be able to split our code for obvious reasons.

Modules were born.

AMD, CommonJS, UMD

Unfortunaly, no specifications managed to solve this problem, so the community started to build tools that would solve the problem.

This is what AMD, Common JS and UMD are, tools to enable modularization within our projects.

From this point onward, we were able to do this :

// multiply.js
function multiply(a, b) {
    return a * b;
};

module.exports = multiply;
// index.js
var multiply = require('./multiply.js'); // Relative path to the file

var result = multiply(10, 10); // 100

We're now able to get the multiply function from any file by adding the correct require at the top level of your file.

2000 lines long files era is over !

ES6, export and import

Up to the ES6 release in 2015, we had to use such tools to split our codes and using modules. Now we got a standardized syntax and now work without such tools !

If we take back our previous example, this how it look like now :

// multiply.js
export function multiply(a, b) {
    return a * b;
};
// index.js
import { multiply } from './multiply.js' // Relative path to the file

const result = multiply(10, 10); // ES6 is here, we can now use const keyword 😉

Welcome to the present, let's dive straight into the ES6 modules !

Basics

I'll take functions as exemples because they are, in my opinion, the easiest way to understand modules. But you can do the exacte same behavior with all JS data types such as objects, arrays, etc.

Named exports/imports

You can either export this way :

export function multiply(a, b) {
    return a * b;
};

OR this way :

function multiply() { ... };

export { multiply };

Both are actually working and complety valid, they actually are doing the exact same thing. So what's the difference ? Well, let's say we have multiples things that we want to export, we can either add export in front of every functions ...

export function a() {...};
export function b() {...};
export function c() {...};
export function d() {...};

OR we could group all the export in one place :

function a() {...};
function b() {...};
function c() {...};
function d() {...};

export { a, b, c, d };

Let's now use our functions in a different file :

import { a, b, c, d } from './multiply.js';

Be very careful, the names of the values within the brackets must be the same as the exported ones

import { foo } from './multiply.js'; //<--- Will fail because there's nothing named 'foo' exported from the file

IMPORTANT NOTES

Exporting this way will give you references to the exported things, meaning that every modification on the value, will have an impact on all your imports.

Quick example :

const foo = {
    bar: true
};

// After 10 ms, we change the value of bar
setTimeout(() => {
    foo.bar = false;
}, 10);

export { foo };
import { foo } from './foo.js'

console.log(foo); // <--- { bar: false }

But what if the modification was caused by a function into a specific workflow ? You now have a global mutation, and lost track of the value of your object within your application. It may cause crashes and such terrible headaches debugging.

Default exports/imports

function multiply () {...};

export { multiply as default };

Adding the as default allow you to do the following :

import multiply from './multiply.js'; // <-- You're now free to remove the brackets

Oh, wait, before we move on, let's use the shortcut synthax for the export :

function multiply () {...};

export default multiply ;

OR

export default function multiply () {...};

Using a default exports has multiples consequences :

  • It no longer export a reference but a value, it may be changed witout impacts on other imports
  • You are free to name your imports variable however you want, it's no longer binded to the exported names

    import foo from './multiply.js'; // <-- You're still getting the multiply function, but it's renamed
    
  • You can only do ONE default export per module

IMPORTANT NOTES

There's actually a difference between

export { multiply as default };

and

export multiply as default;

But this is way past the goal of this article. Besides, Jake Archibald did an amazing blog post that you can found here that fully cover the subject.

Fancy but useful features

We covered the basics to get you started with the modules in JS, but there's some more syntax to know about imports/exports !

Mixing named and default exports/imports

//multiply.js
function multiply () {...};
function a() {...}

export { a };
export default multiply;

//index.js
import multiply, { a } from './multiply.js'; // <-- Work like a charm

Renaming

You're free to rename you imports/exports within the declaration :

// multiply.js
export { multiply as foo };

// index.js
import { foo } from './multiply.js'; // <-- Will give you the multiply function

foo();

OR

// multiply.js
export { multiply };

// index.js
import { multiply as foo } from './multiply.js'; 

foo();

This came become handy in case you have name conflicts.

Import everything at once

Let's say you have a pretty big list of exported things :

function a() {...};
function b() {...};
function c() {...};
function d() {...};

export { a, b, c, d };

Instead of importing them all one by one :

import { a, b, c, d } from './foo.js';

You're allowed to do this :

import * as whateverYouLike from './foo.js';

whateverYouLike.a();
whateverYouLike.b();
...

We'll wrap up here, I hope you enjoyed and learnt has much as I did writing this, feel free to reach me to discuss about it or if any mistakes were made.

You can find the original article on the Othrys website and you can follow my Twitter or tag me here to discuss about this article.

18