A module is a file with its own scope that shares names with export. It may have many named exports, imported in braces (renamed with as) or as a namespace (* as ns), and one default export, imported under any name. export * from "./x.js" re-exports another module. Static imports sit at the top level with a literal specifier (relative paths need ./). Each module runs once, and its imports are live, read-only views of the exports.
export let count = 0; // named exports
export const inc = () => ++count;
export default function square(x) { // one default export per module
return x * x;
}import square, { count, inc as increment } from "./math.mjs";
import * as math from "./math.mjs";
import config from "./config.json" with { type: "json" }; // { "version": "1.2.0" }
increment();
console.log(square(4), count, math.count, config.version); // imports are live
try { count = 5; } catch (e) { console.log(e.message); } // but read-only
const name = "math"; // dynamic import() with top-level await
const { default: sq } = await import(`./${name}.mjs`);
console.log(sq(3), sq === square, import.meta.url.split("/").pop());16 1 1 1.2.0 Assignment to constant variable. 9 true main.mjs
Dynamic import() (ES2020) takes a computed string, also works in classic scripts, and returns a promise for the namespace object. Bundlers such as webpack 3,824 split its targets into separate, on-demand chunks.
Import attributes (ES2025, Baseline 2025): with { type: "json" } loads JSON as the default export. The type is required, so a server expected to send data cannot make the page run script instead.
import.meta.url is the current module's own URL.
Top-level await (ES2022, modules only): importers wait for the module, while unrelated modules keep loading.
Browsers cannot resolve bare specifiers such as "lodash". An import map (Baseline since March 2023) maps them to URLs; place it before the modules that use it. A key ending in / maps a path prefix.
<script type="importmap">
{ "imports": {
"lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.18.1/+esm",
"lodash/": "https://cdn.jsdelivr.net/npm/lodash-es@4.18.1/" } }
</script>
<script type="module">
import { camelCase } from "lodash";
import kebabCase from "lodash/kebabCase.js";
document.body.textContent = `${camelCase("user name")} | ${kebabCase("User Name")}`;
</script>