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:

js aplikasi.mjs
import { formatRupiah, helperTakDipakai } from "./format-rupiah.mjs";
console.log(formatRupiah(150000));

Perintah bundling paling dasar menunjuk berkas entry, tanpa transformasi tambahan:

bash
npx --yes esbuild@latest aplikasi.mjs --bundle --outfile=dist/aplikasi.js

Output yang Dihasilkan:

text
  dist/aplikasi.js  193b 

⚡ Done in 2ms

Isi dist/aplikasi.js hasil bundling:

js dist/aplikasi.js (hasil bundle)
(() => {
  // 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:

  1. // format-rupiah.mjs: isi berkas utilitas disalin utuh ke dalam berkas keluaran, diberi komentar penanda asalnya. Pernyataan import dan export menghilang — antar-berkas tidak lagi saling memanggil, karena semuanya kini satu kesatuan.
  2. formatRupiah(15e4): esbuild menuliskan literal angka 150000 dalam bentuk singkatnya, 15e4. Keduanya bernilai identik; inilah gaya penulisan angka esbuild, dan nilainya tetap terbaca browser.
  3. (() => { ... })();: 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:

bash
node dist/aplikasi.js

Output yang Dihasilkan:

text
Rp150.000

Tree-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:

bash
rg "helperTakDipakai|tidak ikut terpakai" dist/aplikasi.js || echo "tidak ditemukan"

Output yang Dihasilkan:

text
tidak ditemukan

Mekanisme 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:

bash
npx --yes esbuild@latest aplikasi.mjs --bundle --minify --sourcemap --outfile=dist/aplikasi.min.js

Output yang Dihasilkan:

text
  dist/aplikasi.min.js      140b 
  dist/aplikasi.min.js.map  594b 

⚡ Done in 2ms

Isi dist/aplikasi.min.js kini satu baris pendek:

js dist/aplikasi.min.js (hasil minify)
(()=>{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:

json dist/aplikasi.min.js.map (awal berkas)
{
  "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,CA

Field 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:

AlatDitulis denganKekuatan utamaPosisi lazim
esbuildGoKecepatan bundling dan minify yang tinggiMesin di balik alat lain; bundler mandiri untuk proyek sederhana
RollupJavaScriptKeluaran ESM yang bersih dan presisi untuk libraryBundler utama untuk package library dan build produksi Vite
ViteJavaScriptDevelopment server dengan hot module replacementLingkungan 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.