*
Previous Event Bubbling & Delegation let, const, arrow functions Next

JavaScript Modules: Import & Export

📦 Introduction to JavaScript Modules

JavaScript modules allow developers to split code into reusable pieces across multiple files. This improves maintainability, readability, and scalability of applications. Modules are supported natively in modern browsers and JavaScript environments.

🔧 Exporting Code

You can export variables, functions, or classes from a module so they can be used in other files.

Named Exports

// utils.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

Default Export

// message.js
const greeting = "Hello, world!";
export default greeting;

📥 Importing Code

Use the import statement to bring in exported code from another module.

Importing Named Exports

// app.js
import { add, subtract } from './utils.js';

console.log(add(2, 3)); // 5

Importing Default Export

// main.js
import greeting from './message.js';

console.log(greeting); // Hello, world!

📄 HTML Setup

To use modules in the browser, set type="module" in your script tag:

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

✅ Benefits of Modules

  • Encapsulation of logic
  • Improved code organization
  • Reusability across projects
  • Support for lazy loading and tree shaking

⚠️ Notes

  • Modules are loaded asynchronously.
  • Module paths must be relative or absolute and include file extensions.
  • Only one default export is allowed per module.

ES6 Modules: import and export

With the introduction of ECMAScript 2015 (ES6), JavaScript gained a standardized module system for organizing code across multiple files. This system uses the import and export statements to manage dependencies, control scope, and enable code reuse.

Each JavaScript file can be a module, and its top-level variables and functions are scoped to that file by default. This prevents naming conflicts and keeps the global scope clean. To use a function or variable from one module in another, it must be explicitly exported and then imported.

export statement

The export statement makes a variable, function, class, or object available for use in other modules. There are two primary types of exports:

Named exports

You can have multiple named exports in a single module. The importing module must use the exact same name to access the exported value.

./math.js

// Export a constant
export const PI = 3.14159;

// Export a function
export function add(a, b) {
  return a + b;
}

// Export multiple items at once
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;
export { subtract, multiply };

Default exports

A module can have only one default export. This is useful for when a module's main purpose is to provide a single value, such as a class or a function. The importing module can use any name for a default export.

./user.js

// Export a default class
export default class User {
  constructor(name) {
    this.name = name;
  }
}

Important: A module can have both a default export and named exports.

import statement

The import statement is used in a module to bring in functionality that has been exported from another module.

Importing named exports

To import named exports, you must use the exact names and wrap them in curly braces {}.

./app.js

import { PI, add, multiply } from './math.js';

console.log(add(PI, 2)); // 5.14159
console.log(multiply(4, 5)); // 20

Importing a default export

To import a default export, you do not use curly braces. You can name the import anything you like.

./app.js

import MyUserClass from './user.js';

const user = new MyUserClass('Alice');
console.log(user.name); // Alice

Importing everything (* as)

You can import all the named exports from a module as a single namespace object. This is useful for avoiding naming conflicts.

./app.js

import * as math from './math.js';

console.log(math.PI); // 3.14159
console.log(math.add(4, 5)); // 9

Combining imports

You can import both a default export and named exports in a single line.

./app.js

import User, { PI, add } from './user_and_math.js';
// Assuming user_and_math.js exports both a default and named exports

Enabling modules in the browser

To use ES6 modules in a web browser, you must include the type="module" attribute in your <script> tag.

    <!DOCTYPE html>
    <html>
    <body>
    <script type="module" src="./app.js"></script>
    </body>
    </html>

Advantages

  • Encapsulation: Modules create a private scope for your code. Variables and functions inside a module are not globally accessible unless explicitly exported.
  • Code Reusability: You can write a utility function once and reuse it across your entire application by importing it wherever needed.
  • Dependency Management: The import and export syntax makes a module's dependencies explicit, making code easier to read and maintain.
  • Performance: The static nature of ES modules allows build tools to perform optimizations like "tree-shaking," which removes unused code to create smaller, more efficient bundles.

Disadvantages and considerations

  • CORS restrictions: When loading modules in a browser, Cross-Origin Resource Sharing (CORS) rules apply. You cannot load a module from a different origin unless proper CORS headers are set.
  • Static imports only at the top: import and export statements must be at the top level of a module. They cannot be nested inside conditional blocks (if) or function scopes, though dynamic imports offer a workaround.
  • Local server requirement: Browsers often block local file access for security reasons. To use modules in a browser, you must serve your files from a local web server.

Best practices

  • Be consistent: Choose between named or default exports and stick with it. Using default exports for the primary function and named exports for helpers is a common pattern.
  • Use aliases to avoid conflicts: If a named export conflicts with a local variable, use the as keyword to give it a different name during import: import { add as myAdd } from './math.js';
  • Use import * as for utility libraries: Importing a namespace object for a utility module (e.g., import * as utils from './utils.js') keeps your code clean and prevents polluting the local namespace.
  • Use dynamic import() for on-demand loading: For lazy-loading modules or importing based on a condition, use the dynamic import() expression, which returns a Promise.
Back to Index
Previous Event Bubbling & Delegation let, const, arrow functions Next
*
*