notes on es modules with chantastic
- JavaScript
- ESM
- Node
- esbuild

Introduction
ES Modules (JavaScript Modules, if you like) help us structure, organize and isolate code.
These examples focus on the ES Modules features available in a Node.js environment. Most JavaScript applications today (early 2021) still go through some form of bundling before being sent to the browser. The features we cover should be common to all Node.js bundling tools (Webpack, Rollup, esbuild) (the latest LTS) Node.js environments (v14+).
index.js - main file
- This is where all exercises are run
- Examples in
readme.md
can be copy/pasted intoindex.js
./modules - prepared module files
- The names relate to the type of content inside
- Various examples might use these prepared modules to explore a concept
assignment.mjs - examples
./modules/assignment.mjs
is an empty module.
1. Import a module from the file system
Modules are imported using the import
keyword and a string path to that module. This is predominantly done at the opening of a file. We can import any module that exists on the file system.
Import the assignment.mjs
module using the import
keyword and file path
// index.js
import "./modules/assignment.mjs"
2. Make your module leaky
The first thing to know about modules is that they leak. They don’t perfectly encapsulate all code. Global code is global code, even if in a module. This might sounds bad (and it can be) but it’s an important feature.
Add globally executing code to the assignment.mjs
module
Globally executing code can be console.log("booo!!")
or an assignment like global.leak = "oh no!"
.
// modules/assignment.mjs
console.log("sah dude")
global.leak = "oops"
// index.js
import "./modules/assignment.mjs"
console.log(global.leak)
sah dude
oops
3. Import a module dynamically
The import
keyword has two variants: static and dynamic. You can change a static import to a dynamic import by adding parenthesis around the path string.
import "./modules/assignment.mjs" // static
import("./modules/assignment.mjs") // dynamic
Change the static import to a dynamic import
Dynamic import
returns a Promise
, so try handling that promise using await
.
// index.js
await import("./modules/assignment.mjs")
console.log(global.leak)
Use .then()
to resolve the Promise.
// index.js
import("./modules/assignment.mjs")
.then(
() => console.log(global.leak)
)
sah dude
oops
4. Add a function declaration and variable to the module and export using an export list
Global code is executed at import
but variables and function declarations are not. Even though a function or variable might exist in an imported module, it can’t be accessed outside of that module.
Add a variable and function declaration to the module
// modules/assignment.mjs
let fallbackName = "there"
function greet(name = fallbackName) {
return `Hey, ${name}!`
}
Use export
list to export greet
function
We can export anything defined in our module by adding it to the comma separated export
list.
// modules/assignment.mjs
let fallbackName = "there"
function greet(name = fallbackName) {
return `Hey, ${name}!`
}
export { fallbackName, greet }
Anything exported can also use the same list syntax, {}
, for import
. Importing specific imports from a module requires the from
keyword before the path string.
// index.js
import { greet } from "./modules/assignment.mjs"
console.log(greet())
Hey, there!
export
list is commonly at the end of a file to guarantee that everything exported — or referenced — already exists.
Export and import just the greet
function using an export
list
// modules/assignment.mjs
let fallbackName = "dude"
function greet(name = fallbackName) {
return `Sah, ${name}`
}
export { greet }
// index.js
import { greet } from "./modules/assignment.mjs"
console.log(greet())
Sah, dude
greet
still has access to fallbackName
, even though fallbackName
isn’t exported.
5. Use as
to alias (or rename) imports and exports
Modules might not share object syntax with modules but they still allow for aliasing (or renaming) of variables and functions using the as
keyword.
// modules/assignment.mjs
export { aGoodLocalName as aBetterExportedName }
It works identically on both import
and export
side.
// index.js
import {
aBetterExportedName as anEvenBetterContextualName
} from "./modules/assignment"
Rename the greet
function at export
At import, use the new function name you’ve exported and then rename it back to greet
at import
.
// modules/assignment.mjs
let fallbackName = "dude";
function greet(name = fallbackName) {
return `Sah, ${name}`
}
export { greet as sahDude }
// index.js
import { sahDude as greet } from "./modules/assignment.mjs"
console.log(greet())
Sah, dude