Vue · Bab 10
Dari Prototipe ke Produksi
Memindah kode lab ke proyek Vite dengan komponen SFC, mengatur VITE_API_URL, membangun berkas statis, fallback SPA, CORS di Laravel, tempat token di produksi, dan mempublikasikan hasil sebagai situs statis.
Selama bab 1–9 aplikasi Vue-mu hidup di satu index.html di dalam lab. Di dunia kerja, aplikasi frontend terpisah dibangun sebagai proyek dengan alat build, lalu dipasang sebagai berkas statis. Bab ini adalah jobsheet laptop: kerjakan di komputermu sendiri dengan Node.js terpasang. Tidak ada preview di halaman ini.
10.1 Dari Satu Berkas ke SFC
Di proyek sungguhan, setiap komponen ditulis sebagai Single File Component (.vue): template, skrip, dan gaya dalam satu berkas.
| Di lab | Di SFC |
|---|---|
<template id="tpl-kartu-buku"> + template: '#tpl-kartu-buku' | blok <template> di KartuBuku.vue |
setup() { … return { … } } | <script setup>: semua yang dideklarasikan langsung terpakai di template |
<style> bersama | <style scoped>: gaya hanya berlaku untuk komponen ini |
const { ref } = Vue; | import { ref } from 'vue'; |
<!-- src/components/KartuBuku.vue -->
<script setup>
const props = defineProps({ buku: { type: Object, required: true } });
const emit = defineEmits(['pinjam']);
</script>
<template>
<article class="kartu">
<h2>{{ props.buku.judul }}</h2>
<p>{{ props.buku.penulis }}</p>
<button type="button" @click="emit('pinjam', props.buku)">Pinjam</button>
</article>
</template>
<style scoped>
.kartu { border: 1px solid #cbd5e1; border-radius: 8px; padding: 12px; }
</style>
Masalah template di dalam DOM dari bab 3 (huruf besar-kecil, tag yang menutup sendiri) tidak ada lagi di SFC, karena template dikompilasi oleh alat build, bukan diurai browser.
10.2 Jobsheet: Proyek Vite
npm create vue@latest pelita-web # pilih: Router = Yes, Pinia = Yes, TypeScript = No
cd pelita-web
npm install
npm install axios
npm run dev # http://localhost:5173
Susunan yang disarankan di src/: components/ (komponen kecil), views/ (satu komponen per rute), stores/ (store Pinia), dan api.js (instance axios dan interceptor). Pindahkan kode lab bagian demi bagian, lalu jalankan setiap kali satu bagian selesai.
10.3 Alamat API: VITE_API_URL
Alamat API berbeda di laptop dan di server, jadi ditulis di berkas lingkungan, bukan di kode:
# .env (tidak di-commit)
VITE_API_URL=http://127.0.0.1:8000/api
# .env.example (di-commit, tanpa nilai rahasia)
VITE_API_URL=
// src/api.js
import axios from 'axios';
export const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: { Accept: 'application/json' },
});
Hanya variabel berawalan VITE_ yang terbaca kode frontend, dan semuanya ikut ter-bundle ke berkas JavaScript yang publik. Siapa pun bisa membacanya lewat DevTools. Karena itu kunci rahasia, password database, atau token admin tidak pernah ditaruh di frontend; tempatnya di server Laravel.
10.4 Build Statis
npm run build # hasil di folder dist/
npm run preview # mencoba hasil build di http://localhost:4173
dist/ berisi HTML, CSS, dan JavaScript biasa yang bisa dipasang di hosting statis mana pun. Build yang baik selesai tanpa peringatan; bila ada, baca dan perbaiki sebelum dipasang.
10.5 Fallback SPA
Dengan createWebHashHistory() (dipakai di lab), alamatnya /#/buku/3 dan server hanya pernah melihat /. Dengan createWebHistory(), alamatnya /buku/3 yang lebih rapi, tetapi saat pengguna memuat ulang halaman itu, server mencari berkas /buku/3 yang tidak ada. Host harus diatur supaya semua path yang tidak dikenal dijawab dengan index.html (fallback SPA). Caranya berbeda per layanan hosting; cari “SPA fallback” di dokumentasinya. Bila ragu, tetap pakai hash history.
10.6 CORS di Laravel
Frontend di http://localhost:5173 dan API di http://127.0.0.1:8000 berbeda origin. Browser hanya mengizinkan JavaScript membaca jawaban API bila server mengizinkan origin itu. Di Laravel, atur config/cors.php:
'paths' => ['api/*'],
'allowed_origins' => ['http://localhost:5173'],
'allowed_headers' => ['Content-Type', 'Authorization', 'Accept'],
Request dengan header Authorization didahului request preflight OPTIONS; Laravel menjawabnya otomatis bila konfigurasi di atas benar.
10.7 Token di Produksi
Pilihan yang sama dengan bab 8, kini dengan akibat nyata: token di memori aman dari pencurian lewat XSS tetapi hilang saat halaman dimuat ulang (pengguna login ulang); localStorage bertahan tetapi terbaca skrip jahat; cookie httpOnly (Sanctum mode stateful) paling kuat tetapi butuh pengaturan server dan domain yang sama, di luar cakupan kursus ini. Apa pun pilihannya, keluar yang sungguhan berarti token dicabut di server.
10.8 Publikasikan hasilmu
Hasil npm run build bisa dipasang gratis sebagai situs statis, misalnya di GitHub Pages atau Cloudflare Pages: unggah isi dist/ atau sambungkan repositori dan atur perintah build npm run build dengan folder keluaran dist. Batas dan syarat layanan bisa berubah, jadi baca ketentuannya saat mendaftar.
API Laravel (tingkat 3–4) tidak bisa dipasang sebagai situs statis, sehingga situs publikmu tidak bisa menyambung ke Laravel di laptopmu. Untuk demo publik, pakai mode demo tanpa server: unduh berkas tiruan-smart-retail.js dari folder /vue-runner/ situs ini. Berkas itu berisi adapter axios yang menjawab dari data contoh Smart Retail yang sama dengan lab, di dalam browser. Pasang adapter itu hanya ketika VITE_API_URL kosong:
// src/api.js (mode demo)
import axios from 'axios';
import { buatAdapterDemo } from './demo/tiruan.js'; // berkas demo yang diunduh, disimpan dengan nama ini
const urlApi = import.meta.env.VITE_API_URL;
export const api = axios.create({
baseURL: urlApi || 'http://127.0.0.1:8000/api',
headers: { Accept: 'application/json' },
...(urlApi ? {} : { adapter: buatAdapterDemo(axios) }),
});
Tulis dengan jelas di halaman demo bahwa yang tampil adalah data contoh yang kembali ke awal setiap halaman dimuat ulang. Menyambung ke Laravel sungguhan tetap dilakukan di laptop: php artisan serve dan npm run dev berdampingan.
10.9 Daftar Periksa Serah Terima
README.md: cara menjalankan, isi.env.example, akun contoh, dan tautan demo..env.exampleada;.envtidak ikut di-commit.npm run buildtanpa peringatan.- Setiap halaman punya keadaan memuat, kosong, galat, dan berhasil.
- Diuji di lebar 375 dan 1024; axe atau Lighthouse tanpa pelanggaran aksesibilitas dasar.
Berikutnya: Bab 11 berisi tantangan untuk menerapkan semua ini pada proyekmu sendiri.