Chapter 4: ES6 Modules

Introduction

ES6 modules allow you to organize code into separate files, with an explicit import/export system.

In PHP, you use require, include, or PSR-4 autoloading. In modern JavaScript, we use import and export.

Export

Named Export

Allows exporting multiple elements from a file:

// utils/math.js

// Export at declaration
export const PI = 3.14159;

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

export const multiply = (a, b) => a * b;

// OR grouped export at the end
const PI = 3.14159;
function add(a, b) { return a + b; }
const multiply = (a, b) => a * b;

export { PI, add, multiply };

Default Export

Only one default export per file. Generally used for the main component/class:

// components/Button.jsx

const Button = ({ label, onClick }) => {
    return <button onClick={onClick}>{label}</button>;
};

export default Button;

// OR directly
export default function Button({ label, onClick }) {
    return <button onClick={onClick}>{label}</button>;
}

Mixing Both

// services/api.js

// Default export: main API client
const apiClient = {
    get: (url) => fetch(url).then(r => r.json()),
    post: (url, data) => fetch(url, { method: 'POST', body: JSON.stringify(data) })
};

// Named exports: utilities
export const API_URL = "https://api.example.com";
export const handleError = (error) => console.error(error);

export default apiClient;

Import

Named Import

// Import named exports (braces required)
import { add, multiply } from './utils/math.js';
import { PI } from './utils/math.js';

// Rename on import
import { add as addition } from './utils/math.js';

// Import everything under a namespace
import * as MathUtils from './utils/math.js';
// Usage: MathUtils.add(1, 2), MathUtils.PI

Default Import

// Import default export (no braces)
import Button from './components/Button.jsx';

// The name is free (not linked to the name in the source file)
import MonBouton from './components/Button.jsx'; // OK

Mixed Import

// Default + named in the same import
import apiClient, { API_URL, handleError } from './services/api.js';

Import Paths

Relative Paths

// Same directory
import { helper } from './helper.js';

// Parent directory
import { config } from '../config.js';

// Subdirectory
import Button from './components/Button.jsx';

Package Imports (node_modules)

// npm packages - no relative path
import React from 'react';
import { useState, useEffect } from 'react';
import axios from 'axios';

Path Aliases (Vite/Webpack)

SmartMaker configures a src alias:

// Without alias (fragile relative paths)
import Button from '../../../components/Button.jsx';

// With alias (cleaner)
import Button from 'src/components/Button.jsx';

Configuration in vite.config.js:

export default defineConfig({
    resolve: {
        alias: {
            'src': '/src'
        }
    }
});

Comparison with PHP

// PHP - PSR-4 autoloading
namespace App\Services;

use App\Models\User;
use App\Utils\Helper;

class UserService {
    // ...
}
// JavaScript ES6
import User from 'src/models/User.js';
import { formatDate } from 'src/utils/helper.js';

export default class UserService {
    // ...
}
PHP JavaScript
namespace No direct equivalent (file = module)
use App\Class import Class from 'path'
new Class() new Class() or component <Class />
Autoloading Bundler (Vite/Webpack) resolves imports

React File Organization

Typical SmartMaker Structure

src/
├── components/
│   ├── app/                    # Providers, Router
│   │   ├── SmartCommonProvider/
│   │   │   └── index.jsx       # export default SmartCommonProvider
│   │   └── Router/
│   │       └── index.jsx
│   │
│   ├── pages/
│   │   ├── public/
│   │   │   └── LoginPage/
│   │   │       └── index.jsx
│   │   └── private/
│   │       └── HomePage/
│   │           └── index.jsx
│   │
│   └── common/
│       ├── Button/
│       │   └── index.jsx
│       └── Card/
│           └── index.jsx
│
├── hooks/
│   └── useAuth.js
│
├── services/
│   └── api.js
│
└── utils/
    └── format.js

Convention: index.jsx

When a directory contains index.jsx, you can import the directory directly:

// These two imports are equivalent
import Button from './components/common/Button/index.jsx';
import Button from './components/common/Button';

Barrel File (re-export)

A file that re-exports multiple modules:

// components/common/index.js
export { default as Button } from './Button';
export { default as Card } from './Card';
export { default as Modal } from './Modal';

// Usage
import { Button, Card, Modal } from 'src/components/common';

Dynamic Import

For code-splitting (lazy loading):

// Static import (loaded at startup)
import HeavyComponent from './HeavyComponent';

// Dynamic import (loaded when needed)
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

// Usage with Suspense
<Suspense fallback={<Loading />}>
    <HeavyComponent />
</Suspense>

Exercises

Exercise 1: Create a Module

Create a file utils/string.js that exports:

  • A constant DEFAULT_LOCALE = "fr-FR"
  • A function capitalize(str) that capitalizes the first letter
  • A default function formatName(firstName, lastName) that returns "LASTNAME Firstname"

Solution:

// utils/string.js

export const DEFAULT_LOCALE = "fr-FR";

export function capitalize(str) {
    if (!str) return '';
    return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}

export default function formatName(firstName, lastName) {
    return `${lastName.toUpperCase()} ${capitalize(firstName)}`;
}

Exercise 2: Import the Module

Import and use the created module:

// app.js
// 1. Import formatName (default)
// 2. Import capitalize and DEFAULT_LOCALE (named)
// 3. Display formatName("jean", "dupont")
// 4. Display capitalize("bonjour")

Solution:

// app.js
import formatName, { capitalize, DEFAULT_LOCALE } from './utils/string.js';

console.log(formatName("jean", "dupont")); // "DUPONT Jean"
console.log(capitalize("bonjour"));         // "Bonjour"
console.log(DEFAULT_LOCALE);                // "fr-FR"

Key Points to Remember

  1. Named export: export const x or export { x, y }
  2. Default export: export default X (only one per file)
  3. Named import: import { x, y } from './file' (braces)
  4. Default import: import X from './file' (no braces)
  5. Alias: import { x as alias } from './file'
  6. Import all: import * as Module from './file'
  7. index.jsx: allows import X from './folder'

Module 1 Summary

You now have the ES6+ JavaScript basics needed for React:

Concept Syntax
Variables const, let
Destructuring const { a, b } = obj
Spread { ...obj }, [...arr]
Arrow functions (x) => x * 2
Default parameters (x = 10) => ...
Template literals `Hello ${name}`
Promises .then(), .catch()
async/await async function, await
Modules import, export

<- Previous Chapter | Back to Module | Next Module: Introduction to React ->