Modules Β· Astro Tech Blog

Modules

Modules, Introduction

Modules let you split code into separate files, each with its own scope. Before modules, JavaScript had global scope pollution β€” every script shared the same global namespace.

What Modules Solve

  • Scope isolation β€” variables don’t leak to the global scope
  • Reusability β€” import/export code across files
  • Dependency management β€” explicit declarations of what each file needs
  • Lazy loading β€” load code only when needed

Module Syntax

HTML:

<script type="module">
  import { greet } from "./utils.js";
  console.log(greet("World"));
</script>

JavaScript files are modules by default when loaded with type="module".

Module vs Script

FeatureRegular ScriptModule
ScopeGlobalOwn scope
Strict modeOptionalAlways strict
Top-level thiswindowundefined
Defer by defaultNoYes (scripts are deferred)
await at top levelNoYes (in some environments)
Import/exportNoYes

Module-level β€œthis”

// script.js
console.log(this); // β†’ window

// module.js
console.log(this); // β†’ undefined

Modules are Deferred

Module scripts are always deferred β€” they wait for HTML parsing to finish before executing:

<!-- These execute in order, after the page loads -->
<script type="module" src="app.js"></script>
<script type="module" src="utils.js"></script>

Export and Import

Named Exports

// πŸ“ utils.js

// Export individual items
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Calculator { ... }

// Export at the end
const PI = 3.14159;
function add(a, b) { return a + b; }
class Calculator {}
export { PI, add, Calculator };

Named Imports

// πŸ“ app.js
import { PI, add, Calculator } from "./utils.js";

console.log(PI);           // β†’ 3.14159
console.log(add(2, 3));    // β†’ 5
let calc = new Calculator();

Import All

import * as utils from "./utils.js";

console.log(utils.PI);
console.log(utils.add(2, 3));

Default Export

Each module can have one default export:

// πŸ“ user.js
export default class User {
  constructor(name) {
    this.name = name;
  }
}

// πŸ“ main.js
import User from "./user.js"; // No curly braces!

Mixed Exports

// πŸ“ math.js
export default function add(a, b) { return a + b; }
export const PI = 3.14159;
export const E = 2.71828;

// πŸ“ app.js
import add, { PI, E } from "./math.js";

console.log(add(PI, E)); // β†’ 5.85987

Renaming Imports and Exports

// Rename on export
export { add as sum, PI as PI_VALUE };

// Rename on import
import { add as sum, PI } from "./math.js";
console.log(sum(2, 3)); // β†’ 5

Re-exporting

// πŸ“ index.js β€” barrel file
export { add, subtract } from "./math.js";
export { User } from "./user.js";
export { formatDate } from "./date-utils.js";

// Now consumers can import from a single file
import { add, User, formatDate } from "./index.js";

Re-export All

export * from "./math.js";       // Re-export all named exports
export { default } from "./user.js"; // Re-export default

Module Rules

// 1. Module code runs only once per import
// 2. Modules are singletons β€” shared across all importers
// 3. Modules are evaluated lazily (in some systems) or eagerly

Module Evaluation Order

// πŸ“ a.js
console.log("A evaluated");
export const a = 1;

// πŸ“ b.js
console.log("B evaluated");
export const b = 2;

// πŸ“ main.js
import { a } from "./a.js";
import { b } from "./b.js";

// Output:
// "A evaluated"
// "B evaluated"

export default function vs export function

// Can import with any name:
export default function greet() {}
import myGreet from "./greet.js"; // Works
import hello from "./greet.js";   // Also works

// Must import with exact name:
export function greet() {}
import { greet } from "./greet.js"; // Must use { greet }

Dynamic Imports

Static imports must be at the top level. Dynamic imports (import()) let you load modules on demand.

Static vs Dynamic

// Static import β€” at the top of file
import { add } from "./math.js";

// Dynamic import β€” anywhere in code
const mathModule = await import("./math.js");
console.log(mathModule.add(2, 3));

When to Use Dynamic Imports

// 1. Code splitting β€” load heavy modules lazily
button.addEventListener("click", async () => {
  const chart = await import("./heavy-chart-library.js");
  chart.render(data);
});

// 2. Conditional loading β€” load based on conditions
if (user.locale === "de") {
  const i18n = await import("./locales/de.js");
  applyTranslations(i18n);
}

// 3. Feature detection
if ("IntersectionObserver" in window) {
  const observer = await import("./lazy-loader.js");
} else {
  // Fallback
}

Dynamic Import API

// Returns a module namespace object
const module = await import("./utils.js");
module.default;     // Default export
module.namedExport; // Named export
module.PI;          // Named constant

Dynamic Import in Regular Scripts

Dynamic imports work in regular scripts (not just modules):

<script>
  // Works in regular scripts too!
  import("./utils.js").then(module => {
    console.log(module.default());
  });
</script>

Practical Example

// πŸ“ helpers.js
export function formatDate(date) {
  return date.toLocaleDateString();
}
export function formatCurrency(amount) {
  return `$${amount.toFixed(2)}`;
}

// πŸ“ main.js
async function loadHelpers() {
  try {
    const helpers = await import("./helpers.js");
    console.log(helpers.formatDate(new Date()));
    console.log(helpers.formatCurrency(42.5));
  } catch (err) {
    console.error("Failed to load module:", err);
  }
}

loadHelpers();

Dynamic Import with Default Export

// πŸ“ logger.js
export default function log(message) {
  console.log(`[LOG] ${message}`);
}

// πŸ“ app.js
const { default: log } = await import("./logger.js");
log("Application started");

// Or:
const logger = await import("./logger.js");
logger.default("Application started");

Batch Dynamic Loading

async function loadAll() {
  const [utils, api, ui] = await Promise.all([
    import("./utils.js"),
    import("./api.js"),
    import("./ui.js")
  ]);

  return { ...utils, ...api, ...ui };
}

const app = await loadAll();
app.formatDate(new Date());
Demo: Dynamic Import (Simulated)
JavaScript
// Simulating dynamic imports since we can't use true modules here
let modules = {
greet: { default: (name) => 'Hello, ' + name + '!' },
math: { add: (a, b) => a + b, multiply: (a, b) => a * b }
};

async function loadModule(name) {
// Simulate async module loading
await new Promise(r => setTimeout(r, 500));
return modules[name];
}

async function main() {
document.write('Loading modules...<br>');

let greet = await loadModule('greet');
document.write(greet.default('World') + '<br>');

let math = await loadModule('math');
document.write('2 + 3 = ' + math.add(2, 3) + '<br>');
document.write('4 Γ— 5 = ' + math.multiply(4, 5) + '<br>');

document.write('<br>All modules loaded dynamically!');
}

main();
Live Output Window