JavaScript modules allow us to divide code into small pieces. They also let us keep some code private while exposing other pieces of code that can be imported into another module.
In this article, we’ll look at how to use define and use them.
Named Exports
Names exports start with the export
keyword and they expose items from a module to the outside. Then we can import them somewhere else.
There can be multiple named exports in one module. For instance, we can write:
export const foo = 1;
export let bar = 1;
Then we can import them into another module as follows:
import { foo, bar } from "./module";
const baz = foo + bar;
We can also import the whole module with the *
sign as follows:
import * as module from "./module";
const baz = module.foo + module.bar;
Default Export
We can export one default export using the export default
keywords. For instance, we can write:
export default 1;
Then import it as follows:
import foo from "./bar";
const baz = foo;
We can also export functions and class as follows:
export default () => {};
or:
export default class {}
We don’t need a semicolon at the end of the class export.
Also, we can define default exports with the following code:
const foo = 1;
export { foo as default };
Browser Differences Between Scripts and Modules
In browsers, scripts are denoted by the script
tag. Modules are the same, but it’s denoted by the type attribute with value module
.
Scripts are not in strict mode by default, while modules are in strict mode by default.
Top-level variables are global in scrips and are local to the module in modules.
Top-level value of this
is window
in scripts and it’s undefined
in modules.
Scripts are run synchronously while modules are run asynchronously.
There are no import
statements in scripts and we can selectively import module members in modules.
We can programmatically import both scripts and modules using promise-based APIs.
Module Characteristics
ES6 modules can be statically analyzed for static checking, optimization, and more. It has a declarative syntax for importing and exporting.
Imports are hoisted to the top so that they can be referenced anywhere in the module.
For instance, if we have:
export const foo = 1;
Then we can import it as follows:
const baz = foo;
import { foo } from "./bar";
Also, they must be at the top-level. Therefore, we can’t have something like:
{
import { foo } from "./bar";
const baz = foo;
}
Imports are Read-Only Views on Exports
ES6 imports are read-only views on export entities. Connections to variables inside the module that imported the export remain live.
For instance, if we have:
export const foo = 1;
Then if we have the following:
import { foo } from "./bar";
foo = 1;
Then we’ll get a ‘”foo” is read-only.’ error since it’s referencing the export directly in a read-only manner.
We get the same result if we change the const
to let
.
Cyclic Dependencies
If module A and B import members from each other, then we call it a cyclic dependency. This is supported with ES6 modules. For instance, if we have:
index.js
:
import { foo } from "./bar";
export const baz = 2;
bar.js
:
import { baz } from "./index";
export let foo = 1;
Then the modules are cyclic dependencies since we import a member from bar
inindex
and we import a member from index
in bar
.
This works because imports just refer to the original data, so it doesn’t matter when they come from.
Importing Styles
We can import JavaScript modules in various ways. One way is the default import, which is how we import members from a module.
For instance, we can write:
bar.js
:
let foo = 1;
export default foo;
Then we can import it as follows:
import foo from "./bar";
Named imports can be imported as follows. Given the following named exports:
export let foo = 1;
Then we can import it as follows:
import { foo } from "./bar";
We can rename named exports by using the as
keyword as follows:
import { foo as baz } from "./bar";
We can also rename default exports as follows:
import { default as foo } from "./bar";
We can also have empty where we don’t import anything. Instead, we run what’s included in the module.
For instance, if we have the following in bar.js
:
console.log("bar");
Then we can run the code in bar.js
as follows:
import "./bar";
Therefore, we should see 'bar'
logged in the console log.
Conclusion
ES6 modules are a great way to divide code into small chunks. We can export module members and import them in another file. Imported members are read-only. Modules are in strict mode by default, so we avoid lots of issues with non-strict mode.