Mekanisme export dan import sudah tuntas, tetapi mekanisme saja tidak membuat proyek rapi. Saat jumlah module tumbuh dari dua berkas menjadi dua puluh, pertanyaan yang muncul bukan lagi “bagaimana mengimpor”, melainkan “module yang dicari ada di mana” dan “mengimpor lima berkas satu per satu seperti ini seumur hidup?”. Chapter penutup section ini membahas kebiasaan menata module: penamaan, penempatan, dan satu berkas khusus bernama index.mjs yang menjadi pintu masuk sebuah folder.

Prinsip Menata Module #

Empat kebiasaan yang membuat struktur module tetap bisa dinavigasi seiring bertambahnya berkas:

  • Satu tanggung jawab per module. Nama berkas sekaligus menggambarkan isinya: produk.mjs untuk data dan pencarian produk, format.mjs untuk utilitas format. Berkas serba ada semacam utilitas-macam-macam.mjs mengaburkan batas tanggung jawab dan paling cepat menjadi sarang kode sulit ditebak.
  • Nama yang menyebut isi, bukan sifat. format.mjs lebih bermakna daripada helper.mjs; enam bulan kemudian, nama konkret yang menyelamatkan.
  • Kelompokkan berdasarkan fitur. Kode yang sering diubah bersama diletakkan berdekatan; pengimpor antar folder cukup melalui satu pintu (dibahas di bawah).
  • Arah ketergantungan satu arah. Prinsip dari chapter Circular Dependencies diterapkan di skala proyek: hindari folder yang saling mengimpor bolak-balik.

Aturan praktis untuk menentukan momen memecah: bila menjelaskan isi satu berkas membutuhkan kata “dan” untuk dua hal yang tidak bertetangga — data produk dan penyusunan struk, misalnya — keduanya biasanya lebih sehat di berkas terpisah. Sebaliknya, jangan terlalu cepat memecah: dua puluh berkas berisi satu fungsi kecil sama menyulitkan navigasi seperti satu berkas raksasa. Titik seimbangnya biasanya ditemukan saat satu berkas mulai dipakai oleh dua fitur yang berbeda arah perkembangannya.

Struktur Folder #

Struktur aktual chapter ini — sekaligus latihan kecilnya — terdiri atas empat module dan satu berkas pengimpor:

text
module-organization/        ← folder chapter ini (leaf bundle)
├── index.md                ← naskah chapter
├── produk.mjs              ← data produk dan pencariannya
├── format.mjs              ← utilitas format harga
├── index.mjs               ← barrel: pintu masuk folder kasir
└── utama.mjs               ← titik masuk program

Pada proyek yang lebih besar, pola yang sama diulang per folder fitur. Contoh susunan untuk aplikasi kasir dengan dua fitur:

text
src/
├── kasir/
│   ├── index.mjs
│   ├── produk.mjs
│   └── struk.mjs
├── laporan/
│   ├── index.mjs
│   └── penjualan.mjs
└── utama.mjs

Setiap folder fitur memuat module-module isiannya plus satu index.mjs. Berkas pengimpor di luar folder — misalnya src/utama.mjs — cukup mengenal folder lewat index.mjs-nya, tanpa perlu tahu berkas apa saja yang berada di dalamnya.

Jalur impor antar folder memakai ../: berkas di dalam laporan/ yang membutuhkan format harga menulis import { formatRupiah } from '../kasir/index.mjs';. Semakin dalam penempatan berkas, semakin panjang jalur relatifnya — salah satu alasan folder fitur dengan barrel pintu masuknya lebih disukai daripada menata berkas berdasarkan jenisnya (semua data di satu folder, semua utilitas di folder lain). Dengan folder fitur, apa yang berubah bersama tetap hidup berdekatan, dan jalur yang panjang hanya muncul di tempat yang memang lintas fitur.

index.mjs: Barrel #

Berkas index.mjs semacam ini disebut barrel: module yang isinya hanya re-export dari module-module sekitarnya. Ia tidak menambah logika apa pun; tugasnya merangkum “folder ini menawarkan apa saja”. Pemilihan nama index.mjs juga mengikuti konvensi umum: banyak proyek dan alat mengenali berkas bernama index sebagai entri sebuah folder, sehingga konvensi ini aman dibawa ke proyek mana pun. Bentuk barrelnya untuk struktur pertama:

js index.mjs
// index.mjs — barrel: satu pintu masuk untuk folder kasir
export { daftarProduk, cariProduk } from './produk.mjs';
export { formatRupiah } from './format.mjs';

Dua module yang dirangkum barrelnya masing-masing kecil dan berfokus. Berkas produk.mjs:

js produk.mjs
// produk.mjs — data produk dan pencariannya
export const daftarProduk = [
  { nama: "Kopi Susu", harga: 18000 },
  { nama: "Es Teh", harga: 8000 },
  { nama: "Roti Bakar", harga: 12000 },
];

export function cariProduk(nama) {
  return daftarProduk.find((p) => p.nama === nama) ?? null;
}

Dan berkas format.mjs:

js format.mjs
// format.mjs — utilitas format harga
export function formatRupiah(angka) {
  return "Rp" + angka.toLocaleString("id-ID");
}

Berkas utama.mjs membuktikan barrelnya bekerja: seluruh kebutuhan diambil dari satu jalur, ./index.mjs:

js utama.mjs
// utama.mjs — cukup tahu satu jalur impor: index.mjs
import { daftarProduk, cariProduk, formatRupiah } from './index.mjs';

for (const p of daftarProduk) {
  console.log(p.nama + ": " + formatRupiah(p.harga));
}

const roti = cariProduk("Roti Bakar");
console.log(roti);
console.log(formatRupiah(roti.harga));

Output yang Dihasilkan:

text
Kopi Susu: Rp18.000
Es Teh: Rp8.000
Roti Bakar: Rp12.000
{ nama: 'Roti Bakar', harga: 12000 }
Rp12.000

Mari kita telaah baris-baris pentingnya:

  1. Baris 2 index.mjs (export { daftarProduk, cariProduk } from './produk.mjs';): bentuk re-export — nilai diambil dari produk.mjs dan langsung ditawarkan lagi tanpa dideklarasikan di barrel. Barrel tidak pernah memegang nilainya sendiri.
  2. Baris 2 utama.mjs (import { ... } from './index.mjs';): satu jalur impor untuk tiga nilai dari dua module berbeda. Bila suatu saat produk.mjs dipecah lagi, berkas pengimpor tidak perlu berubah — yang disesuaikan hanya barrelnya.
  3. Baris 4–6 utama.mjs (for (const p of daftarProduk) ...): seluruh produk dicetak dengan format rupiah yang berasal dari module lain — kolaborasi dua module lewat satu pintu.
  4. Baris 8–10 utama.mjs (const roti = cariProduk("Roti Bakar"); ...): pencarian mengembalikan objek produk utuh { nama: 'Roti Bakar', harga: 12000 }, dan harga terakhirnya diformat menjadi Rp12.000.

Merawat barrel punya satu kebiasaan yang perlu dijaga: setiap module baru di dalam folder harus didaftarkan di index.mjs agar terlihat dari luar. Lupa mendaftar membuat module terselubung — berkasnya ada, tetapi tidak ada jalur impor yang menjangkaunya dari luar folder. Kebiasaan memeriksa barrel setiap menambah berkas sebaiknya ditanam sejak proyek masih kecil, ketika daftarnya masih pendek.

export * from dan Catatan Default #

Selain daftar re-export eksplisit, barrel juga bisa ditulis dengan bentuk selebaran:

js Barrel dengan export * from
export * from './produk.mjs';
export * from './format.mjs';

Bentuk ini menawarkan kembali seluruh named export module sumber. Tiga catatan penting sebelum memilihnya:

  • Default export tidak ikut. export * from melewati default; bila perlu, default harus di-re-export secara eksplisit dengan export { default } from './produk.mjs';.
  • Nama yang bentrok menjadi kabur. Bila dua module yang dirangkum lewat export * from mengandung nama sama, JavaScript tidak punya cara memutuskan versi mana yang dimaksud. Barrel eksplisit seperti di atas membuat konflik semacam itu tertangkap sejak ditulis.
  • API folder menjadi tidak terlihat. Dengan daftar eksplisit, isi index.mjs berfungsi sebagai daftar isi yang bisa dibaca; bentuk selebaran menyembunyikannya di balik nama-nama berkas sumber.

Dengan penataan folder dan barrel, section Modules tuntas dari dua arah: mekanisme bahasanya (export, import, import(), scope, siklus) dan kebiasaan menyusunnya dalam proyek nyata. Bekal organisasi ini juga ikut terbawa ke depan — section berikutnya, Object-Oriented JavaScript, membangun di atas fondasi yang sama, dan class yang akan kita tulis di sana paling sering hidup sebagai module tersendiri dengan satu tanggung jawab yang jelas.