Sejak section Modules, kode kita terbagi menjadi banyak berkas: satu berkas utilitas, satu berkas komponen, dan seterusnya, yang saling terhubung lewat import. Browser modern sanggup membaca import secara langsung — tetapi proyek nyata membawa puluhan hingga ratusan berkas, ditambah format atau sintaks yang belum didukung semua browser. Mengirim ratusan permintaan kecil ke server, satu per satu, membuat halaman lambat terasa sejak detik pertama.
Bundler menjawabnya dengan kebalikan arah: membaca seluruh grafik module dari satu berkas titik masuk (entry point), lalu menggabungkannya menjadi satu (atau beberapa) berkas hasil yang siap dikirim — lengkap dengan pemangkasan kode yang tidak terpakai, pemampatan, dan source map. Chapter ini menuntaskan proses itu dengan esbuild pada proyek dua berkas, lalu memetakan posisi esbuild, Rollup, dan Vite.
Dari Entry ke Bundle #
Proyek latihan kita berisi dua berkas module. Berkas pertama menyediakan fungsi pemformat dan satu fungsi yang sengaja tidak dipakai:
export function formatRupiah(angka) {
return "Rp" + new Intl.NumberFormat("id-ID").format(angka);
}
export function helperTakDipakai() {
return "fungsi ini tidak ikut terpakai";
}Berkas kedua adalah titik masuk. Perhatikan bahwa ia mengimpor kedua fungsi tetapi hanya memanggil satu:
import { formatRupiah, helperTakDipakai } from "./format-rupiah.mjs";
console.log(formatRupiah(150000));Perintah bundling paling dasar menunjuk berkas entry, tanpa transformasi tambahan:
npx --yes esbuild@latest aplikasi.mjs --bundle --outfile=dist/aplikasi.jsOutput yang Dihasilkan:
dist/aplikasi.js 193b
⚡ Done in 2msIsi dist/aplikasi.js hasil bundling:
(() => {
// format-rupiah.mjs
function formatRupiah(angka) {
return "Rp" + new Intl.NumberFormat("id-ID").format(angka);
}
// aplikasi.mjs
console.log(formatRupiah(15e4));
})();Hasil ini memperlihatkan kerja bundler secara gamblang:
// format-rupiah.mjs: isi berkas utilitas disalin utuh ke dalam berkas keluaran, diberi komentar penanda asalnya. Pernyataanimportdanexportmenghilang — antar-berkas tidak lagi saling memanggil, karena semuanya kini satu kesatuan.formatRupiah(15e4): esbuild menuliskan literal angka150000dalam bentuk singkatnya,15e4. Keduanya bernilai identik; inilah gaya penulisan angka esbuild, dan nilainya tetap terbaca browser.(() => { ... })();: seluruh isi dibungkus IIFE (fungsi yang langsung dipanggil). Pembungkus ini menjaga variabel internal bundle agar tidak tumpah ke cakupan global halaman.
Berkas hasil langsung bisa dijalankan Node maupun dipasang di halaman HTML lewat tag <script> biasa — tanpa type="module", tanpa permintaan berkas kedua:
node dist/aplikasi.jsOutput yang Dihasilkan:
Rp150.000Tree-Shaking: Memangkas Kode Tak Terpakai #
aplikasi.mjs mengimpor helperTakDipakai tetapi tidak pernah memanggilnya. Bila bundler menyalin segala yang diimpor, fungsi itu ikut terkirim ke pengguna sebagai beban mati. esbuild melakukan tree-shaking — menyusuri grafik dependensi dan hanya menyertakan kode yang benar-benar dicapai (reachable). Bukti dari berkas hasil bundle di atas: nama helperTakDipakai tidak ada di mana pun. Pemeriksaan cepat memastikannya:
rg "helperTakDipakai|tidak ikut terpakai" dist/aplikasi.js || echo "tidak ditemukan"Output yang Dihasilkan:
tidak ditemukanMekanisme ini bisa berjalan karena import/export bersifat statis: bentuknya sudah pasti sebelum kode dieksekusi, sehingga bundler dapat menghitung dengan pasti fungsi mana yang tercapai. Kebiasaan yang mendukung tree-shaking antara lain mengimpor simbol yang dibutuhkan saja dan menghindari efek samping di level teratas module.
Minify dan Source Map Sekali Jalan #
Dua flag tambahan menyiapkan bundle untuk produksi — --minify memampatkan kode, --sourcemap membuat petanya:
npx --yes esbuild@latest aplikasi.mjs --bundle --minify --sourcemap --outfile=dist/aplikasi.min.jsOutput yang Dihasilkan:
dist/aplikasi.min.js 140b
dist/aplikasi.min.js.map 594b
⚡ Done in 2msIsi dist/aplikasi.min.js kini satu baris pendek:
(()=>{function i(r){return"Rp"+new Intl.NumberFormat("id-ID").format(r)}console.log(i(15e4));})();
//# sourceMappingURL=aplikasi.min.js.map
Perbandingannya jelas: 193 byte menjadi 140 byte pada proyek sekecil ini — pada proyek nyata, pemangkasan yang sama berlaku pada ratusan kilobyte. Minifier memangkas spasi dan nama (formatRupiah menjadi i), sedangkan source map yang dibahas chapter sebelumnya menjaga kemampuan debugging tetap utuh. Berkas .map-nya memuat keduanya:
{
"version": 3,
"sources": ["../format-rupiah.mjs", "../aplikasi.mjs"],
"sourcesContent": ["export function formatRupiah(angka) {\n return \"Rp\" + new Intl.NumberFormat(\"id-ID\").format(angka);\n}\nexport function helperTakDipakai() {\n return \"fungsi ini tidak ikut terpakai\";\n}\n", "import { formatRupiah, helperTakDipakai } from \"./format-rupiah.mjs\";\nconsole.log(formatRupiah(150000));\n"],
"mappings": "MAAO,SAASA,EAAaC,EAAO,CAClC,MAAO,KAAO,IAAI,KAAK,aAAa,OAAO,EAAE,OAAOA,CAAK,CAField sources mencantumkan dua berkas asal — bukti bahwa satu berkas hasil build mewakili dua berkas sumber.
Posisi esbuild, Rollup, dan Vite #
esbuild hanyalah satu nama di lanskap bundler. Tiga yang paling sering ditemui, dengan peran masing-masing:
| Alat | Ditulis dengan | Kekuatan utama | Posisi lazim |
|---|---|---|---|
| esbuild | Go | Kecepatan bundling dan minify yang tinggi | Mesin di balik alat lain; bundler mandiri untuk proyek sederhana |
| Rollup | JavaScript | Keluaran ESM yang bersih dan presisi untuk library | Bundler utama untuk package library dan build produksi Vite |
| Vite | JavaScript | Development server dengan hot module replacement | Lingkungan kerja framework modern; esbuild saat dev, Rollup saat build |
Pola yang layak dicatat dari tabel itu: alat-alat ini saling melengkapi, bukan saling meniadakan. Vite, misalnya, memakai esbuild untuk memproses kode dengan cepat saat pengembangan, lalu Rollup untuk bundling produksi yang lebih penuh fitur. Prinsip kerjanya — entry, grafik module, tree-shaking, minify, source map — tetap sama untuk semuanya, sehingga pemahaman dari chapter ini berlaku lintas alat.
Bundler bekerja di satu momen tertentu: saat kita menjalankan perintah build di mesin pengembang. Kapan persisnya hal-hal ini terjadi, dan apa yang masih berjalan di browser pengguna — dua momen itulah pembahasan penutup section ini.