Advanced JavaScript · 2 min read
ES Modules
import and export, named vs default exports, live bindings, dynamic import() for code splitting, and module scope.
A module is a file with its own scope. Nothing leaks to the global scope unless you export it, and nothing comes in unless you import it. Every modern dApp front end, including this one, is built from modules.
Named and default exports
js
// near.js
export const NETWORK = "testnet";
export function explorerUrl(account) {
return "https://testnet.nearblocks.io/address/" + account;
}
export default function formatNear(yocto) { /* ... */ }
// app.js
import formatNear, { NETWORK, explorerUrl as link } from "./near.js";
import * as near from "./near.js"; // namespace object: near.NETWORKPrefer named exports: they are easier to search for, rename consistently, and tree-shake.
Modules behave differently from scripts
Classic <script> | <script type="module"> | |
|---|---|---|
| Scope | Global | Module scope |
| Strict mode | Opt-in | Always on |
| Loading | Blocks parsing unless defer | Deferred by default |
Top-level await | No | Yes |
| Runs | Every time included | Once per URL, then cached |
Live bindings
Imports are live views of the exporter's variables, not copies:
js
// counter.js
export let count = 0;
export function inc() { count++; }
// main.js
import { count, inc } from "./counter.js";
inc();
console.log(count); // 1, the binding updated
count = 5; // TypeError: imports are read-onlyDynamic import()
import() returns a promise and loads code on demand. That is how this site loads the wallet selector only when you need it:
js
button.addEventListener("click", async () => {
const { setupWalletSelector } = await import("@near-wallet-selector/core");
// ...the wallet code is downloaded only now
});Try it: modules in the browser
Inline module scripts can import from URLs. Here, a module is created from a string with a Blob URL so the example is self-contained.
<pre id="out"></pre>Edit this example in the Playground.
Summary
Modules give every file private scope, explicit dependencies and strict mode. Use named exports, remember imports are live and read-only, and reach for import() to split heavy code like wallet SDKs out of your first load.
Saved in this browser.