Modules
Instead of writing all the code in one giant file, we can split the code into multiple files called modules
. Only things that are highly related should go in a module.
This increases maintainability, code reuse and abstraction (blackbox).
CommonJS (old) - Synchronous
// foo.js module
function foo() {
return 'bar';
}
module.exports.foo = foo;
// index.js use
const { foo } = require('foo');
ES6 - Must use Babel, can be asynchronous
// foo.js module
export function foo() {
return 'bar';
}
// index.js use
import { foo } from 'foo';
Every file in Node is considered a module. Everything declared inside is scoped to the file i.e. they are private.
Node does not give access to the global scope. In order to use the content of a module, we need to export it i.e. make it public.
var message = "hello";
console.log(global.message); // undefined
ES6 Modules
A proper format, unlike the CommonJS convention.
// add.js
export function add(a, b) {
return a + b;
}
// index.js
import { add } from "./add";
Modules are exported with export
and imported with import
. We can export one or more objects from a module.
There are default
and named
exports. We use a default export if there is a single object we want to export.
module
import { Person } from "./person";
// Named export
export function promote() {}
// Default export
export default class Teacher extends Person {
constructor(name, degree) {
super(name);
this.degree = degree;
}
teach() {
console.log("teach");
}
}
import
import Teacher, { promote } from "./teacher";
// Default -> import ... from "";
// Named -> import { ... } from "";
const teacher = new Teacher("John", "MSc");
teacher.teach();
Browsers
Must include the .js
file extension during import.
circle.js
// Implementation detail, not exported
const _radius = new WeakMap(); // private property
// Public interface i.e. exported part
export class Circle {
constructor(radius) {
_radius.set(this, radius);
}
draw() {
console.log("Circle with radius" + _radius.get(this));
}
}
index.js
import { Circle } from "./circle.js"; // Must include the .js file extension
const c = new Circle(10);
c.draw(); // Circle with radius 10
index.html
We need to add the module type in order to avoid the Uncaught SyntaxError: Unexpected token {
error, caused by the import
curly brace.
<script type="module" src="index.js"></script>
Old formats
Conventions/syntax for defining modules. ES6 natively supports them.
- CommonJS - Loads files synchronously.
Since ES5 doesn't support modules, developers came up with different syntaxes to define them. These are only used in legacy applications.
- AMD (Browser) - Asynchronous Module Definition.
- UMD (Browser / Node)- Universal Module Definition.
CommonJS (Node)
Two problems:
- Browsers cannot load files synchronously. This is solved via bundling a huge file including everything, even unused things.
- JS engine cannot tell what a module exports until it runs it.
// add.js
function add(a, b) {
return a + b;
}
module.exports = add;
// index.js
const add = require("./add"); // Loads synchronously
add(2, 3); // 5
CommonJS defines the:
module.exports
for exporting modules. It represents the object that is exported from a module.module
refers to the current module (file).exports
is an object and property ofmodule
.
require("./module")
for importing modules.
Single class export
When we import the foo.js
module, we directly get the Foo
class.
class Foo {}
module.exports = Foo;
Multiple class exports
We can import the exports
objects and access its properties.
class Foo {}
class Bar {}
module.exports.Foo = Foo;
module.exports.Bar = Bar;
Example
circle.js
// Implementation detail, not exported
const _radius = new WeakMap(); // private property
// Public interface i.e. exported part
class Circle {
constructor(radius) {
_radius.set(this, radius);
}
draw() {
console.log("Circle with radius" + _radius.get(this));
}
}
module.exports = Circle;
index.js
const Circle = require("./circle");
const c = new Circle(10);
c.draw(); // Circle with radius 10