JavaScript Modules Explained: The Foundation of Modern JS Applications

JavaScript Modules Explained: The Foundation of Modern JS Applications

A deep, practical explanation of JavaScript ES Modules: execution model, shared exports, strict mode, browser rules, and real-world implications.

Martin Ferret

Martin Ferret

March 3, 2026

JavaScript Modules Explained: What You’re Actually Using Every Day

For years, JavaScript grew without a native module system. Early applications were small, scripts lived in the global scope, and naming conflicts were common. As applications became larger and more complex, this approach quickly reached its limits.

Today, JavaScript modules (ES Modules) are everywhere: in browsers, Node.js, and every modern framework and bundler. Yet many developers use them daily without fully understanding how they actually work.

This article breaks down JavaScript modules from first principles, focusing on what is true, what matters, and why it works this way.

What Is a JavaScript Module?

A JavaScript module is simply a file.

Each file is an isolated unit with:

  • its own scope
  • explicit dependencies
  • explicitly exported functionality

Modules communicate only through:

  • export → what a module exposes
  • import → what a module consumes

// sayHi.js export function sayHi(name) { return Hello ${name}; }

// main.js import { sayHi } from './sayHi.js';

console.log(sayHi('John'));

No implicit globals. No hidden dependencies. Everything is explicit.

dm5Q5mRzxJk63FM4xdHGGZq0B3SuN1wJbxlFF14O.png

Why Modules Changed JavaScript for Good

Before native modules:

  • global variables everywhere
  • fragile script loading order
  • hidden coupling between files
  • poor maintainability at scale

Modules solved these problems by enforcing:

  • encapsulation
  • clear dependency graphs
  • predictable execution
  • tooling support (tree-shaking, bundling)

This is not syntactic sugar: it is a structural foundation for large applications.

Core Rules of JavaScript Modules

1. Modules Are Always in Strict Mode

Modules automatically run in strict mode.

x = 10; // ❌ ReferenceError

This prevents silent errors and enforces safer code by default.

2. Each Module Has Its Own Scope

Top-level variables are never global.

// user.js export const user = 'John';

// app.js import { user } from './user.js'; console.log(user);

Without export and import, nothing is shared.

Even multiple <script type="module"> tags on the same page do not share scope.

3. A Module Is Executed Only Once

No matter how many times a module is imported, its top-level code runs only once.

// logger.js console.log('Module initialized');

import './logger.js'; import './logger.js';

Output: Module initialized

This behavior is intentional and fundamental.

4. Exports Are Shared, Not Copied

Exports are live bindings, not snapshots.

// config.js export const config = {};

// init.js import { config } from './config.js'; config.user = 'Pete';

// feature.js import { config } from './config.js'; console.log(config.user); // Pete

All importers receive the same reference.

This enables controlled configuration and shared state, when used deliberately.

Modules in the Browser

type="module" Is Required

Browsers only recognize modules when explicitly declared.

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

Without it, import and export will fail.

Modules Are Deferred by Default

Module scripts:

  • do not block HTML parsing
  • execute after the document is loaded
  • respect script order

This makes them behave similarly to defer, but by default.

No “Bare” Imports in Browsers

This is invalid in the browser:

import { tool } from 'myLib'; // ❌

A path is required:

import { tool } from './myLib.js'; // ✅

Bare imports are resolved by:

  • Node.js
  • bundlers (Vite, Webpack, Rollup)

Browsers do not resolve them natively.

Modules Require HTTP(S)

ES modules do not work over file://.

You must use:

  • a local web server
  • or tools like Vite, Live Server, or similar

This is a security requirement, not a limitation.

import.meta: Module Context

Each module has access to metadata about itself:

console.log(import.meta.url);

Useful for:

  • resolving relative assets
  • environment-aware logic
  • debugging

Why Bundlers Still Matter

Even with native modules, bundlers remain essential in production.

They provide:

  • tree-shaking
  • optimized dependency graphs
  • cross-browser compatibility
  • asset imports (CSS, images, HTML)
  • performance optimizations

In real-world applications, native modules are the input, bundlers are the output.

Modules Are a Discipline, Not Just Syntax

Modules enforce architectural clarity:

  • explicit dependencies
  • separation of concerns
  • predictable behavior
  • easier testing and refactoring

If a codebase feels messy with modules,

the issue is not the module system, it’s the architecture.

Conclusion

JavaScript modules are not an optional modern feature.

They are the foundation of contemporary JavaScript.

Understanding:

  • execution rules
  • scope isolation
  • shared exports
  • browser constraints

is what separates developers who use modules

from developers who control them.

More certificates.dev articles

Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

Looking for Certified Developers?

We can help you recruit Certified Developers for your organization or project. The team has helped many customers employ suitable resources from a pool of 100s of qualified Developers.

Let us help you get the resources you need.

Contact Us
Customer Testimonial for Hiring
like a breath of fresh air
Everett Owyoung
Everett Owyoung
Head of Talent for ThousandEyes
(a Cisco company)