kodingup.camp

Beranda/Kualitas Web: Aksesibilitas, Performa, Keamanan, Testing & Tooling

Bundler & Build Tool

Modul 8 dari 86 menit bacaBundlers and Build Tools

Isi modul

    Kenapa ini penting

    Bayangkan kamu pindahan rumah. Kamu bisa saja membawa barang satu per satu dengan motor, bolak-balik ratusan kali. Atau kamu bisa mengemasnya ke dalam kardus berlabel, membuang barang yang tidak terpakai, membungkus yang rapuh, lalu mengangkut semuanya dengan satu truk. Bundler adalah jasa pindahan untuk kodemu: ia mengumpulkan puluhan sampai ratusan file JavaScript dan CSS, membuang yang tidak terpakai, memadatkannya, memberi label versi, lalu menyiapkannya untuk dikirim ke browser pengguna seefisien mungkin.

    Selama Fase 2 kamu mungkin menulis <script src="app.js"> langsung di HTML. Itu cukup untuk proyek kecil. Begitu proyek tumbuh, masalah muncul: urutan <script> harus tepat, paket dari npm (misalnya pustaka tanggal) tidak bisa langsung di-import dengan namanya, setiap file berarti satu permintaan jaringan, kode tidak diminifikasi, dan browser terus memakai versi cache lama setelah kamu deploy. Build tool seperti Vite menyelesaikan semua itu, ditambah dev server yang langsung memperbarui halaman saat kamu menyimpan file. Vite juga fondasi yang akan kamu pakai di Fase 3 untuk React, dan menjadi dasar Vitest yang sudah kamu pakai di modul Testing.

    Konsep inti

    ES Modules: bahasa yang dipahami bundler

    ES Modules (ESM) adalah sistem modul standar JavaScript: export untuk mengekspor, import untuk mengimpor. Browser modern mendukungnya langsung lewat <script type="module">, yang juga otomatis berperilaku defer.

    // export bernama (bisa banyak per file)
    export function formatRupiah(angka) { /* ... */ }
    // export default (satu per file)
    export default function setupForm() { /* ... */ }
    
    // import
    import setupForm from './form.js';              // default
    import { formatRupiah } from './utils.js';       // bernama
    import * as utils from './utils.js';             // semua sebagai object
    const { grafik } = await import('./grafik.js');  // dinamis: dimuat saat dibutuhkan
    

    Tanpa bundler, browser butuh path lengkap ('./utils.js'). Dengan bundler, kamu bisa menulis import dayjs from 'dayjs' (bare import) dan bundler mencarinya di node_modules. Karena ESM bersifat statis (bisa dianalisis tanpa dijalankan), bundler bisa melakukan tree-shaking: membuang export yang tidak pernah di-import.

    Apa saja yang dilakukan bundler saat build?

    Langkah Apa yang terjadi Manfaat
    Resolve & bundle Mengikuti semua import, menggabungkan modul jadi sedikit file Lebih sedikit permintaan jaringan
    Tree-shaking Membuang kode yang tidak dipakai File lebih kecil
    Transform Mengubah sintaks sesuai target browser, memproses CSS/SCSS Kompatibilitas
    Minify Menghapus spasi, komentar, memendekkan nama variabel File jauh lebih kecil
    Code splitting import() dinamis menjadi file terpisah yang dimuat belakangan Halaman awal lebih ringan (bagus untuk LCP & INP)
    Hashing Nama file berisi hash isi: index-DTMPt3vS.js Cache setahun aman (modul Performa)
    Aset Gambar kecil di bawah 4 KiB di-inline sebagai base64, sisanya diberi hash Lebih sedikit permintaan

    Vite: dev cepat, build optimal

    Vite (dibaca "vit", bahasa Prancis untuk "cepat") versi 8.3 membutuhkan Node.js 20.19+ atau 22.12+. Ia bekerja dalam dua mode:

    Variabel environment

    Vite membaca file .env, .env.development, .env.production di root proyek. Hanya variabel berawalan VITE_ yang diekspos ke kode klien lewat import.meta.env. Vite juga menyediakan import.meta.env.MODE ('development'/'production'), DEV, dan PROD (boolean). Saat build, Vite mengganti ekspresi tersebut dengan nilai literal. Kami membuktikannya: console.log(import.meta.env.VITE_API_URL) di hasil build menjadi console.log(`https://api.contoh.id`), sementara variabel tanpa awalan VITE_ menjadi void 0 (undefined). Karena nilainya tertanam di file publik, VITE_ hanya untuk konfigurasi yang boleh dilihat publik (lihat modul Keamanan). Selalu masukkan .env ke .gitignore, dan sediakan .env.example tanpa nilai rahasia.

    PostCSS dan Autoprefixer

    PostCSS adalah alat untuk mengubah CSS lewat plugin, mirip bundler untuk CSS. Plugin paling terkenal adalah Autoprefixer, yang menambahkan vendor prefix (-webkit-, -moz-) berdasarkan daftar browser target dari Browserslist (file .browserslistrc atau kunci browserslist di package.json). Vite otomatis membaca postcss.config.js kalau file itu ada.

    Satu hal menarik dari pengujian kami di Vite 8: Autoprefixer menambahkan -webkit-user-select, tetapi minifier CSS bawaan Vite (Lightning CSS) menyesuaikan prefix dengan build.target/build.cssTarget, lalu membuang prefix yang dianggap tidak dibutuhkan target bawaan. Saat build dijalankan dengan --target safari14, prefix itu tetap ada. Pelajarannya: samakan target browser di Browserslist dan di konfigurasi build, lalu periksa file hasil build, jangan berasumsi.

    Contoh kode

    Membuat proyek dan melihat strukturnya:

    npm create vite@latest newsletter -- --template vanilla
    cd newsletter
    npm install
    npm run dev
    # →   VITE v8.3.5  ready in 185 ms
    # →   ➜  Local:   http://localhost:5173/
    
    newsletter/
    ├── index.html        ← titik masuk (bukan di public/, beda dengan alat lama)
    ├── package.json      ← "type": "module" + script dev/build/preview
    ├── public/           ← file disalin apa adanya (favicon, robots.txt)
    └── src/
        ├── main.js       ← di-import dari index.html lewat <script type="module">
        └── style.css     ← bisa di-import dari JS: import './style.css'
    

    Kode aplikasi yang memakai import CSS, environment, dan code splitting:

    // src/main.js
    import './style.css';                 // Vite memproses & menyuntikkan CSS
    import { setupForm } from './form.js';
    
    setupForm(document.querySelector('#app'));
    
    console.log('API:', import.meta.env.VITE_API_URL);
    console.log('Rahasia:', import.meta.env.RAHASIA_DB);
    console.log('Mode:', import.meta.env.MODE, 'DEV?', import.meta.env.DEV);
    
    // Code splitting: modul berat hanya diunduh saat tombol diklik
    document.querySelector('#lihat-statistik')?.addEventListener('click', async () => {
      const { tampilkanGrafik } = await import('./grafik.js'); // jadi file terpisah
      tampilkanGrafik();
    });
    
    # .env  (jangan di-commit!)
    VITE_API_URL=https://api.contoh.id
    RAHASIA_DB=jangan-bocor
    

    Build dan periksa hasilnya:

    npm run build
    # → vite v8.3.5 building client environment for production...
    # → ✓ 7 modules transformed.
    # → dist/index.html                 0.60 kB │ gzip: 0.35 kB
    # → dist/assets/index-CzUM7UuI.css  0.64 kB │ gzip: 0.39 kB
    # → dist/assets/index-DTMPt3vS.js   1.85 kB │ gzip: 0.93 kB
    # → ✓ built in 592ms
    
    # Buktikan apa yang tertanam di bundle publik:
    grep -o 'console.log([^)]*)' dist/assets/*.js
    # → console.log(`API:`,`https://api.contoh.id`)        ← VITE_ ikut terbawa!
    # → console.log(`Rahasia:`,void 0)                      ← tanpa VITE_: tidak terekspos
    # → console.log(`Mode:`,`production`,`DEV?`,!1)
    
    npm run preview
    # → ➜  Local:   http://localhost:4173/
    

    Perhatikan dist/index.html hasil build: Vite otomatis mengganti <script src="/src/main.js"> dengan file ber-hash dan menambahkan <link rel="stylesheet">:

    <script type="module" crossorigin src="/assets/index-DTMPt3vS.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-CzUM7UuI.css">
    

    Konfigurasi PostCSS + Autoprefixer, dan konfigurasi Vite gabungan (build + test):

    npm install -D postcss autoprefixer
    printf 'last 2 versions\n> 0.5%%\nnot dead\n' > .browserslistrc
    
    // postcss.config.js, otomatis dibaca Vite
    import autoprefixer from 'autoprefixer';
    
    export default {
      plugins: [autoprefixer()],
    };
    
    // vite.config.js
    import { defineConfig } from 'vitest/config';
    
    export default defineConfig({
      build: {
        // Samakan dengan target browser-mu. Bawaan: 'baseline-widely-available'
        // cssTarget: ['safari14'],
        sourcemap: true, // file .map untuk debugging error produksi
      },
      test: {
        environment: 'jsdom',
        include: ['src/**/*.test.js'],
        setupFiles: ['./vitest.setup.js'],
      },
    });
    
    # Bandingkan hasil CSS untuk target berbeda
    npx vite build --minify false && grep -o "[-a-z]*user-select[^;}]*" dist/assets/*.css
    # → -webkit-user-select: none
    # → -moz-user-select: none
    # → user-select: none
    npx vite build && grep -o "[-a-z]*user-select[^;}]*" dist/assets/*.css
    # → user-select:none      (Lightning CSS membuang prefix untuk target bawaan)
    npx vite build --target safari14 && grep -o "[-a-z]*user-select[^;}]*" dist/assets/*.css
    # → -webkit-user-select:none
    # → user-select:none
    

    Akhirnya, package.json proyek lengkap dari seluruh materi ini:

    {
      "name": "newsletter",
      "private": true,
      "type": "module",
      "scripts": {
        "dev": "vite",
        "build": "vite build",
        "preview": "vite preview",
        "test": "vitest",
        "e2e": "playwright test",
        "lint": "eslint . && stylelint \"src/**/*.css\"",
        "format": "prettier . --write",
        "prepare": "husky"
      }
    }
    

    Kesalahan umum

    1. Membuka index.html langsung dengan klik dua kali (file://). Module script dan import tidak jalan dari file://. Selalu pakai npm run dev atau npm run preview.
    2. Men-deploy folder proyek, bukan dist/. Browser lalu meminta /src/main.js mentah dan bare import gagal. Yang di-deploy adalah isi dist/ hasil npm run build.
    3. Menaruh rahasia di VITE_.... Nilainya tertanam di bundle publik. Rahasia hanya di server.
    4. Menaruh file yang di-import di public/. Isi public/ tidak diproses (tidak di-hash, tidak diminifikasi). Gambar dan CSS yang di-import dari kode taruh di src/. public/ untuk file yang butuh nama tetap seperti robots.txt dan favicon.svg.
    5. Mengira vite preview adalah server produksi. Itu hanya untuk mengecek hasil build secara lokal. Di produksi, pakai hosting statis (Netlify, Vercel, Cloudflare Pages) atau server seperti contoh di modul Keamanan.

    Latihan

    1. Mudah, Jelaskan kenapa nama file hasil build seperti index-DTMPt3vS.js berubah setiap kali isinya berubah, dan apa hubungannya dengan header cache.
    2. Sedang, Buat file .env.development berisi VITE_API_URL=http://localhost:3000 dan .env.production berisi VITE_API_URL=https://api.contoh.id. Tulis kode yang memakai URL itu untuk fetch('/produk'), lalu jelaskan nilai mana yang dipakai saat npm run dev dan saat npm run build.
    3. Menantang, Buat modul src/grafik.js yang mengekspor tampilkanGrafik(), muat dengan import() dinamis saat tombol diklik, lalu buktikan lewat output npm run build bahwa modul itu menjadi file terpisah.
    Lihat solusi

    1. Hash dihitung dari isi file. Isi berubah, hash berubah, nama file berubah. Karena itu file di /assets/ aman diberi Cache-Control: public, max-age=31536000, immutable: browser tidak akan memakai versi lama, karena index.html baru (yang no-cache) menunjuk ke nama file baru.

    2.

    // src/api.js
    const BASE = import.meta.env.VITE_API_URL;
    
    export async function ambilProduk() {
      const res = await fetch(`${BASE}/produk`);
      if (!res.ok) throw new Error(`Gagal memuat produk: ${res.status}`);
      return res.json();
    }
    // npm run dev   → mode development → http://localhost:3000/produk
    // npm run build → mode production  → https://api.contoh.id/produk (tertanam di bundle)
    

    3.

    // src/grafik.js
    export function tampilkanGrafik() {
      const el = document.createElement('p');
      el.textContent = 'Grafik dimuat!';
      document.querySelector('#app').append(el);
    }
    
    // src/main.js (tambahan)
    const tombol = document.createElement('button');
    tombol.type = 'button';
    tombol.textContent = 'Lihat statistik';
    tombol.addEventListener('click', async () => {
      const { tampilkanGrafik } = await import('./grafik.js');
      tampilkanGrafik();
    });
    document.querySelector('#app').append(tombol);
    
    npm run build
    # → dist/assets/grafik-BDfR925C.js   0.14 kB   ← file terpisah (chunk)
    # → dist/assets/index-Cbslhm8Y.js    …
    

    Di tab Network DevTools, grafik-*.js baru diunduh saat tombol diklik.

    Ringkasan

    Video pendamping

    Kalau penjelasan di atas belum masuk, tonton ini. Video dari kanal YouTube pihak lain.

    Tutorial Vite : Frontend Build Tool (Bahasa Indonesia)Programmer Zaman Now · Bahasa IndonesiaBuka di YouTube

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.