JavaScript Modules: A walkthrough.
Tags: javascript, typescript, node, es6
Published: 2021-05-31 15:18:13

In JavaScript, modules are pieces of encapsulated code that can reused in different parts of your application or program whenever you need it.

Basically you can write your JavaScript code without modules but breaking down your code into multiple reusable modules helps you to keep your code clean, readable, reusable and mode manageable when you have dependencies.

The syntax for modules was officially introduced in ES6. Let's have a look at the various ways we can achieve this:

#### 1. Asynchronous Module Definition (AMD):
This method is used for the browser environments. Large web applications require many external modules. Now, the traditional way of loading them is using the `<script>` element. When using `<script>` the browser loads them in a synchronous(sequential) fashion. Now it would be much better if we can load those external modules in parallel provided that there are no dependencies among each other.
So, lets have a look at the Asynchronous Module Definition(AMD) syntax:

```javascript
define('yourModuleName',
    ['foo', 'bar'], /* dependencies(optional) */
    // module definition function
    // dependencies (foo and bar) are mapped to function parameters
    function (foo, bar) {
        // return a value that defines the module export
        // (i.e the functionality we want to expose for consumption)

        // create your module here
        var yourModuleName = {
            someAction: function () {
                console.log('This is a log statement!');
            }
        }

        return yourModuleName;
    });
```

#### 2. CommonJS Format:
This format is used in NodeJS. It uses `module.export` and `require` keywords to define and consume the modules.
Let's have a look at the syntax:
```javascript
/* file: yourModule.js */
exports.findLength = str => str.length;

/* otherFile.js */
const stringFunc = require("yourModule.js");
const val = stringFunc.findLength("I love pancakes!");
console.log(val) // 16
```
You can export more than one function or value and `require` them using destructuring.
```javascript
/* file: yourModule.js */
exports.findLength = str => str.length;
exports.changeToCaps = str => str.toUpperCase();
exports.changeToLowCaps = str => str.toLowerCase();

/* otherFile.js */
const {findLength, changeToCaps, changeToLowCaps} = require("yourModule.js");
const val = findLength("I love pancakes!");
const upper = changeToCaps("I love burgers!");
const lower = changeToLowCaps("Stop talking about food!")
```

### 3. Universal Module Definition (UMD):
Well, this pattern for JavaScript modules are efficient to work everywhere, be it a client on server or elsewhere. The main attempt of this format is compatibility.
The pattern is a bit verbose, but is both AMD and CommonJS compatible, as well as supporting the “global” variable definition style.
```javascript
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        /* AMD. 
        Register as an anonymous module. */
        define(['b'], factory);
    } else if (typeof module === 'object' && module.exports) {
        /* Node. 
        Does not work with strict CommonJS, but
        only CommonJS-like environments that support module.exports,
        like Node. */
        module.exports = factory(require('b'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.b);
    }
}(this, function (b) {
    const findLength = str => str.length;
    const changeToCaps = str => str.toUpperCase();
    const changeToLowCaps = str => str.toLowerCase();

    return ({
        findLength,
        changeToCaps,
        changeToLowCaps
    });
}));
```

### 4. System.register:
System.register can be considered as a new module format designed to support the exact semantics of ES6 modules within ES5.
Let's take an example here, the following module is defined in ES6 syntax:
```javascript
import dependencyModule1 from "./dependencyModule1.js";
dependencyModule1.api1();

// Named export:
export const findLength = function (str) { return str.length };
export const changeToCaps = function (str) { return str.toUpperCase() };
export const changeToLowCaps = function (str) { return str.toLowerCase() };

// Or default export:
export default {
    findLength,
    changeToCaps,
    changeToLowCaps
}
```
The above in System.register format:
```javascript
System.register(["./dependencyModule1.js"], function (_export, _context) {
    "use strict";
    var dependencyModule1, findLength, changeToCaps, changeToLowCaps;
    var __moduleName = _context && _context.id;
    return {
        setters: [
            function (dependencyModule1) {
                dependencyModule1 = dependencyModule1;
            }
        ],
        execute: function () {
            dependencyModule1.default.api1();
            // Named export:
            _export("findLength", findLength = function (str) { return str.length };
            _export("changeToCaps", changeToCaps = function (str) {
                return str.toUpperCase();
            };);
_export("changeToLowCaps", changeToLowCaps = function (str) {
    return str.toLowerCase();
};);
// Or default export:
_export("default", {
    findLength,
    changeToCaps,
    changeToLowCaps
});
        }
    };
});
```

### 5. ES6 module:
The goal for ES6 modules was to create a format that both users of CommonJS and of AMD are content. The basic syntax is `import` and `export` keyword.
You can use the `export` keyword for exporting the public API of a module.
```javascript
export const findLength = function (str) { return str.length };
export const changeToCaps = function (str) { return str.toUpperCase() };
export const changeToLowCaps = function (str) { return str.toLowerCase() };
export default theDefaultFunc = function () {console.log("This is a default export!!")}
```
And use the `import` keyword for importing it for use.
```javascript
// So, this is how you make default import and named imports
import theDefaultFunc, {findLength, changeToCaps, changeToLowCaps} from "./yourModuleFile.js"

//  You can use alias while importing
import theDefaultFunc as defFunc from "./yourModuleFile.js"

// You can also import a complete module like below
import * from "./someModule.js"
```
For browser, `<script>`’s nomodule attribute can be used as a fallback option.
```html
<script nomodule>
    alert("Module not supported.");
</script>
```
JavaScript has standard built-in language features for modules. This is supported in NodeJS but not by all the browser versions yet. But, we can already use the ES6 module format. We need a transpiler like Babel, Webpack etc. to transpile our code to an ES5 module format such as AMD or CommonJS before we can actually run our code in the browser.