May 28, 2020

JavaScript Modules

JavaScript modules is a scary term that means a pretty simple thing: files. Modules are usual files where you can have anything you want from a simple string to an entire library, export it and use it anywhere in your application. Only some modern browsers support modules out of the box so you need to set up Webpack or use RequireJS to use them if you haven't done it yet.

Imagine you have a function that calculates a sum of values in a given array and return a result. Like so:

// app.js

function total(numbers) {
    return numbers.reduce((a, b) => a + b, 0)
}

console.log(total([1, 2, 3, 4, 5]))

So you want to extract it to some file and use it anywhere in your code. All you need to do is to place this function in a separate file and export it using export keyword:

// total.js

function total(numbers) {
    return numbers.reduce((a, b) => a + b, 0)
}

export default total

Do not pay attention to the default keyword for now. Now we can import it using an import keyword:

// app.js

import total from './total.js'

console.log(total([1, 2, 3, 4, 5]))

We specify a variable name after an import keyword and a path to the file that exports it after a from keyword. In this case, we used the total variable name but we could use anything because we exported the function with a default keyword. That's what it means: when we export something with a default keyword we can import it using any variable we want. Also, note how we specify the path: it is started with ./. If you want to import local files you MUST use relative paths like ./ or ../. Otherwise, it will import a module from a node_modules directory. If you export a single variable it's advised to use default export. Here is the example of importing an axios library from node_modules:

import axios from 'axios'

Not default export

Sometimes we need to export multiple variables in a single file and it's perfectly valid. The only difference is that we do not use default keyword anymore but {} syntax like so:

// functions.js

function total(numbers) {
    return numbers.reduce((a, b) => a + b, 0)
}

function sum(a, b) {
    return a + b
}

export { total, sum }

Now we cannot import it using any variable we want, we can only use the ones we export:

// app.js

import { total, sum } from './functions.js'

Notice how we can import any number of variables from that file separated by a comma. We need to wrap variables in curly braces when we don't use default export.

Aliases

We can also use an alias to import some predefined variable but use it with the name we want:

import { total as totalQuantity, sum } from './functions.js'

What you can import

You can import anything your bundler supports, even CSS files:

import '../sass/app.scss'

You can also import simple JS files that don't export anything the same way as in the example above.

Common path conventions

You can omit the extension of js files. In this case, it will find the .js file with the given name. If there is no file with such a name it will find the directory with the given name that contains an index.js file and import it.

Pitfalls

You can use import only at the top level which means you cannot import a file even inside an if statement. You cannot also have a dynamic path in import. In these cases, you can utilize RequireJS. If your bundler supports usual exports and imports it supports RequireJS by default so you don't need to install anything. So imagine you need to place an import to the if statement:

if (someCondition) {
    const { total, sum } = require('./functions.js')
}

You can also use dynamic path if it's needed because now it is specified inside a usual function:

const file = 'functions.js'
const { total, sum } = require('./' + file)

I think you will find a lot of use cases where it can be very useful.

You can also import some default variable using the following syntax of ReqireJS:

const total = require('./total.js').default

I hope it was helpful. See you next time :)