Rel Blade-Vite sudah tersambung; sekarang saatnya mengisinya. Pada chapter ini kita menyusun struktur CSS dan JavaScript di folder resources/, memecah kode menjadi modul ES6 yang saling mengimpor, memakai package dari npm, sampai melihat bagaimana Vite membundling semuanya — termasuk gambar dan potongan kode yang sengaja dimuat belakangan. Semua kode di sini ditulis dan dibangun di sandbox, jadi setiap klaim tentang hasil build punya bukti nyata.

Titik Masuk yang Minim di Aplikasi Segar #

Aplikasi Laravel 13 baru lahir dengan frontend yang nyaris kosong: resources/js/app.js hanya berisi satu baris komentar //, dan folder resources/js/ tidak lagi memuat berkas bootstrap.js. Pada Laravel versi 12 ke bawah, bootstrap.js bawaan berisi konfigurasi axios (import axios from 'axios' plus pengaturan header X-Requested-With) yang diimpor oleh app.js. Tim Laravel kini membiarkan kita memilih sendiri HTTP client — axios, fetch bawaan browser, atau yang lain. Kita mulai dari nol, dan itu justru tempat terbaik untuk memahami tiap baris.

Modul ES6: Satu Tanggung Jawab per Berkas #

JavaScript modern memecah kode lewat export dan import. Berkas pertama kita, resources/js/format.js, mengekspor satu fungsi format uang:

js
// resources/js/format.js
export function formatRupiah(angka) {
    return new Intl.NumberFormat('id-ID', {
        style: 'currency',
        currency: 'IDR',
        maximumFractionDigits: 0,
    }).format(angka);
}

Berkas kedua mengimpornya untuk keperluan sendiri — bukti bahwa modul kita juga bisa jadi dependency modul lain:

js
// resources/js/salutation.js
import { formatRupiah } from './format';

export function sapa(nama, totalBelanja) {
    const jumlah = formatRupiah(totalBelanja);
    return `Halo ${nama}, total belanja kamu ${jumlah}.`;
}

Kata kunci export membuat fungsi terlihat dari luar berkas; import { nama } mengambil tepat yang kita butuhkan. Verifikasi tercepat tanpa browser adalah menjalankan modul ini langsung di Node, karena sintaks ES module sudah didukung penuh:

bash
node --input-type=module \
  -e "import { formatRupiah } from './resources/js/format.js'; console.log(formatRupiah(250000));"

Output yang Dihasilkan:

text
Rp 250.000

Entry Point: app.js Mengimpor Semuanya #

Sekarang kita isi resources/js/app.js — gerbang yang didaftarkan di input Vite. Versi yang kita pakai pada tahap ini mengimpor modul sendiri, gambar, package npm, sekaligus mendemokan dynamic import:

js
import { sapa } from './salutation';
import logo from '../images/logo.svg';
import dayjs from 'dayjs';
import 'dayjs/locale/id';

dayjs.locale('id');

console.log(sapa('Budi', 250000));
console.log('Dibangun pada:', dayjs('2026-01-20').format('DD MMMM YYYY'));
console.log('Logo:', logo);

// dynamic import: kode utilitas hanya dimuat saat benar-benar dibutuhkan
document.addEventListener('DOMContentLoaded', () => {
    const el = document.querySelector('[data-diskon]');
    el?.addEventListener('click', async () => {
        const { hitungDiskon } = await import('./diskon');
        el.textContent = `Hemat: ${hitungDiskon(1500000, 20)}`;
    });
});

Empat pola impor berbeda hidup di berkas ini. import { sapa } from './salutation' adalah impor lokal antar modul kita. import logo from '../images/logo.svg' meminta Vite memproses gambar — hasilnya berupa URL berkas ter-hash yang bisa dipakai di DOM atau CSS. import dayjs from 'dayjs' mengambil package npm yang kita pasang lewat npm install dayjs, lalu import 'dayjs/locale/id' memuat locale Indonesia tanpa mengekspor apa pun (impor hanya demi efek sampingnya). Terakhir, await import('./diskon') adalah dynamic import yang menunda pemuatan berkas diskon.js sampai tombol dengan atribut data-diskon benar-benar diklik.

CSS juga bisa dibuat mengikuti jalur yang sama. Bawaan Laravel 13 mendaftarkan resources/css/app.css sebagai entry terpisah di @vite, tetapi pola import '../css/app.css'; dari dalam app.js juga sah — pada sandbox, pola ini tetap menghasilkan berkas CSS di public/build/assets yang otomatis dimuat saat chunk JS dieksekusi.

Apa yang Vite Lakukan dengan Semuanya #

Jalankan npm run build, dan inilah output nyatanya:

text
transforming chunks...
public/build/assets/logo-8U9yLZMu.svg                           0.25 kB │ gzip:  0.19 kB
public/build/assets/app-n8mIr9Kk.css                           93.28 kB │ gzip: 18.26 kB
public/build/assets/diskon-DoBX3aLs.js                          0.06 kB │ gzip:  0.08 kB
public/build/assets/app-CGxU66Lq.js                            10.98 kB │ gzip:  4.79 kB

✓ built in 1.52s

Tiga detail pantas digarisbawahi. Pertama, logo.svg keluar sebagai berkas tersendiri dengan hash — Vite mengoptimalkan aset yang diimpor, bukan menyalinnya mentah. Kedua, diskon-DoBX3aLs.js terpisah dari app.js: dynamic import benar-benar menghasilkan chunk terpisah yang hanya diunduh browser saat pengguna mengeklik tombolnya. Ketiga, app.js membengkak ke 10.98 kB karena kode dayjs ikut terbundling — pengguna tidak perlu node_modules di komputernya. Manifest mencatat seluruh peta ini:

json
{
  "resources/js/app.js": {
    "file": "assets/app-CGxU66Lq.js",
    "isEntry": true,
    "dynamicImports": ["resources/js/diskon.js"],
    "assets": ["assets/logo-8U9yLZMu.svg"]
  },
  "resources/js/diskon.js": {
    "file": "assets/diskon-DoBX3aLs.js",
    "isDynamicEntry": true
  }
}

Struktur ini — modul kecil bermata satu, entry point yang merangkai, aset yang diimpor — adalah fondasi yang sama untuk semua frontend Laravel. Mulai chapter berikutnya kita isi sisi CSS-nya secara serius dengan Tailwind CSS, framework utility-first yang sudah terpasang di aplikasi segar kita.