Today there are two module systems that are actively being used. CommonJS (CJS) is what Node.js has used historically. ESM (EcmaScript modules) is a newer system which has been added to the JavaScript specification. Browsers already support ES modules, and Node is adding support.

CommonJS and ESM

CommonJS was primarily intended for server-side development with Node.js. It is named ServerJS before. CommonJS’s specification of how modules should work is widely used today for server-side JavaScript with Node.js. It is also used for browser-side JavaScript, but that code must be packaged with a transpiler since browsers don’t support CommonJS.

The other major module specification in use is the ECMAScript (ES) modules specification (ES6 modules aka ES2015 modules). CommonJS can be recognized by the use of the require() function and module.exports, while ES modules use import and export statements for similar (though not identical) functionality.

CommonJS and ES modules have different syntax.

CommonJS module that exports two functions:

module.exports.add = function(a, b) {
  return a + b;
} 
module.exports.subtract = function(a, b) {
  return a - b;
} 

We can also import the public functions into another Node.js script using require, just as we do here:

const {add, subtract} = require('./util')

console.log(add(5, 5)) // 10
console.log(subtract(10, 5)) // 5

ES modules:

export function add(a, b) {
  return a + b;
}
export function subtract(a, b) {
  return a - b;
}

We can then import both functions using the import statement:

import {add, subtract} from './util.mjs'

console.log(add(5, 5)) // 10
console.log(subtract(10, 5)) // 5

CommonJS vs ECMAScript modules

  • Node.js support for ES modules too.
  • CommonJS offers flexibility with module imports.
  • CommonJS loads modules synchronously, ES modules are asynchronous.
  • All in all, ECMAScript modules are the future of JavaScript.

ECMAScript modules

Export

After the export keyword, you can use let, const, and var declarations, as well as function or class declarations. You can also use the export { name1, name2 } syntax to export a list of names declared elsewhere.

Note that export {} does not export an empty object — it’s a no-op declaration that exports nothing (an empty name list).

JS have default export and named export. JS have export from syntax as well.

Default exports

Default export is used to export a single value as the default value for a module. This value can be a variable, function, class, or any other JavaScript entity. When importing a default export, you can assign it any name you want in the importing module.

Example:

// moduleA.js
const myDefault = 'Default Value';
export default myDefault;

// moduleB.js
import myAlias from './moduleC';

Named exports

Named exports allow you to export multiple values from a module and give each of them a specific name. You can import these values by using their respective names when importing in another module.

Example:

// moduleC.js
export const foo = 'Foo';
export function bar() {
    // function implementation
}
const foobar = 'FooBar';
export {foobar};

// moduleD.js
import { foo, bar, foobar } from './moduleA';

Mix them

you can mix both named and default exports in the same module:

// moduleE.js
export const namedExport = 'Named Export';
const defaultExport = 'Default Export';
export default defaultExport;

// moduleF.js
import myDefault, { namedExport } from './moduleE';

More info at here.

Import

The static import declaration is used to import read-only live bindings which are exported by another module. The imported bindings are called live bindings because they are updated by the module that exported the binding, but cannot be re-assigned by the importing module.

There are four forms of import declarations:

  • Named import: import { export1, export2 } from "module-name";
  • Default import: import defaultExport from "module-name";
  • Namespace import: import * as name from "module-name";
  • Side effect import: import "module-name";
import myDefault, * as myModule from "/modules/my-module.js";
// or
import { default as myDefault } from "/modules/my-module.js";
// or
import myDefault, { foo, bar } from "/modules/my-module.js";

CommonJS moduels

Exporting

Identifiers are exported via setting the exports property on a global called module.

function absolute(num: number) {
  if (num < 0) return num * -1;
  return num;
}
 
module.exports = {
  pi: 3.14,
  squareTwo: 1.41,
  phi: 1.61,
  absolute,
};

Then these files can be imported via a require statement:

const maths = require("./maths");
maths.pi; // any

Or you can simplify a bit using the destructuring feature in JavaScript:

const { squareTwo } = require("./maths");
squareTwo; // const squareTwo: any

Deep dive module

What problem do modules solve

When you think about it, coding in JavaScript is all about managing variables. It’s all about assigning values to variables, or adding numbers to variables, or combining two variables together and putting them into another variable.

Because so much of your code is just about changing variables, how you organize these variables is going to have a big impact on how well you can code and how well you can maintain that code.

Having just a few variables to think about at one time makes things easier.

JavaScript has a way of helping you do this, called scope. It also has a downside, though. It does make it hard to share variables between different functions. The common way to handle this is to put it on a scope above you, for example, on the global scope. This makes maintaining code tricky. And because these variables are on the global scope, every part of the code that’s inside of that global scope can change the variable.

How do modules help

Modules give you a better way to organize these variables and functions. With modules, you group the variables and functions that make sense to go together. But unlike function scopes, module scopes have a way of making their variables available to other modules as well.

When something is made available to other modules, it’s called an export. Once you have an export, other modules can explicitly say that they depend on that variable, class or function.

Once you have the ability to export and import variables between modules, it makes it a lot easier to break up your code into small chunks that can work independently of each other.

How ES modules work

When you’re developing with modules, you build up a graph of dependencies. The connections between different dependencies come from any import statements that you use.

TypeScript and Modules

How JavaScript Modules are Defined

In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered a module.

Conversely, a file without any top-level import or export declarations is treated as a script whose contents are available in the global scope (and therefore to modules as well).

Modules are executed within their own scope, not in the global scope. This means that variables, functions, classes, etc. declared in a module are not visible outside the module unless they are explicitly exported using one of the export forms. Conversely, to consume a variable, function, class, interface, etc. exported from a different module, it has to be imported using one of the import forms.

References