
A deep, practical explanation of JavaScript ES Modules: execution model, shared exports, strict mode, browser rules, and real-world implications.
Martin Ferret
March 3, 2026
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.
A JavaScript module is simply a file.
Each file is an isolated unit with:
Modules communicate only through:
export → what a module exposesimport → 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.

Before native modules:
Modules solved these problems by enforcing:
This is not syntactic sugar: it is a structural foundation for large applications.
Modules automatically run in strict mode.
x = 10; // ❌ ReferenceError
This prevents silent errors and enforces safer code by default.
Top-level variables are never global.
`// user.js
export const user = 'John';`
`// app.js
import { user } from './user.js';
console.log(user);`
Sin export and import, nothing is shared.
Even multiple <script type="module"> tags on the same page do not share scope.
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.
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.
type="module" Is RequiredBrowsers only recognize modules when explicitly declared.
<script type="module" src="main.js"></script>
Without it, import and export will fail.
Module scripts:
This makes them behave similarly to defer, but by default.
This is invalid in the browser:
import { tool } from 'myLib'; // ❌
A path is required:
import { tool } from './myLib.js'; // ✅
Bare imports are resolved by:
Browsers do not resolve them natively.
ES modules do not work over file://.
You must use:
This is a security requirement, not a limitation.
import.meta: Module ContextEach module has access to metadata about itself:
console.log(import.meta.url);
Useful for:
Even with native modules, bundlers remain essential in production.
They provide:
In real-world applications, native modules are the input, bundlers are the output.
Modules enforce architectural clarity:
If a codebase feels messy with modules,
the issue is not the module system, it’s the architecture.
JavaScript modules are not an optional modern feature.
They are the foundation of contemporary JavaScript.
Understanding:
is what separates developers who use modules
from developers who control them.
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.

Cómo funciona realmente el bucle del trabajador de la cola
Domina las colas de Laravel comprendiendo qué ocurre entre bastidores cuando se envían y procesan las tareas. Esta guía analiza los trabajadores de colas, la serialización de modelos, los reintentos, las tareas fallidas, el encadenamiento y el procesamiento por lotes: conceptos clave para crear aplicaciones fiables y superar con éxito los exámenes de certificación de Laravel.
Steve McDougall
25 de junio de 2026

Primeros pasos con rstore en Vue
Una guía paso a paso sobre rstore, el almacén de datos reactivo para Vue con almacenamiento en caché normalizado, consultas tipadas y un sistema de complementos.
Reza Baar
24 de junio de 2026

Promise.withResolvers(): el patrón «Deferred» integrado
Promise.withResolvers() sustituye al patrón «deferred» manual en JavaScript. Una sola desestructuración, sin ejecutor, sin «let». ES2024, compatible con todos los entornos de ejecución modernos.
Martin Ferret
23 de junio de 2026