ES modules: exporting and importing values, dynamic imports, and Node.js interop.

ES modules

Introduction

ES modules (ESM) use import and export; each module is its own file.

Exporting

export const x = 1              // named export
export function fn() {}         // named export
export { a, b }                 // named exports
export { x as y }               // renamed export
export default function () {}   // default export (one per module)
export { x } from './x.js'      // re-export named
export * from './x.js'          // re-export all named

A module has many named exports but only one default.

See: export (developer.mozilla.org)

Importing

import def from './mod.js'            // default export
import { a, b as c } from './mod.js'  // named exports
import * as ns from './mod.js'        // namespace object
import './setup.js'                   // side effects only
import def, { a } from './mod.js'     // default + named

Static imports are hoisted and must be top-level. Named imports are live bindings, reflecting later changes in the exporting module.

See: import (developer.mozilla.org)

Dynamic imports

const mod = await import('./mod.js')
mod.default                        // the default export
if (needsChart) {
  const { Chart } = await import('./chart.js')
}
import.meta.url                    // → URL of this module

import() returns a promise, so it works inside conditions. import.meta.url is the module's own URL.

See: import(), import.meta (developer.mozilla.org)

Browser

Importing

index.html

<script type="module" src="main.js"></script>

Browser modules are deferred by default, and relative specifiers need the file extension.

See: JavaScript modules (developer.mozilla.org)

Import maps

index.html

<script type="importmap">
{ "imports": { "lodash": "/vendor/lodash.js" } }
</script>

Bare specifiers (eg import 'lodash') need an import map.

See: import maps (developer.mozilla.org)

Node.js

Module formats

ExtensionModule system
.mjsAlways ESM
.cjsAlways CommonJS
.jsFrom "type" in package.json

.mjs and .cjs set the format by extension; .js follows the nearest package.json.

See: Node.js: determining module system (nodejs.org)

"type": "module"

package.json

{ "type": "module" }

With "type": "module", .js files load as ES modules. Without it, they default to CommonJS.

See: Node.js: "type" (nodejs.org)

createRequire()

import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const legacy = require('./legacy.cjs')

In ESM use import, not require. createRequire() builds a require() for CommonJS packages.

A CJS module.exports becomes the default export when imported. Node 22.12+ can require() ESM that avoids top-level await.

See: createRequire() (nodejs.org)

0 Comments for this cheatsheet. Write yours!