Last updated: 2025-09-12 14:07

You Don't Know ESM

https://beyondcodebootcamp.github.io/presos/javascript-modules/

https://beyondcodebootcamp.github.io/presos/javascript-modules/

Video: https://youtu.be/Wt46wuoVZT8


2025: The year of ESM

You can publish a package as a module without breaking downstream dependencies.


Syntax v. Implementation

import "...";
await main();

import Widget from "widget";

https://caniuse.com/import-maps (Mar, 2023)


let Widget = require("widget").default;

https://nodejs.org/en/blog/release/v22.12.0 (Dec, 2024)


Who am I? 🔍


AJ ONeal

Father of 2.
Father of 3.

Deep Learner.


Credentials 🪪


Vermont-Area Linux

Utah JS

Utah Node.js

Utah Rust

Utah Zig

Utah Colo
(big XMission fan)


The Root Company

aj@therootcompany.com

🐹 Go
(🛜 Network 🔐 Security)

🏢 Colo
(🐧 Linux/POSIX 📦 Proxmox 💽 TrueNAS)


Goal


Try JavaScript

maybe for the first time


maybe create the first modern JS framework


"JavaScript" Modules


"JavaScript"

TC39, WHATWG, W3C, JS Foundation


A Real, Interpreted Language

  1. By a Browser
  2. By node.js, etc
  3. And, most importantly, by both

Whatever you can copy and paste and have it work equally well whether into a browser console or a node REPL.


Framework1Script

import Logo from "./logo.png";
import "./styles.css";
import Greeter from "./greeter";

function HelloWorld() {
  return (
    <div className="flex justify-center items-center h-screen">
      <img src={Logo} alt="Logo" className="logo" />
      <h1 className="text-4xl font-bold text-blue-600">
        <Greeter name="World" />
      </h1>
    </div>
  );
}

Framework2Script

import config from "./config.json";

function greet(name: string = "World"): string {
  const greeting: string = config.defaultGreeting || "Hello";
  const message: string = `${greeting}, ${name}!`;
  return message;
}

JavaScript (with Types)

import Config from "./config.js";

/**
 * @param {String} [name]
 * @returns String
 */
function greet(name = "World") {
  let greeting = Config.defaultGreeting || "Hello";
  let message = `${greeting}, ${name}!`;
  return message;
}

NOT JavaScript, NOT ESM

import "./foo";
import "./style.css";
import "./data.json";

ESM is for JavaScript


Module

  1. use code from another file
  2. use code from another package (namespaced)
  3. as JavaScript

1. Local Files


customer.js

var User = {};
User.create = function () {};

employee.js:

var User = {};
User.create = function () {};

main.js

import Customer from "./customer.js";
import Employee from "./employee.js";

// do stuff ...

TL;DW: export

let User = {};

// ...

export default User;
export let create = User.create;
export let get = User.get;

TL;DW: import

// default
import Employee from "./employee.js";
// explicit
import * as Employee from "./employee.js";

TL;DW: require

// default (counterintuitive)
let Employee = require("./employee.js").default;
// explicit
let Employee = require("./employee.js");

https://nodejs.org/en/blog/release/v22.12.0 (Dec, 2024)


2. Published Packages


Syntax


main.js

import Auth from "@example/auth";
import Keypairs from "keypairs";

// do stuff ...

TC39 ECMAScript Modules: https://tc39.es/ecma262/#sec-modules


Syntax import

MDN docs for import

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import


Syntax export

MDN docs for export

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export


Package Paths & Namespaces

(runtime entry points)


Semantics

index.html

<form method="dialog" onsubmit="FooUI.submit(window.event);">...</form>

<script type="importmap">
  {
    "imports": {
      "foo": "./foo.js",
      "foo/": "./lib/",
      "bar": "./node_modules/bar/index.js"
    }
  }
</script>

<script type="module">
  import FooUI from "foo/ui.js";
  window.FooUI = FooUI;
</script>

WHATWG: https://html.spec.whatwg.org/multipage/webappapis.html#import-map


package.json

{
  "name": "foo",
  "type": "module",
  "imports": {
    "foo": "./foo.js",
    "foo/": "./lib/",
    "baz": "./vendor/baz.js"
  },
  "exports": {
    ".": "./foo.js",
    "./*": "./lib/*"
  }
}

https://nodejs.org/api/packages.html#package-entry-points


package.json (bonus)

__dirname, __filename

import Path from "node:path";

let modulePath = import.meta.url.slice("file://".length);
let moduleDir = Path.dirname(modulePath);

jsconfig.json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "paths": {
      "foo/*": ["./lib/*"],
      "foo": ["./foo.js"],
      "baz": ["./vendor/baz.js"]
    }
}

https://www.typescriptlang.org/tsconfig/#paths
https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths


FUD


The rumors...


Top-Level Await

# ⚠️ not for published packages
await fetch();
Foobar.init = async function () {
  await fetch();
};

Tree Shake Stuttering

// ❌ stuttering doesn't shake harder
import { createFoo, getFoo } from "./stuttering-foo.js";
# ✅ easy-to-search
grep -r -F 'Foo.'

// default
import Employee from "./employee.js";
// explicit
let Employee = require("./employee.js");

This is the way.


2026: The Year of JavaScript?


EOF


Q&A


AJ ONeal - The Root Company
aj@therootcompany.com
🐹 Go (🛜 Network 🔐 Security)
🏢 Colo (🐧 Linux/POSIX 📦 Proxmox 💽 TrueNAS)


https://beyondcodebootcamp.github.io/presos/javascript-modules/ beyondcodebootcamp.github.io/presos/javascript-modules/