Overview
Modules are small units of independent, reusable code that is desired to be used as the building blocks in creating a non-trivial Javascript application. Modules let the developer define private and public members separately, making it one of the more desired design patterns in JavaScript paradigm.
Export and import directives are very versatile.
Export before declarations
We can label any declaration as exported by placing export
before it, be it a variable, function or a class.
For instance, here all exports are valid:
// export an array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
export class User {
constructor(name) {
this.name = name;
}
}
Please note that export
before a class or a function does not make it a function expression. It’s still a function declaration, albeit exported.
Most JavaScript style guides recommend semicolons after statements, but not after function and class declarations.
That’s why there should be no semicolons at the end of export class
and export function
.
export function sayHi(user) {
alert(`Hello, ${user}!`);
} // no ; at the end
Export apart from declarations
Also, we can put export
separately.
Here we first declare, and then export:
// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
export {sayHi, sayBye}; // a list of exported variables
Import
Usually, we put a list of what to import into import {...}
, like this:
// 📁 main.js
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!
But if the list is long, we can import everything as an object using import * as <obj>
, for instance:
// 📁 main.js
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
At first sight, “import everything” seems such a cool thing, short to write, why should we ever explicitly list what we need to import?
Well, there are few reasons.
- Modern build tools (webpack and others) bundle modules together and optimize them to speedup loading and remove unused stuff.Let’s say, we added a 3rd-party library
lib.js
to our project with many functions:
/ 📁 lib.js
export function sayHi() { ... }
export function sayBye() { ... }
export function becomeSilent() { ... }
- …Then the optimizer will automatically detect it and totally remove the other functions from the bundled code, thus making the build smaller. That is called “tree-shaking”.
- Explicitly listing what to import gives shorter names:
sayHi()
instead oflib.sayHi()
. - Explicit imports give better overview of the code structure: what is used and where. It makes code support and refactoring easier.
Import “as”
We can also use as
to import under different names.
For instance, let’s import sayHi
into the local variable hi
for brevity, and same for sayBye
:
// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';
hi('John'); // Hello, John!
bye('John'); // Bye, John!
Export “as”
The similar syntax exists for export
.
Let’s export functions as hi
and bye
:
// 📁 say.js
...
export {sayHi as hi, sayBye as bye};
// 📁 main.js
import * as say from './say.js';
say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!
export default
So far, we’ve seen how to import/export multiple things, optionally “as” other names.
In practice, modules contain either:
- A library, pack of functions, like
lib.js
. - Or an entity, like
class User
is described inuser.js
, the whole module has only this class.
Mostly, the second approach is preferred, so that every “thing” resides in its own module.
Naturally, that requires a lot of files, as everything wants its own module, but that’s not a problem at all. Actually, code navigation becomes easier, if files are well-named and structured into folders.
Modules provide special export default
syntax to make “one thing per module” way look better.
It requires following export
and import
statements:
- Put
export default
before the “main export” of the module. - Call
import
without curly braces.
For instance, here user.js
exports class User
:
// 📁 user.js
export default class User { // just add "default"
constructor(name) {
this.name = name;
}
}
// 📁 main.js
import User from './user.js'; // not {User}, just User
new User('John');
Imports without curly braces look nicer. A common mistake when starting to use modules is to forget curly braces at all. So, remember, import
needs curly braces for named imports and doesn’t need them for the default one.
Naturally, there may be only one “default” export per file.
We may have both default and named exports in a single module, but in practice people usually don’t mix them. A module has either named exports or the default one.
Another thing to note is that named exports must (naturally) have a name, while export default
may be anonymous.
For instance, these are all perfectly valid default exports:
export default class { // no class name
constructor() { ... }
}
export default function(user) { // no function name
alert(`Hello, ${user}!`);
}
// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Categories: #Javascript Tags: #Programming #Javascript #Module #Import Export