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.mjsuntuk data dan pencarian produk,format.mjsuntuk utilitas format. Berkas serba ada semacamutilitas-macam-macam.mjsmengaburkan batas tanggung jawab dan paling cepat menjadi sarang kode sulit ditebak. - Nama yang menyebut isi, bukan sifat.
format.mjslebih bermakna daripadahelper.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:
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 programPada proyek yang lebih besar, pola yang sama diulang per folder fitur. Contoh susunan untuk aplikasi kasir dengan dua fitur:
src/
├── kasir/
│ ├── index.mjs
│ ├── produk.mjs
│ └── struk.mjs
├── laporan/
│ ├── index.mjs
│ └── penjualan.mjs
└── utama.mjsSetiap 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:
// 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:
// 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:
// 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:
// 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:
Kopi Susu: Rp18.000
Es Teh: Rp8.000
Roti Bakar: Rp12.000
{ nama: 'Roti Bakar', harga: 12000 }
Rp12.000Mari kita telaah baris-baris pentingnya:
- Baris 2
index.mjs(export { daftarProduk, cariProduk } from './produk.mjs';): bentuk re-export — nilai diambil dariproduk.mjsdan langsung ditawarkan lagi tanpa dideklarasikan di barrel. Barrel tidak pernah memegang nilainya sendiri. - Baris 2
utama.mjs(import { ... } from './index.mjs';): satu jalur impor untuk tiga nilai dari dua module berbeda. Bila suatu saatproduk.mjsdipecah lagi, berkas pengimpor tidak perlu berubah — yang disesuaikan hanya barrelnya. - 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. - Baris 8–10
utama.mjs(const roti = cariProduk("Roti Bakar"); ...): pencarian mengembalikan objek produk utuh{ nama: 'Roti Bakar', harga: 12000 }, dan harga terakhirnya diformat menjadiRp12.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:
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 * frommelewati default; bila perlu, default harus di-re-export secara eksplisit denganexport { default } from './produk.mjs';. - Nama yang bentrok menjadi kabur. Bila dua module yang dirangkum lewat
export * frommengandung 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.mjsberfungsi 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.