1. Pengenalan ES Modules
ES Modules (ESM) adalah sistem modul resmi yang diperkenalkan dalam spesifikasi ECMAScript 2015 (ES6). ESM memungkinkan Anda memecah kode JavaScript menjadi file-file terpisah yang saling terhubung melalui mekanisme import dan export. Ini adalah standar modern yang didukung oleh semua browser utama dan Node.js.
Sebelum ESM, JavaScript tidak memiliki sistem modul bawaan di browser. Pengembang mengandalkan pola seperti IIFE (Immediately Invoked Function Expressions), CommonJS (Node.js), atau AMD (RequireJS). ESM menyatukan semuanya menjadi standar yang konsisten.
Mengapa ES Modules Penting?
| Keunggulan | Penjelasan |
|---|---|
| Modularitas | Kode terorganisir dalam file terpisah, mudah dikelola dan dipelihara |
| Encapsulation | Setiap modul memiliki scope sendiri, variabel tidak bocor ke global |
| Tree Shaking | Bundler bisa menghapus kode yang tidak digunakan untuk mengurangi ukuran bundle |
| Static Analysis | Struktur import/export bisa dianalisis saat compile time |
| Browser Native | Didukung langsung oleh browser modern tanpa perlu bundler |
| Async Loading | Dynamic import memungkinkan lazy loading modul secara asynchronous |
ES Modules vs CommonJS vs AMD
| Aspek | ES Modules | CommonJS | AMD |
|---|---|---|---|
| Sintaks | import/export | require/module.exports | define/require |
| Loading | Static & Async | Synchronous | Asynchronous |
| Environment | Browser & Node.js | Node.js (server) | Browser |
| Tree Shaking | ✅ Ya | ❌ Tidak | ❌ Tidak |
| Top-level Await | ✅ Ya | ❌ Tidak | ❌ Tidak |
| Strict Mode | Otomatis | Manual | Manual |
Setup Awal
Untuk menggunakan ES Modules di browser, tambahkan atribut type="module" pada tag script di HTML:
7. Top-Level Await
Top-level await memungkinkan Anda menggunakan await di luar fungsi async, langsung pada level modul. Fitur ini tersedia di ES2022 dan sangat berguna untuk inisialisasi modul yang membutuhkan operasi asynchronous.
Penggunaan Top-Level Await
9. Best Practices ES Modules
Berikut adalah panduan dan best practices untuk menggunakan ES Modules secara efektif dalam proyek JavaScript modern.
Organisasi File & Struktur Modul
src/
/"features/"/
auth/
/"AuthContext.js"/
/"LoginForm.js"/
/"useAuth.js"/
/"authApi.js"/
/"index.js"/
/"dashboard/"/
/"DashboardPage.js"/
/"DashboardCharts.js"/
/"index.js"/
/"profile/"/
/"ProfilePage.js"/
/"ProfileForm.js"/
/"index.js"/
shared/
/"components/"/
/"Button.js"/
/"Modal.js"/
/"index.js"/
hooks/
/"useLocalStorage.js"/
/"useDebounce.js"/
/"index.js"/
utils/
/"formatters.js"/
/"validators.js"/
/"index.js"/
app.js
/"routes.js"/
Aturan Penting ES Modules
// ====== 1. SELALU gunakan file extension (.js) ======
// Beberapa bundler membutuhkan eksplicit extension
import { helper } from './utils/helper.js'; // ✅
import { helper } from './utils/helper'; // ❌ bisa error di Deno/native ESM
// ====== 2. Hindari circular dependencies ======
// a.js mengimpor b.js, b.js mengimpor a.js → masalah!
// Solusi: pindahkan shared code ke modul ketiga (c.js)
// ====== 3. Satu ekspor per file (Single Responsibility) ======
// ✅ Bagus: file fokus pada satu hal
// UserService.js — hanya tentang User Service
export class UserService {
async getUser(id) { /* ... */ }
async updateUser(id, data) { /* ... */ }
async deleteUser(id) { /* ... */ }
}
// ❌ Kurang baik: file menangani banyak hal berbeda
// utils.js — terlalu banyak ekspor berbeda
export function formatRupiah() { /* ... */ }
export function validateEmail() { /* ... */ }
export function sendNotification() { /* ... */ }
export class DatabaseService { /* ... */ }
export const API_URL = '...';
// ====== 4. Gunakan constants terpisah ======
// constants/routes.js
export const ROUTES = {
HOME: '/',
LOGIN: '/login',
DASHBOARD: '/dashboard',
PROFILE: '/profile',
SETTINGS: '/settings',
};
export const API_ENDPOINTS = {
USERS: '/api/users',
PRODUCTS: '/api/products',
ORDERS: '/api/orders',
};
// ====== 5. Type exports (untuk TypeScript/JSDoc) ======
/** @typedef {{ id: number, nama: string, email: string }} User */
// Export type sebagai value (untuk JSDoc runtime checking)
export const UserType = Object.freeze({
id: 'number',
nama: 'string',
email: 'string',
});
Anti-Pattern yang Harus Dihindari
// ❌ 1. Mutable exports — bisa menyebabkan bug tak terduga
export let counter = 0;
export function increment() { counter++; }
// Importer mendapat binding langsung, bukan copy!
// ✅ Solusi: Gunakan getter pattern
let _counter = 0;
export function getCounter() { return _counter; }
export function increment() { _counter++; }
// ❌ 2. Import dengan side effects yang tidak disadari
import './analytics'; // Apa yang dilakukan modul ini?
// ✅ Solusi: Jelaskan mengapa Anda mengimpor side-effect module
// Load analytics untuk tracking page views
import './analytics';
// ❌ 3. Deep import dari internal package
import helper from 'some-package/lib/internal/utils/helper.js';
// ✅ Solusi: Gunakan public API
import { helper } from 'some-package';
// ❌ 4. Barrel file yang terlalu besar
// components/index.js — re-export 100+ komponen!
export { Button } from './Button';
export { Modal } from './Modal';
// ... 98 lagi
// ✅ Solusi: Sub-barrel files
// components/forms/index.js — hanya form components
// components/layout/index.js — hanya layout components
// ❌ 5. Mix CommonJS dan ESM secara acak
const fs = require('fs'); // CommonJS
import path from 'path'; // ESM
// Di Node.js, ini bisa menyebabkan error!
// ✅ Solusi: Pilih satu, konsisten
import fs from 'node:fs/promises'; // ESM sepanjang jalan
import path from 'node:path';
10. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang ES Modules: