f in x
JavaScript ES Modules — Organize Code with Import/Export and Lazy Loading
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

JavaScript ES Modules — Organize Code with Import/Export and Lazy Loading

[2026-06-21] Author: Ing. Calogero Bono

Have you ever opened a JavaScript project and found a single script.js file two thousand lines long? Or dozens of scattered <script> tags in your HTML with dependencies that look like a puzzle? We see this often in sites coming to us for a redesign: monolithic code, global variables stepping on each other, and pages that load everything even when only two functions are needed. The result? Slow downloads, nightmare maintenance, and developers wasting hours searching for “where is that function defined?”.

ES Modules solve these problems at the root. No more manual concatenation, no more polluted global namespace, no more bundles that include the admin panel the user will never see. With import and export you can write modular, maintainable code — and with lazy loading, load only what you need, when you need it. We at Meteora Web have been using them for years in our Laravel and Vue projects, and every time we go back to an old site without modules we remember why they are essential.

Why use ES Modules instead of old concatenated scripts?

Before ES Modules, organizing JavaScript code was a problem. Patterns like IIFE (Immediately Invoked Function Expression) were used to create private scopes, or libraries like RequireJS or CommonJS (for Node.js). But in the browser everything ended up in a single global space. Two libraries with the same variable? Silent error.

ES Modules bring three concrete benefits:

  • Isolated scope — Each module has its own context. No accidental global variables.
  • Explicit declarations — Each file declares what it exports and what it imports. Reading the header you immediately understand the dependencies.
  • Native asynchronous loading — The browser can load modules in parallel without blocking rendering. Combined with lazy loading, it reduces the initial page weight.

Real example: An e-commerce client had a checkout.js file of 400 KB that included everything: cart, validation, payment handling, shipping map. With modules we split it into 4 files: only the cart is loaded immediately, the rest loads when needed. First interaction went from 4 seconds to 1.2 seconds.

Sponsored Protocol

What to do right now

If you are starting from scratch, use the type="module" attribute in your <script> tag:

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

The browser will treat main.js as a module. You can already use import and export inside it. Supported by all modern browsers (Chrome, Firefox, Safari, Edge).

How do import and export work in ES Modules?

An ES Module is simply a .js file with one of two directives: export makes a function, variable or class available to other modules; import consumes it.

Named vs default exports

You can export in two ways:

  • Named: multiple exports per module, ideal for libraries.
  • Default: one per module, useful for single components.

Example: Create a utils.js file that exports two functions:

Sponsored Protocol

// utils.js
export function sum(a, b) {
  return a + b;
}

export function formatPrice(value) {
  return `$ ${value.toFixed(2)}`;
}

export default function greet(name) {
  return `Hello, ${name}!`;
}

Now in main.js import only what you need:

// main.js
import greet, { sum, formatPrice } from './utils.js';

console.log(sum(5, 3)); // 8
console.log(formatPrice(19.99)); // "$ 19.99"
console.log(greet('Marco')); // "Hello, Marco!"

Note: default import doesn't need curly braces, named ones do. The order is arbitrary.

Relative and absolute paths

Modules always use URLs. For local files, use relative paths starting with ./ or ../. For CDN modules, use full URLs:

import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';

No installation needed: the browser downloads the module directly.

Re-export to organize libraries

You can group exports from multiple modules into a single index file:

// lib/index.js
export { sum, formatPrice } from './utils.js';
export { default as greet } from './utils.js';

Then in your code import from './lib/index.js' instead of individual files. Simplifies dependencies.

How to handle async loading with lazy loading?

The real power of ES Modules is the ability to import code on demand, without blocking startup. This is called lazy loading (or code splitting) and is achieved with dynamic import().

Dynamic import: import()

Unlike static imports (resolved at load time), import() is a function that returns a Promise. You can call it whenever you want, e.g., when the user clicks a button or scrolls to a certain point.

Sponsored Protocol

// main.js
const button = document.getElementById('load-map');

button.addEventListener('click', async () => {
  const module = await import('./map.js');
  module.initMap();
});

The browser downloads map.js only on click. It was never cached or executed before.

When to use lazy loading

  • Heavy components: chart libraries, maps, rich editors.
  • Route-based code (Single-Page Apps): load section code only when the user navigates to it.
  • Admin features: 90% of users never see them, don't load them immediately.

Real example: A client has a management system with an invoice module. The PDF export used jsPDF (150 KB). With dynamic import triggered by the “Download PDF” click: the main page went from 350 KB to 200 KB, interaction time dropped by 40%.

Common lazy loading mistakes

  • Forgetting await: import() returns a Promise. If you don't wait, code using the module runs before it's loaded.
  • Wrong paths: dynamic paths are evaluated at runtime, make sure they are accessible from the server.
  • Excessive overhead: do not lazy-load tiny modules (e.g., a 2-line function). The HTTP request cost outweighs the benefit.

What are common ES Module mistakes and how to avoid them?

Even with a clean system, you can fall into traps. Here are the ones we see most often in projects we review.

Sponsored Protocol

Modules loaded twice

If you statically and dynamically import the same module, the browser executes it only once because ES Modules are singletons. But careful: if you use different paths (e.g., './utils.js' vs './Utils.js'), the browser treats them as different modules. Always use consistent casing.

Forgetting type="module"

If you put <script src="main.js"> without the attribute, import and export throw an error. The browser reads them as regular scripts, where import is not recognized.

CORS with local modules

ES Modules require the server to send the Content-Type: application/javascript header and that requests are served via HTTP (not file://). During local development, use a server like npx serve or php -S localhost:8000.

# Start a simple HTTP server in the project folder
python3 -m http.server 8000

Then open http://localhost:8000 in the browser.

Modules and old browsers

If you need to support Internet Explorer or very old browsers, native ES Modules won't work. Solutions:

  • Use a bundler (Webpack, Vite, Rollup) that produces compatible scripts.
  • Combine type="module" with nomodule to provide a fallback.
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>

How to measure performance impact with modules?

Just implementing modules is not enough: you must verify they work. Use the browser's developer tools.

  • Network tab: check that modules are loaded with status 200 and that lazy loading occurs only on interaction.
  • Coverage tab (Chrome): shows how much code was actually executed. Target: below 50% for first interaction.
  • Lighthouse: metrics like “Total Blocking Time” and “First Contentful Paint” improve when you reduce unused code.

We at Meteora Web always integrate a small test: after deployment, we run an audit with lighthouse-ci and compare before/after numbers. A client with a blog saw TBT drop from 300ms to 80ms just by isolating the weather widget into a lazy module.

Sponsored Protocol

What to do next

Here are three concrete actions you can take today:

  1. Convert a single JS file into modules: take the largest file in your project. Split it into two or three files using export and static import. Add type="module" to the script tag. Verify everything works.
  2. Add a dynamic import: identify a rarely used feature (e.g., an advanced contact form). Move its code into a separate module and load it with import() only when the user clicks the button.
  3. Measure the result: before and after, run a Lighthouse audit. Compare the JavaScript weight and TBT. If it doesn't improve, look for modules loaded too early.

ES Modules are not a trend: they are the browser's native way to write organized and performant code. Use them today, your future self (and your users) will thank you. To dive deeper into the latest language features, check out our pillar guide on JavaScript ES2024.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()