Why do we need modules anyway ?
Javascript was not always as large as we see it right now — earlier it was used to majorily automate tasks, providing a bit of interactivity to your web pages where needed. So huge scripts were never in the picture. Fast forward a decade and we hardly have any webpage that does not use js, more over it has penitrated into other domains as well (Eg node.js, tensorflow.js).
Hence it makes sense to split up our code into small chunks, something like components of
a car, that each one of them serves a different purpose, can be used by other car
models, and it can be replaced by other companies making the same component as well.
A module is just a file. One script can be one module.
The ECMAScript 5 module systems
In that era , module systems were implemented via libraries, not built into the language. ES6 is the first time that JavaScript has built-in modules.Two popular ones are:
- CommonJS (targeting the server side)
- AMD (Asynchronous Module Definition, targeting the client side)
CommonJS
Originally CommonJS for modules was created for server platforms majorily. It achieved enormous popularity in the original Node.js module system. Contributing to that popularity were the npm package manager for Node and tools that enabled using Node modules on the client side (browserify, webpack, and others).This is an example of a CommonJS module:
// Imports
var importedFunc1 = require('./other-module1.js').importedFunc1;
var importedFunc2 = require('./other-module2.js').importedFunc2;
// Body
function internalFx() {
// ···
}
function exportedFx() {
importedFunc1;
importedFunc2;
internalFx();
}
// Exports
module.exports = {
exportedFunc: exportedFunc,
};
AMD (Asynchronous Module Definition) modules
The AMD module system was created to be used in browsers than the CommonJS format. Its most popular implementation is RequireJS. The following is an example of an AMD module.
define(['./other-module1.js', './other-module2.js'],
function (otherModule1, otherModule2) {
var importedFunc1 = otherModule1.importedFunc1;
var importedFunc2 = otherModule2.importedFunc2;
function internalFunc() {
// ···
}
function exportedFunc() {
importedFunc1();
importedFunc2();
internalFunc();
}
return {
exportedFunc: exportedFunc,
};
});
So, how to module in ES6 ?
ECMAScript modules (ES modules or ESM) were introduced with ES6. They continue the tradition of JavaScript modules and have all of their aforementioned characteristics. Additionally:
- With CommonJS, ES modules share the compact syntax and support for cyclic dependencies.
- With AMD, ES modules share being designed for asynchronous loading.
ES modules also have new benefits:
- The syntax is even more compact than CommonJS’s.
- Modules have static structures (which can’t be changed at runtime). That helps with static checking, optimized access of imports, dead code elimination, and more.
- Support for cyclic imports is completely transparent.
This is an example of ES module syntax:
import {importedFunc1} from './other-module1.mjs';
import {importedFunc2} from './other-module2.mjs';
function internalFunc() {
···
}
export function exportedFunc() {
importedFunc1();
importedFunc2();
internalFunc();
}
Modules can load each other and use special directives export and import to interchange functionality and 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.
Named imports awesomeness
You can import directly via name,
import {square} from './lib/my-math.mjs';
assert.equal(square(3), 9);
Or even by renaming it, if it conflicts it with some of your local declarations
import {square as sq} from './lib/my-math.mjs';
assert.equal(sq(3), 9);
Remember that named import is not distructing !
Although both named importing and destructuring look similar:
import {foo} from './bar.mjs'; // import
const {foo} = require('./bar.mjs'); // destructuring
But they are quite different:
- Imports remain connected with their exports.
You can destructure again inside a destructuring pattern, but the {} in an import statement can’t be nested.
The syntax for renaming is different:
import {foo as f} from './bar.mjs'; // importing
const {foo: f} = require('./bar.mjs'); // destructuring
Namespace imports awesomeness
Namespace imports can be treated as alternative to named imports. If we namespace-import a module, it becomes an object whose properties are the named exports. For eg
// Module my-math.js has two named exports: square and LIGHTSPEED.
function times(a, b) {
return a * b;
}
export function square(x) {
return times(x, x);
}
export const LIGHTSPEED = 299792458;
import * as myMath from './lib/my-math.mjs'; <--Namespace imports
assert.equal(myMath.square(3), 9);
assert.deepEqual(
Object.keys(myMath), ['LIGHTSPEED', 'square']);
I always had an issue with getting my head around these different types of imports, so I spent some time looking around it. I hope you found this use-full. Thanks for the time. ❤️
Top comments (2)
Great article - I also have a problem with import * and the like... This helped!
Great Explanation ! Totally loved reading this.