Joshua's Cheatsheets
Light
help

JS Modules - Cheatsheet

Resources

What Type Link
Flavio Copes guides to ES Modules Code examples Post
Tyler McGinnis's guide to Modules (very comprehensive) Comprehensive blog post Post
FreeCodeCamp (Preethi Kasireddy) post on Modules, lots of code samples FreeCodeCamp Post FreeCodeCamp
CanIUse for ES6 (ECMAScript 2015), which includes modules Browser Coverage CanIUse.com
ES6 vs CommonJS Examples Code Examples 2ality.com

What is a module?

Simply put, a module is a grouping of code into a distinct unit (the module) that is separated from other code both in physicality (separate file and/or code wrapping) and in functionality (variable scoping, namespaces, etc.)

Modules are isolated, yet reusable,

Native vs Non-Native

Modules have historically not been supported natively in JS, so many environments (nodejs) used a non-native implementation/flavor of modules, such as CommonJS.

The terminology has gotten a little convoluted over time, but currently there is really only one native JS module format, usually referred to as ES6 Modules or just ES Modules.

  • These are very common, in both JS and TS

There are many non-native module solutions:

  • CommonJS
  • AMD (Asynchronous Module Definition)
  • UMD (Universal Module Definition)

These non-native formats require loaders, such as requirejs, in most environments, such as the browser.

Handcoded / Generic modules

Because the term "module" is generic and just refers to the idea of isolated building blocks, there are many ways to build modules that don't rely on a standardized definition/spec like CommonJS. In fact, a basic IIFE (Immediately Invoked Function Expression) could be considered a module format, since it provides closure/scoped vars:

// Module 'customDebug'
(function(){
    // These variables are local scoped to IIFE/module
    var defaultDebugLevel = 1;
    var currentDebugLevel = 1;
    var customDebug = {
        setDebugLevel: function(level){
            currentDebugLevel = level;
        },
        logMsg: function(msg,dbgLevel){
            if (dbgLevel <= currentDebugLevel){
                console.log(msg);
            }
        }
    }
    // This will expose module to global namespace
    App = typeof(App)!=='undefined' ? App : {};
    App.customDebug = customDebug;
})()

Syntax differences between module options:

Module Type Import Syntax Export Syntax Notes
CommonJS const fs = require('fse');

Using ES6 Object Destructuring:
const {foo, bar} = require('myfile.js');
Per file, define module.exports (can be function, object, etc.)

If you want to emulate ES6 export default, just assign a single thing to module.exports, e.g. module.exports = function(){};
- Used by NodeJS
- Default is one def per file
AMD require(['dependencyAlpha','dependencyBeta'],function(depA,depB){ // Do something }); define('moduleName',['dependencyAlpha'],function(depA){ // Return what the module should be equal to }); - Unlike CJS, can have multiple defs per file
UMD UMD Example from FreeCodeCamp
From FreeCodeCamp
Depends on environment, see code example for import to see how it exposes the factory - Basically a combination of AMD+CommonJS.
- Flexibility allows it to work in multiple environments
- Trade-off is less legible generated code
ES Modules import ModuleA from 'my-modules';
import {ModuleA,ModuleB} from 'my-modules';
import * as myModules from 'my-modules';

Dynamic importing* (limited support) via:
import('./lib/my-module').then((module)=>{});
or:
await module = await import('./lib/my-module');

Importing via <script type="module"> tag in the browser is on the way.
Just prefix basically anything with the export or export default keyword.

export const getUsernameById = (userId) => {//...};
- You can have multiple named exports per file, one default per file
- Named exports are preferred over export default, for tree-shaking

ES Modules - further notes

Dynamic imports

As noted in the table above, dynamic imports in ES Modules is a newer feature, that is not fully supported everywhere.

  • Node:

    • Supported V9.7+ (reminder, you can check from CLI with node -v)
  • Browser:

    • A TC39 proposal, which should land fully in ES2020
    • CanIUse link - As of 10/24/2019, around 84% support

Here is a nice write-up of the feature and some examples on how to use it: v8.dev

Why default export is bad

If you start looking up information about ES Module exports, you will likely start seeing posts and headlines talking about why export default is bad and should be avoided.

Here is a brief summary of the faults of export default

  • Makes it harder, or impossible, for bundling / minifying programs to "tree-shake", e.g. remove dead un-used code.

    • If you are concerned about performance and keeping request size down, this is important.
  • (Arguable) - Decreases readability and increases ambiguity

    • To me, using default exports all over the place can lead to some readiblity issues, because it tends to encourage aliasing and renaming. Example:

      • In one file, a dev might choose to use import * as couch from './furniture/sku-24.js'
      • In another file, another dev might choose to import the same default as import * sofa from './furniture/sku-24.js'
      • If I'm just eye-balling these files, and not looking at the top of every file, I might not notice at first that these are pulling from the same file. And searching for specific imports might give me trouble too.
  • (Arguable) - Although it makes refactoring harder, it also makes it more explicit

Benefits of export default

  • Easier to refactor

    • Because you can easily alias without knowing the actual name of the export within the file, if that export is changed, you don't need to update the files that consume it
    • However, this same benefit could be achieved by using a class as a named export instead of default, and changing the names of methods in the class instead
  • Can make writing code a tiny bit faster, since there is slightly less boilerplate

Due to the mix of drawbacks and benefits, some people prefer to use a mix. For example, if your file is mostly centered around a class, let's say Person, you might export the class as the default, but then export things related to that class as named exports. For example:

export default class Book {
	constructor({name, author}) {
		this.name = name;
		this.author = author;
	}
	//.. tons of members, methods, etc.
}

// Named export
export const oz = new Book({
	name: 'The Wonderful Wizard of Oz',
	author: 'L. Frank Baum'
});

If you are looking for a thread full of arguments on the topic, this Airbnb JS style guide issue thread is a pretty lively read.

Markdown Source Last Updated:
Wed Nov 13 2019 09:04:31 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Wed Aug 21 2019 00:46:11 GMT+0000 (Coordinated Universal Time)