Mengapa Kita Membutuhkan Sistem Modul?
Pada era awal web, semua berkas JavaScript dimuat dengan deretan <script src="..."> biasa yang berbagi satu ruang lingkup global (window) yang sama. Jika berkas navigasi.js mendeklarasikan variabel let terbuka = false, lalu berkas modal.js di bawahnya juga mendeklarasikan variabel bernama terbuka, keduanya akan saling bertabrakan!
Standar resmi ES Modules (ESM) menyelesaikan masalah ini secara permanen:
- **Cakupan Terisolasi per Berkas (Module Scope): Variabel, fungsi, atau class yang Anda tulis di dalam sebuah modul bersifat privat di berkas tersebut, kecuali Anda menandainya secara eksplisit dengan kata kunci
export**. - **Otomatis Berjalan dalam Strict Mode (
"use strict"):** Mencegah pembuatan variabel global tak sengaja dan menangkap kesalahan diam-diam sejak dini. - **Otomatis Bersifat Deferred di Peramban:** Menulis
<script type="module" src="/app.js"></script>di dalam<head>secara otomatis mengunduh modul beserta rantai impornya secara paralel tanpa memblokir pembacaan HTML!
1. Named Exports vs Default Export
Ada dua cara mengekspor dan mengimpor fungsionalitas antar-berkas:
A. Named Exports (Diekspor dengan Nama Pasti — Sangat Direkomendasikan!)
Satu berkas modul dapat memiliki banyak named export. Keunggulan utamanya: nama fungsi konsisten di seluruh proyek, memudahkan pelengkapan otomatis (autocomplete) di editor kode, dan memudahkan tree-shaking (penghapusan kode tak terpakai).
// Berkas: ./utilitas/format.js
export const MATA_UANG_BAWAAN = "IDR";
export function formatRupiah(nominal) {
return new Intl.NumberFormat("id-ID", {
style: "currency",
currency: MATA_UANG_BAWAAN,
maximumFractionDigits: 0
}).format(nominal);
}
export function formatTanggal(isoString) {
return new Intl.DateTimeFormat("id-ID", { dateStyle: "long" }).format(new Date(isoString));
}
Cara mengimpornya di berkas lain:
// Berkas: ./app.js
// PENTING: Di peramban murni (tanpa bundler), jalur relatif WAJIB diawali './' atau '../'
// dan menyertakan ekstensi berkas '.js'!
import { formatRupiah, formatTanggal as ubahKeTanggalIndo } from "./utilitas/format.js";
console.log(formatRupiah(150000)); // "Rp 150.000"
console.log(ubahKeTanggalIndo("2026-10-16")); // "16 Oktober 2026"
B. Default Export (Maksimal Satu per Berkas)
Biasanya digunakan jika satu berkas hanya mewakili satu class atau satu komponen utama:
// Berkas: ./layanan/PenghitungKuis.js
export default class PenghitungKuis {
// ...
}
// Berkas pemanggil:
import PenghitungKuis from "./layanan/PenghitungKuis.js";
2. Pemuatan Malas Sesuai Kebutuhan dengan Dynamic import()
Pernyataan import ... from "..." di bagian atas berkas bersifat statis: seluruh berkas yang diimpor akan diunduh saat halaman dibuka.
Namun, bagaimana jika halaman Anda memiliki fitur berat—seperti Editor Kode Interaktif atau Pengekspor Laporan PDF—yang hanya digunakan apabila pengguna menekan tombol "Buka Simulator"?
Gunakan fungsi import(jalurModul) dinamis yang mengembalikan sebuah Promise: berkas JavaScript tersebut baru akan diunduh dari jaringan pada detik pengguna benar-benar mengeklik tombolnya!
const tombolSimulator = document.querySelector("#buka-simulator");
tombolSimulator.addEventListener("click", async () => {
try {
tombolSimulator.disabled = true;
tombolSimulator.textContent = "Memuat mesin simulator...";
// Berkas simulator.js baru diunduh saat tombol diklik!
const modulSimulator = await import("./fitur/simulator.js");
modulSimulator.jalankanSimulator("#wadah-editor");
} catch (galat) {
console.error("Gagal memuat modul simulator:", galat);
} finally {
tombolSimulator.disabled = false;
}
});
3. Memetakan Nama Modul di Peramban dengan <script type="importmap">
Dulu, menulis import { html } from "lit"; (bare module specifier tanpa ./ atau https://) hanya bisa dilakukan jika kita memakai bundler (seperti Vite, Webpack, atau Rollup) karena peramban tidak tahu di alamat URL mana kata "lit" berada.
Kini, semua peramban modern mendukung <script type="importmap"> (status Baseline Widely Available sejak Maret 2023) yang memetakan nama singkat modul ke jalur berkas sesungguhnya langsung di HTML:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Aplikasi dengan Import Map</title>
<!-- Import Map harus diletakkan sebelum <script type="module"> pertama -->
<script type="importmap">
{
"imports": {
"@utilitas/": "/assets/js/utilitas/",
"penyimpanan": "/assets/js/layanan/penyimpanan.js"
}
}
</script>
<script type="module">
// Kini kita bisa mengimpor dengan nama bersih tanpa jalur '../../../' yang rumit!
import { formatRupiah } from "@utilitas/format.js";
import { penyimpananLokal } from "penyimpanan";
console.log(formatRupiah(250000));
</script>
</head>
<body></body>
</html>
4. Mengenal package.json dan Ekosistem Proyek Modern
Dalam repositori proyek JavaScript (termasuk repositori Kodeloka ini!), berkas package.json di akar proyek berfungsi sebagai manifes utama:
"type": "module": Memberitahu Node.js agar memperlakukan semua berkas.jsdi proyek sebagai ES Modules (sehingga mendukungimport/exportdan Top-Levelawait)."scripts": Pintasan perintah terminal yang dijalankan lewatnpm run <nama>(sepertinpm run dev,npm run build,npm test)."dependencies"vs"devDependencies": Daftar paket pustaka yang dibutuhkan saat aplikasi berjalan di produksi vs alat bantu pengembangan/pengujian (seperti Playwright untuk tes browser).package-lock.json: Mengunci versi persis beserta hash integritas dari setiap paket dan sub-paket agar instalasi vianpm cidi komputer seluruh anggota tim dan server CI/CD dijamin 100% identik!
Latihan Praktik: Memecah Aplikasi Menjadi Tiga Modul Terpisah
Buatlah folder latihan berisi tiga berkas:
konstanta.jsyang mengekspor tarif pajak dan daftar diskon.kalkulator.jsyang mengimporkonstanta.jsdan mengekspor fungsihitungHargaBersih(harga, kodeDiskon).index.htmlyang memuat<script type="importmap">dan<script type="module">untuk menjalankan perhitungan serta memuat modulcetak-struk.jssecara dinamis denganawait import("./cetak-struk.js")saat tombol ditekan.