Bab 12: Modul

3 menit baca

12.1 Pengantar Modul

💡Analogi

Bayangin kamu punya lemari besar dengan semua baju dicampur jadi satu — susah cari, susah rapiin. Modul itu kayak kamu pisahin baju ke laci-laci: laci kaos, laci celana, laci jaket. Setiap laci punya isinya sendiri, dan kamu ambil yang dibutuhkan aja.

Kenapa Perlu Modul?

Dulu, semua kode JavaScript ditaruh di satu file besar. Masalahnya:

  • Variabel bisa bentrok (nama sama)
  • Susah cari kode tertentu
  • Susah kerja tim (semua edit file yang sama)
  • Susah reuse kode

Modul menyelesaikan semua itu:

  • Setiap file = satu modul
  • Variabel di modul private (gak bocor keluar)
  • Hanya yang di-export yang bisa diakses modul lain

Cara Pakai Modul di Browser

html
<!-- Tambahkan type="module" -->
<script type="module">
  import { sapa } from './sapa.js';
  sapa("Budi");
</script>

Cara Pakai Modul di Node.js

Opsi 1: Pakai ekstensi .mjs

Opsi 2: Tambahkan "type": "module" di package.json

json
{
  "type": "module"
}

Fitur Modul

  1. Strict mode otomatis — Gak perlu tulis "use strict"
  2. Scope sendiri — Variabel gak bocor ke global
  3. Dieksekusi sekali — Mau di-import berkali-kali, kodenya cuma jalan sekali
  4. this = undefined — Di top-level modul, this bukan window
javascript
// file: counter.js
let count = 0;

export function tambah() {
  count++;
  return count;
}

// Mau di-import dari mana aja, count-nya SAMA (shared state)
javascript
// file: main.js
import { tambah } from './counter.js';

console.log(tambah()); // 1
console.log(tambah()); // 2
javascript
// file: lain.js
import { tambah } from './counter.js';

console.log(tambah()); // 3 (lanjut dari main.js!)
⚠️Jebakan!
javascript
// Di modul, variabel PRIVATE secara default
// file: rahasia.js
let password = "12345"; // Gak bisa diakses dari luar!

export let nama = "Budi"; // Ini bisa diakses

12.2 Export dan Import

Named Export

javascript
// file: math.js

// Export satu per satu
export const PI = 3.14159;

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

export function kurang(a, b) {
  return a - b;
}

// ATAU export sekaligus di bawah
const EULER = 2.71828;
function kali(a, b) {
  return a * b;
}
export { EULER, kali };

Named Import

javascript
// file: main.js

// Import yang dibutuhkan aja
import { PI, tambah } from './math.js';

console.log(PI);          // 3.14159
console.log(tambah(2, 3)); // 5

// Import semua dengan alias
import * as math from './math.js';

console.log(math.PI);          // 3.14159
console.log(math.tambah(2, 3)); // 5
console.log(math.EULER);       // 2.71828

Rename saat Import/Export

javascript
// Rename saat import
import { tambah as add, kurang as subtract } from './math.js';
console.log(add(2, 3)); // 5

// Rename saat export
// file: math.js
function tambah(a, b) { return a + b; }
export { tambah as add };

Default Export

Setiap modul boleh punya satu default export:

javascript
// file: User.js
export default class User {
  constructor(nama) {
    this.nama = nama;
  }

  sapa() {
    return `Halo, ${this.nama}!`;
  }
}
javascript
// file: main.js
// Import default — TANPA kurung kurawal, nama bebas
import User from './User.js';
// import Pengguna from './User.js'; // Nama bebas!

let user = new User("Budi");
console.log(user.sapa()); // "Halo, Budi!"

Named vs Default Export

FiturNamed ExportDefault Export
Jumlah per fileBanyakMaksimal 1
Sintaks importimport { nama }import nama (tanpa {})
Nama saat importHarus samaBebas
Kapan pakaiUtility functions, constantsClass utama, komponen utama

Campuran Named + Default

javascript
// file: api.js
export default class Api {
  // ...
}

export const BASE_URL = "https://api.example.com";
export function formatUrl(path) {
  return `${BASE_URL}${path}`;
}
javascript
// file: main.js
import Api, { BASE_URL, formatUrl } from './api.js';

Re-export

Berguna untuk bikin "barrel file" (satu pintu masuk):

javascript
// file: components/Button.js
export class Button { /* ... */ }

// file: components/Input.js
export class Input { /* ... */ }

// file: components/index.js — barrel file
export { Button } from './Button.js';
export { Input } from './Input.js';
// Atau: export * from './Button.js';
javascript
// file: main.js — import dari satu tempat
import { Button, Input } from './components/index.js';
⚠️Jebakan!
javascript
// SALAH — import harus di top-level (gak bisa di dalam if/function)
if (kondisi) {
  import { tambah } from './math.js'; // ❌ SyntaxError!
}

// SALAH — path harus string literal
let path = './math.js';
import { tambah } from path; // ❌ SyntaxError!

// Untuk kasus dinamis, pakai Dynamic Import (lihat sub-bab berikutnya)

12.3 Dynamic Import

💡Analogi

Named/default import itu kayak kamu bawa semua peralatan dari rumah sebelum berangkat kerja. Dynamic import itu kayak kamu pesan peralatan online — datangnya nanti, tapi kamu gak perlu bawa semua dari awal.

Kenapa Perlu Dynamic Import?

  • Code splitting — Load kode hanya saat dibutuhkan (halaman lebih cepat)
  • Conditional loading — Load modul berdasarkan kondisi
  • Lazy loading — Fitur berat di-load belakangan

Sintaks

javascript
// import() mengembalikan Promise
let module = await import('./math.js');

console.log(module.tambah(2, 3)); // 5
console.log(module.PI);           // 3.14159

Contoh: Conditional Loading

javascript
async function muatBahasa(kode) {
  let modul;

  if (kode === "id") {
    modul = await import('./bahasa/indonesia.js');
  } else if (kode === "en") {
    modul = await import('./bahasa/english.js');
  }

  return modul.default; // Ambil default export
}

let terjemahan = await muatBahasa("id");
console.log(terjemahan.sapa); // "Halo!"

Contoh: Lazy Loading Fitur Berat

javascript
// Fitur chart cuma di-load kalau user klik tombol
document.getElementById("btnChart").addEventListener("click", async () => {
  // Load library berat HANYA saat dibutuhkan
  let { buatChart } = await import('./chart-library.js');

  buatChart(data);
});

Dynamic Import dengan Default Export

javascript
// file: Greeting.js
export default function() {
  return "Halo Dunia!";
}

// file: main.js
let modul = await import('./Greeting.js');
let sapa = modul.default; // Akses default export via .default
console.log(sapa()); // "Halo Dunia!"
⚠️Jebakan!
javascript
// import() BUKAN fungsi biasa — ini sintaks khusus
// Gak bisa di-assign ke variabel:
let myImport = import; // ❌ SyntaxError!

// Tapi bisa dipanggil di mana aja (termasuk di dalam if, function, dll)
if (butuhFitur) {
  let modul = await import('./fitur.js'); // ✅
}
🎯Challenge

Buat sistem plugin sederhana:

  1. Buat 3 file "plugin": plugin-sapa.js, plugin-hitung.js, plugin-format.js
  2. Setiap plugin export default sebuah objek dengan properti nama dan method jalankan()
  3. Buat fungsi muatPlugin(namaPlugin) yang dynamic import plugin berdasarkan nama
  4. Jalankan plugin yang dimuat
javascript
// file: plugin-sapa.js
export default {
  nama: "Sapa",
  jalankan() {
    return "Halo dari plugin Sapa!";
  }
};

// file: main.js
async function muatPlugin(nama) {
  // Kode kamu di sini
  // Dynamic import berdasarkan nama
}

// Test:
let plugin = await muatPlugin("sapa");
console.log(plugin.nama);       // "Sapa"
console.log(plugin.jalankan()); // "Halo dari plugin Sapa!"

Sudah paham materi ini?

Tandai sebagai selesai untuk melacak progress-mu.