# Modules

# Why use modules?

It makes sense to split JavaScript programs up into separate modules that can be imported when needed.

There are a lot of benefits to using modules:

1) Maintainability: Updating a single module is much easier

2) Namespacing: Sharing global variables between unrelated code is a big no-no in development (opens new window). Modules allow us to avoid namespace pollution by creating a private space for our variables.

3) Reusability

Modern browsers support module functionality natively.


# What is a module? (opens new window)

Modules are reusable pieces of code in a file that can be exported and then imported for use in another file.

-β€Ί separation of concerns

Modules can load each other and use export and import to interchange functionality, call functions of one module from another one:

  • export keyword labels variables and functions that should be accessible from outside the current module
  • import allows the import of functionality from other modules.
// πŸ“ sayHi.js
export function sayHi(user) {
  alert(`Hello, ${user}!`);
}

another file may import and use it:

// πŸ“ main.js
import {sayHi} from './sayHi.js';

sayHi('John'); // Hello, John!
# Import in HTML

type="module" is nescessary, otherwise it won't work

<script type="module">
	import { sayHi } from './sayHi.js';
	document.body.innerHTML = sayHi('John');
</script>

or

<script src="app.js" type="module"></script>

Modules work only via HTTP(s), not locally -> Use a local web-server like "live-server"

# Module-level scope

  • Each module has its own top-level scope -> each module has independent variables

  • Modules should export what they want to be accessible from outside and import what they need.

  • Use import/export instead of relying on global variables.

# A module code is evaluated only the first time when imported

If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers.

# In a module, β€œthis” is undefined (opens new window)

<script>
  alert(this); // window
</script>

<script type="module">
  alert(this); // undefined
</script>

# import/export - Syntax- (ES6 / ESM)

Syntax for natively implementing modules was only introduced in 2015 with the release of ECMAScript 6 (ES6) (opens new window).

ESM stands for ES Modules.


# ES6 Import Syntax

import { exportedResourceA, exportedResourceB } from '/path/to/module.js';

you must also update the html: add the attribute type='module' to the <script> element.

 <script type="module" src="./secret-messages.js"> </script>
  </body>

# Renaming Imports

-> to Avoid naming Collisions

ES6 includes syntax for renaming imported resources by adding in the keyword as.

import { exportedResource as newlyNamedResource } from '/path/to/module'
// index.js

import { ninjas, greet } from './module.js'

console.log(ninjas);
greet()

or

import * as module from './module.js';

console.log(module.ninjas);
module.greet();

# Import entire content

import * as myModule from '/modules/my-module.js';

call it like this:

myModule.functionFromTheModule();

# Import a single export

import {myExport} from '/modules/my-module.js';

# Import multiple exports

import {foo, bar} from '/modules/my-module.js';

# Import with alias

import {reallyReallyLongModuleExportName as shortName}
  from '/modules/my-module.js';
import {
  reallyReallyLongModuleExportName as shortName,
  anotherLongModuleName as short
} from '/modules/my-module.js';

# ES6 Named Export Syntax

The name of each exported resource is listed between curly braces and separated by commas

export { resourceToExportA, resourceToExportB, ...}

individual values may be exported as named exports by placing the export keyword in front of the declaration.

export const toggleHiddenElement = (domElement) => {
  // ...
}
 
export const changeToFunkyColor = (domElement) => {
  // ...
}
# Examples
// Exporting individual features
export let name1, name2, nameN; // also var, const
export function functionName(){...}
export class ClassName {...}

// Export list
export { name1, name2 };

// Renaming exports
export { variable1 as name1, variable2 as name2 };

// Exporting destructured assignments with renaming
export const { name1, name2: bar } = object;
// ninjas.js
export const ninjas = ['shaun', 'yoshi', 'mario', 'peach'];

export const greet = () => {
  console.log(ninjas[0] + ' says hello');
};

or:

// ninjas.js
const ninjas = ['shaun', 'yoshi', 'mario', 'peach'];

const greet = () => {
    console.log(ninjas[0] + ' says hello');
}

export { ninjas, greet }

You can also rename named exports to avoid naming conflicts:

export { myFunction as function1,
         myVariable as variable };

# Default Exports

Every module also has the option to export a single value to represent the entire module called the default export.

  • can be an object containing the entire set of functions and/or data values of a module.

  • syntax : as default renames the exported object to default, a reserved identifier that can only be given to a single exported value.

  • import can be any name

  • only 1 default export!

const resources = { 
  valueA, 
  valueB 
}
export { resources as default };

shorthand syntax:

const resources = {
  valueA,
  valueB
}
export default resources;

or:

export default {
  valueA,
  valueB
}
// Default exports
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
// module "my-module.js"

export default function cube(x) {
  return x * x * x;
}
import cube from './my-module.js';
console.log(cube(3)); // 27

# Default Import

import importedResources from 'module.js';

Notice that the import statement has no curly braces . This syntax is shorthand for:

import { default as importedResources } from 'module.js

if the default export is an object, the values inside cannot be extracted until after the object is imported, like so:

// This will work...
import resources from 'module.js'
const { valueA, valueB } = resources;
 
// This will not work...
import { valueA, valueB } from 'module.js'

Examples

import myDefault from '/modules/my-module.js';

combined:

import myDefault, * as myModule from '/modules/my-module.js';
// myModule used as a namespace

or

import myDefault, {foo, bar} from '/modules/my-module.js';
// specific, named imports

Example:

/* dom-functions.js */
const toggleHiddenElement = (domElement) => {
   //...
}
 
const changeToFunkyColor = (domElement) => {
	//...
}
 
const resources = { 
  toggleHiddenElement, 
  changeToFunkyColor
}
export default resources;

->

// secret-messages.js
import domFunctions from '../modules/dom-functions.js';
 
const { toggleHiddenElement, changeToFunkyColor } = domFunctions;
 
const buttonElement = document.getElementById('secret-button');
const pElement = document.getElementById('secret-p');
 
buttonElement.addEventListener('click', () => {
  toggleHiddenElement(pElement);
  changeToFunkyColor(buttonElement);
});
  • Can be called in HTML:
<script type="module">
  import {func1} from 'my-lib';

  func1();
</script>

# Import a module for its side effects only (opens new window)

Import an entire module for side effects only, without importing anything. This runs the module's global code, but doesn't actually import any values.

import '/modules/my-module.js';

This works with dynamic imports (opens new window) as well:

(async () => {
  if (somethingIsTrue) {
    // import module for side effects
    await import('/modules/my-module.js');
  }
})();

# Dynamic Imports (opens new window)


# require - Syntax (Node - CJS)

  • node uses CJS module format (opens new window).
  • CJS imports module synchronously
  • When CJS imports, it will give you a copy of the imported object.
  • CJS will not work in the browser.
  • modern versions of node can also use import/export

https://www.sitepoint.com/understanding-module-exports-exports-node-js/ (opens new window)

# Export: module.exports

To make functions available to other files, add them as properties to the built-in module.exports object:

module.exports is an object that is built-in to the Node.js runtime environment. Other files can now import this object, and make use of these functions, with the require() function.

/* converters.js */
function celsiusToFahrenheit(celsius) {
  return celsius * (9/5) + 32;
}
 
module.exports.celsiusToFahrenheit = celsiusToFahrenheit;
 
module.exports.fahrenheitToCelsius = function(fahrenheit) {
  return (fahrenheit - 32) * (5/9);
};

or

module.exports = celsiusToFahrenheit

function requestHandler(req, res) { 
	// ...
}

// or:

// const requestHandler = (req, res) => {
//		// ...
// }

module.exports = requesthandler

or to use as: modulename.handler

module.exports = {
	handler: requestHandler,
	someText: 'Some hard coded text'
}

or

module.exports.handler = requestHandler;
module.exports.someText = 'Some hard coded text'

or shortcut:

exports.handler = requestHandler;
exports.someText = 'Some hard coded text'

# Function directily in the Object:

module.exports = {
    circleArea:  (radiusLength) => {
    return PI * radiusLength * radiusLength
  },
    squareArea: function (sideLength) {
    return sideLength**2
  }
}

or

module.exports = {
  circleArea (radiusLength) {
  	return Math.PI * radiusLength * radiusLength
  },
  squareArea (sideLength) {
  	return sideLength**2
  )
}

# Import: require()

Use require -> node will look for module.exports

The require() function accepts a string as an argument. That string provides the file path (opens new window) to the module.

/* water-limits.js */
const converters = require('./converters.js');

./ is a relative path indicating that converters.js is stored in the same folder as water-limits.js. When you use require(), the entire module.exports object is returned and stored in the variable converters.

All exported methods can be used by converters.methodName

# Examples:
const module = require('./module.js')
const http = require('http')

const routes = require ('./routes,js') // custom module

const server = http.createServer(routes)
//-> will use the function stored in routes for incoming requests

server.listen(3000)

# Object Destructuring with require()

You can use object destructuring to extract only the needed functions.

/* celsius-to-fahrenheit.js */
const { celsiusToFahrenheit } = require('./converters.js');
const fahrenheitValue = celsiusToFahrenheit(input);

-> you can access the function directly


MDN export (opens new window)

MDN import (opens new window)

https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm (opens new window)

https://blog.risingstack.com/node-js-at-scale-module-system-commonjs-require/ (opens new window)

https://dev.to/bennypowers/you-should-be-using-esm-kn3 (opens new window)

MDN JS Modules (opens new window)

javascript.info/modules-intro (opens new window)

freecodecamp - Modules Beginners guide (opens new window)