๐ FullStuck v0.4 โ Documentation โ
FullStuck.php adalah micro-framework PHP 1-file yang menggunakan Path-Based Colocation โ URL website Anda dicerminkan langsung dari struktur folder. Tanpa vendor/, tanpa Composer, tanpa konfigurasi routing manual.
๐ฆ 1. Instalasi & Quick Start โ
๐ค Install via AI Coding Agent โ
Install fullstuck.php. Panduan: https://raw.githubusercontent.com/stuckfull/fullstuck/refs/heads/main/docs/ai-setup.mdDownload Manual โ
curl -O https://raw.githubusercontent.com/stuckfull/fullstuck/refs/heads/main/fullstuck.phpCLI (Rekomendasi) โ
php fullstuck.php init --db=sqlite --agent_js=yes --scaffold=yes --htaccess=yes| Flag | Pilihan | Default |
|---|---|---|
--db | sqlite, mysql, pgsql, none | sqlite |
--agent_js | yes, no | yes |
--scaffold | yes, no | yes |
--htaccess | yes, no | yes |
Menjalankan Server Lokal โ
php -S localhost:8000 fullstuck.phpWeb Server Deployment โ
Apache / LiteSpeed โ .htaccess sudah di-generate otomatis oleh init.
Nginx:
location / {
try_files $uri $uri/ /fullstuck.php?$query_string;
}FrankenPHP / Caddy:
:8000 {
root * .
php_server { index fullstuck.php }
}Membaca Dokumentasi via CLI โ
php fullstuck.php docs # Daftar isi
php fullstuck.php docs:1 # Bab tertentu
php fullstuck.php docs:9 # API Reference
php fullstuck.php docs:full # Seluruh dokumen๐๏ธ 2. Struktur Folder & Routing โ
Topologi Proyek โ
my-project/
โโโ .htaccess # Pengaman web server
โโโ fullstuck.json # Konfigurasi utama
โโโ fullstuck.php # Core engine (jangan diedit)
โโโ cache/ # Auto-generated (masukkan ke .gitignore)
โโโ components/ # Komponen UI reusable
โโโ globals/ # Helper PHP (auto-loaded)
โโโ app/ # Pusat routing (Path-Based Colocation)Cara Kerja Routing โ
Setiap folder di app/ otomatis menjadi endpoint URL. Letakkan salah satu dari 4 file standar (4-Pillars) untuk mengaktifkannya:
| File | Fungsi |
|---|---|
content.fst.php | Tampilan halaman (GET). Menggunakan syntax Blade. |
action.php | Logika mutasi data (POST/PUT/PATCH/DELETE). |
client.js | Script frontend SPA. Otomatis dibungkus IIFE. |
_guard.php | Middleware pelindung. Berjalan sebelum content/action. |
Contoh Pemetaan โ
app/
โโโ content.fst.php โ GET /
โโโ _layout.fst.php โ Layout global
โโโ _guard.php โ Guard global
โโโ blog/
โ โโโ content.fst.php โ GET /blog
โ โโโ client.js โ JS untuk /blog
โ โโโ [slug]/
โ โโโ content.fst.php โ GET /blog/artikel-saya
โ โโโ action.php โ POST /blog/artikel-saya
โโโ api/
โ โโโ products/
โ โโโ action.php โ GET|POST|PUT|DELETE /api/products
โโโ admin/
โโโ _guard.php โ Melindungi /admin/*
โโโ _layout.fst.php โ Layout khusus admin
โโโ users/
โโโ content.fst.php โ GET /admin/usersDynamic Routing (Parameter URL) โ
Buat folder dengan nama [parameter] (kurung siku). Nilainya otomatis tersedia di:
fst_input('slug')$_GET['slug']$_REQUEST['slug']
// Di app/blog/[slug]/content.fst.php
<?php $slug = fst_input('slug'); ?>
<h1>Artikel: {{ $slug }}</h1>Mode Headless API โ
Jika sebuah folder hanya berisi action.php (tanpa content.fst.php), maka action.php juga akan melayani request GET. Cocok untuk endpoint REST API murni.
app/api/products/action.php โ Menerima GET, POST, PUT, DELETEPewarisan Layout & Guard (Top-Down & Bottom-Up) โ
Kedua file ini bersifat kumulatif, namun dengan arah eksekusi yang berbeda:
_guard.phpdieksekusi Top-Down (Luar ke Dalam): Guard terluar (misal/app/_guard.php) akan dijalankan lebih dulu. Ini memungkinkan penghentian akses sedini mungkin sebelum mencapai rute di dalamnya._layout.fst.phpdirangkai Bottom-Up (Dalam ke Luar / Matryoshka): Konten dari anak akan di-render terlebih dahulu, dimasukkan ke layout anak, lalu keseluruhan hasilnya dibungkus oleh layout induk (bapak).
๐จ 3. Syntax Templating โ
File .fst.php dikompilasi otomatis menjadi PHP murni dan di-cache. Syntax mirip Laravel Blade:
Output & Escaping โ
<!-- Otomatis di-escape (aman dari XSS) -->
{{ $user['name'] }}
<!-- Output mentah (hanya untuk HTML terpercaya) -->
{!! $html_content !!}
<!-- Escape kurung kurawal untuk JS/JSON literal -->
@{{ variableVueJs }}Percabangan โ
@if($user['role'] === 'admin')
<button>Hapus</button>
@elseif($user['role'] === 'editor')
<button>Edit</button>
@else
<span>Hanya lihat</span>
@endifPerulangan โ
<ul>
@foreach($items as $item)
<li>{{ $item['name'] }}</li>
@endforeach
</ul>Layout: Yield & Section โ
Di app/_layout.fst.php:
<html>
<head><title>@yield('title', 'Default Title')</title></head>
<body>
<nav>...</nav>
<main>@yield('content')</main>
</body>
</html>Di app/dashboard/content.fst.php:
@section('title')
Dashboard
@endsection
<!-- Konten tanpa @section otomatis masuk ke @yield('content') -->
<h1>Selamat Datang</h1>
<p>Ini adalah dashboard.</p>๐งฉ 4. Components โ
Komponen adalah potongan UI reusable di folder components/. Dipanggil on-demand via @component.
Membuat: components/alert.fst.php
<div class="alert alert-{{ $type ?? 'info' }}">
<p>{{ $message }}</p>
</div>Menggunakan:
@component('alert', ['type' => 'danger', 'message' => 'Login gagal!'])Subfolder:
components/admin/sidebar.fst.php โ @component('admin/sidebar')Setiap komponen berjalan dalam Scoped Closure โ variabel di dalamnya tidak bocor ke parent.
โ ๏ธ Peringatan JavaScript pada Komponen: Jangan meletakkan tag
<script>statis (terutama deklarasiconstataulet) di dalam komponen, terlebih jika komponen tersebut dipanggil di dalam perulangan@foreach. Script tersebut akan tercetak berulang kali dan memicu Error: Identifier has already been declared. Sebagai solusinya, gunakan Event Delegation (fst.on) secara terpusat diclient.jsuntuk memberikan interaktivitas pada komponen.
๐ 5. FST-Agent (SPA Engine) โ
Jika agent_js aktif di fullstuck.json, setiap klik link <a> dan submit <form> otomatis dimuat via AJAX tanpa full reload.
๐ก Catatan Ruang Lingkup (Scope): File
client.jsotomatis dibungkus dengan metode IIFE (Immediately Invoked Function Expression) untuk mencegah kebocoran status global (global state). Oleh karena itu, fungsi atau variabel yang Anda deklarasikan di dalamnya tidak bisa dipanggil lewat atribut HTML inline seperti<button onclick="sapa()">. Jika Anda harus membuat fungsi global, daftarkan secara eksplisit ke dalam objek window:window.sapa = () => { ... }.
Event Listener (WAJIB pakai fst.on) โ
Di dalam client.js, jangan pakai document.addEventListener secara langsung. Gunakan fst.on() agar listener otomatis dibersihkan saat navigasi halaman:
// โ
BENAR: Otomatis dibersihkan saat pindah halaman
fst.on('click', '#btn-hapus', (e, el) => {
// ...
});
// โ SALAH: Akan menumpuk di memory setiap buka halaman
document.getElementById('btn-hapus').addEventListener('click', ...);Lifecycle Hook: fst.onMount โ
Callback dijamin berjalan setelah DOM selesai dirender. Jika mengembalikan fungsi, fungsi tersebut dieksekusi saat halaman ditinggalkan (teardown):
fst.onMount(() => {
const chart = new Chart(document.getElementById('myChart'), { ... });
// Teardown: bersihkan saat pindah halaman
return () => chart.destroy();
});Event Bus (Komunikasi Antar-Modul) โ
// Pengirim
fst.emit('cart_updated', { total: 50000 });
// Penerima
fst.on('cart_updated', (detail) => {
console.log('Total:', detail.total);
});Global Listener โ
Listener yang tidak boleh dibersihkan saat navigasi (misal: theme toggle):
fst.on('click', '#btn-theme', (e, el) => { ... }, { global: true });Navigasi Programatik โ
fst.go('/dashboard');
fst.go('/users', { fragment: '#content', history: false, scroll: 'smooth' });HTML Data Attributes โ
| Atribut | Fungsi |
|---|---|
data-fst-fragment="#id" | Target elemen untuk injeksi HTML |
data-fst-normal-load | Bypass SPA, lakukan full page reload |
data-fst-history="false" | Jangan catat di browser history |
data-fst-scroll="false" | Matikan auto scroll-to-top (dukung nilai "smooth") |
data-fst-indicator="class" | CSS class loading kustom |
data-fst-ignore | Script hanya dieksekusi 1 kali |
Script Deduplication โ
Script eksternal (dengan src) yang sudah dimuat tidak akan dimuat ulang saat navigasi SPA. Library seperti Chart.js atau Swiper aman dari eksekusi ganda.
โ ๏ธ Batasan innerHTML & Aturan "Wadah Bisu" (Dumb Wrapper) โ
FST-Agent memotong dan menyuntikkan HTML ke klien menggunakan metode innerHTML. Artinya, hanya isi konten dari elemen target yang akan diganti. Class, inline style, atau atribut yang melekat pada tag pembungkus target itu sendiri TIDAK akan diperbarui saat terjadi navigasi SPA.
Praktik Terbaik: Jangan tempatkan class/styling yang bisa berubah antar-halaman langsung di elemen yang menjadi target X-FST-Fragment (misalnya #app atau #page-content). Jadikan elemen target tersebut sebagai "Wadah Bisu" yang statis.
<!-- โ
BENAR: Wadah statis, class desain ditaruh DI DALAM konten -->
<div id="page-content">
<div class="bg-hitam text-putih">
<h1>Tentang Kami</h1>
</div>
</div>
<!-- โ SALAH: Class 'bg-putih' tidak akan terganti (hilang konteks) saat FST-Agent me-load halaman baru -->
<div id="page-content" class="bg-putih text-hitam">
<h1>Beranda</h1>
</div>๐ฏ Dukungan Selector X-FST-Fragment โ
FST-Agent menggunakan konversi parser HTML kustom di backend, sehingga hanya mendukung CSS selector standar:
โ DIDUKUNG:
- ID:
#app(Paling cepat & direkomendasikan) - Class:
.container - Tag:
main,body - Atribut:
[data-active],[data-role="admin"](Hanya nilai alfanumerik, spasi,/,-,.) - Hierarki/Kombinasi:
#app .container,#app > .sidebar,#app, .cart
โ TIDAK DIDUKUNG (Otomatis Diblokir):
- Pseudo-classes/elements:
:hover,:nth-child(),::before - Sibling:
~,+ - Regex atribut:
[attr^=val],[attr$=val],[attr*=val]
๐ ๏ธ 6. Action & Headless API โ
File action.php menangani logika mutasi data. Fokus ke PHP murni:
<?php
// app/users/action.php
$method = fst_method();
if ($method === 'POST') {
$val = fst_validate(fst_request(), [
'nama' => 'required|min:3',
'email' => 'required|email'
]);
if (!$val['valid']) {
fst_json(['errors' => $val['errors']], 422);
}
$id = fst_db_insert('users', $val['data']);
fst_redirect('/admin/users');
}
if ($method === 'DELETE') {
$id = fst_input('id');
fst_db_delete('users', ['id' => $id]);
fst_json(['success' => true]);
}Method Spoofing (Form HTML) โ
Karena elemen <form> HTML standar hanya mendukung metode GET dan POST, Anda dapat melakukan spoofing untuk metode lain (seperti PUT atau DELETE) dengan menyisipkan input tersembunyi bernama _method:
<form method="POST" action="/admin/users">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="id" value="5">
<button type="submit">Hapus</button>
</form>Fungsi fst_method() di sisi server akan otomatis membaca nilai _method ini sebagai metode request aktual.
Error Handling โ
fst_abort(404, "User tidak ditemukan");- Request ke
/api/*atau dengan headerAccept: application/jsonโ response JSON otomatis. - Request biasa โ framework mencari file
404.fst.phpdari folder error naik keapp/.
๐ 7. Keamanan & Session โ
Session Fixation Protection โ
Wajib dipanggil setelah login berhasil:
fst_session_regenerate(true);Keamanan Form (Tanpa CSRF Token) โ
v0.4 tidak menggunakan CSRF token. Perlindungan form mengandalkan:
- Cookie
SameSite=Lax(otomatis diset framework) - Header
X-FST-Request(disuntikkan FST-Agent)
File Upload (Secure by Default) โ
$result = fst_upload('foto', 'uploads/avatar', [
'max_size' => 1024, // KB
'allowed_types' => ['jpg','png','webp'], // Whitelist
]);
if ($result['success']) {
echo $result['path']; // "uploads/avatar/foto-abc123.jpg"
}Ekstensi berbahaya (.php, .exe, dll) di-block secara hardcoded dan tidak bisa di-override.
XSS Prevention โ
- Backend: Gunakan
{{ $var }}di template (auto-escape) ataue($var)di PHP murni. - Frontend: Gunakan
fst.e(string)sebelum menyuntikkan keinnerHTML.
๐๏ธ 8. Database โ
Fungsi fst_db_* adalah wrapper tipis di atas PDO. Bukan ORM. Mendukung SQLite, MySQL, dan PostgreSQL.
CRUD Dasar โ
// Select
$users = fst_db_select('users', ['status' => 'active'], [
'order_by' => 'id DESC',
'limit' => 10
]);
// Select dengan operator
$products = fst_db_select('products', [
'price >' => 50000,
'name LIKE' => '%baju%'
]);
// Satu baris
$user = fst_db_row('users', ['id' => 1]);
// Cek keberadaan
$exists = fst_db_exists('users', ['email' => 'budi@mail.com']);
// Insert (return last_id)
$id = fst_db_insert('users', ['nama' => 'Budi', 'email' => 'budi@mail.com']);
// Update (return affected_rows)
fst_db_update('users', ['status' => 'inactive'], ['id' => 5]);
// Delete (return affected_rows)
fst_db_delete('users', ['id' => 5]);Raw Query โ
Untuk query kompleks (JOIN, COUNT, GROUP BY), gunakan fst_db():
// Mode: 'ALL' (banyak baris), 'ROW' (1 baris), 'SCALAR' (1 nilai), 'EXEC' (eksekusi)
$posts = fst_db('ALL',
'SELECT p.*, u.nama FROM posts p JOIN users u ON p.user_id = u.id WHERE p.status = ?',
['published']
);
$total = fst_db('SCALAR', 'SELECT COUNT(*) FROM users WHERE status = ?', ['active']);Transactions โ
try {
fst_db_begin();
fst_db_insert('orders', ['total' => 100000]);
fst_db_update('stock', ['qty' => 0], ['id' => 1]);
fst_db_commit();
} catch (Exception $e) {
fst_db_rollback();
}Multi-Connection โ
Semua fungsi database mendukung parameter $connection atau opsi ['connection' => 'nama']:
$remote = fst_db('ALL', 'SELECT * FROM logs', [], 'mysql_remote');
$users = fst_db_select('users', [], ['connection' => 'mysql_remote']);โ๏ธ 9. Konfigurasi fullstuck.json โ
{
// Wajib. true = produksi (cache terkunci, error tersembunyi).
"production": false,
// Konfigurasi routing.
"routing": {
"base_path": "/", // Untuk deploy di subfolder
"public_folders": ["assets", "uploads"] // Folder yang boleh diakses publik
},
// Auto-load file PHP sebelum routing. Default: ["globals"].
"require": ["globals"],
// Aktifkan SPA engine.
"agent_js": true,
// Opsi fragment SPA.
"fragment": {
"history_cache": false // Cache navigasi di sisi klien
},
// Database (opsional). Mendukung multi-koneksi.
"database": {
"default": "main",
"connections": {
"main": {
"driver": "sqlite",
"database_path": "database.sqlite"
},
"mysql_remote": {
"driver": "mysql",
"host": "${DB_HOST}",
"port": 3306,
"dbname": "${DB_NAME}",
"username": "${DB_USER}",
"password": "${DB_PASS}"
}
}
}
}Semua nilai string mendukung interpolasi variabel lingkungan: ${NAMA_ENV}.
๐งพ 10. Logging & Error Handling โ
Logging โ
fst_log('INFO', 'Pembayaran berhasil.', ['invoice' => 'INV-001']);
fst_log('ERROR', 'Koneksi gagal.', ['endpoint' => $url]);Log ditulis ke file .fst.log dalam format JSON per baris.
Error Handler Kustom โ
Daftarkan callback untuk mengirim notifikasi (Telegram, Slack, dll) saat error terjadi:
// Letakkan di globals/error_handler.php
fst_error_handler(function(Throwable $e) {
$msg = $e->getMessage();
// send_telegram_alert("๐จ ERROR: $msg");
});Mode Production vs Development โ
| Aspek | Development | Production |
|---|---|---|
| Error display | Stack trace visual di browser | Pesan generik, detail di .fst.log |
| Cache view | Re-compile jika file berubah | Langsung dimuat tanpa cek |
| Cache router | Re-scan jika folder app/ berubah | Terkunci, tidak pernah re-scan |
๐ 11. API Reference โ
Core & Konfigurasi โ
| Fungsi | Keterangan |
|---|---|
fst_app($key, $value) | Get/set state runtime aplikasi |
fst_config($key, $default) | Baca nilai dari fullstuck.json (dot notation: database.default) |
fst_is_dev() | Cek apakah mode development |
fst_log($level, $message, $context) | Tulis log ke .fst.log |
fst_error_handler(callable) | Daftarkan callback error kustom |
HTTP Request & Response โ
| Fungsi | Keterangan |
|---|---|
fst_uri() | URI request saat ini (tanpa query string) |
fst_input($key, $default) | Ambil data dari GET/POST/JSON body |
fst_request() | Seluruh data request sebagai array |
fst_method() | Metode HTTP (menangani spoofing dari _method) |
fst_file($key) | Data file upload dari $_FILES |
fst_redirect($url, $code, $allow_external) | Redirect aman (cegah open redirect) |
fst_json($data, $status) | Response JSON lalu die() |
fst_text($string, $status) | Response plain text lalu die() |
fst_status_code($code) | Set HTTP status code |
fst_abort($code, $message) | Hentikan eksekusi + tampilkan error page/JSON |
Session โ
| Fungsi | Keterangan |
|---|---|
fst_session_set($key, $value) | Simpan ke session |
fst_session_get($key, $default) | Baca dari session |
fst_session_forget($key) | Hapus dari session |
fst_session_regenerate($delete_old) | Regenerasi ID session (wajib setelah login) |
Database โ
| Fungsi | Keterangan |
|---|---|
fst_db($mode, $sql, $params, $conn) | Raw query. Mode: ALL, ROW, SCALAR, EXEC |
fst_db_select($table, $cond, $opts) | Select banyak baris |
fst_db_row($table, $cond, $opts) | Select satu baris |
fst_db_exists($table, $cond, $opts) | Cek keberadaan data (boolean) |
fst_db_insert($table, $data, $opts) | Insert. Return: last_id |
fst_db_update($table, $data, $cond, $opts) | Update. Return: affected_rows |
fst_db_delete($table, $cond, $opts) | Delete. Return: affected_rows |
fst_db_begin/commit/rollback($conn) | Transaction control |
Keamanan & Utilitas โ
| Fungsi | Keterangan |
|---|---|
e($str) / fst_escape($str) | Escape string untuk mencegah XSS |
fst_upload($key, $folder, $opts) | Upload file secure (whitelist + blacklist) |
fst_validate($data, $rules) | Validasi input (required, email, min, max, numeric, in, min_value, max_value) |
fst_dump(...$vars) | Debug output (hanya di mode dev) |
fst_dd(...$vars) | Debug output lalu die() |
Templating & View โ
| Fungsi | Keterangan |
|---|---|
fst_view($path, $data) | Render file PHP biasa dengan data |
fst_partial($path, $data) | Alias semantik untuk fst_view |
fst_view_share($key, $value) | Bagikan variabel ke semua view |
Fragment (Backend) โ
| Fungsi | Keterangan |
|---|---|
fst_is_fragment_request() | Apakah request dari FST-Agent (SPA) |
fst_fragment_target() | CSS selector target fragment |
FST-Agent API (JavaScript) โ
| Method | Penjelasan & Parameter Ekstra |
|---|---|
fst.on(event, sel, cb, opts) | Event delegation aman. opts menerima konfigurasi seperti { global: true } agar event tidak dihapus otomatis saat pindah halaman. |
fst.onMount(cb) | Hook lifecycle yang berjalan saat halaman/fragment selesai dirender. Return sebuah function di dalamnya untuk melakukan cleanup (mirip useEffect). |
fst.emit(event, detail) | Mengirim custom event ke window (otomatis menggunakan prefix fst: jika perlu). Parameter detail bisa diisi data object. |
fst.go(url, options) | Navigasi programatik SPA. Opsi: โข target: string CSS selector (default: 'body')โข history: boolean (default: true)โข scroll: boolean | 'smooth' | 'instant'โข indicator: string (class loading kustom) |
fst.set(pattern, cb) | Mendaftarkan client-side route. Callback menerima (match, triggerElement). |
fst.group(prefix, cb) | Mengelompokkan pendaftaran route client dengan awalan path yang sama. |
fst.setInterceptor(cb) | Menyisipkan logika sebelum request fetch(). Callback menerima (url, fetchOptions) dan bisa me-return objek fetchOptions baru (berguna untuk menyisipkan header token/auth). |
fst.setBefore(cb) | Hook navigasi. Menerima (url). Jika fungsi ini me-return false, navigasi dibatalkan. |
fst.setAfter(cb) | Hook navigasi. Menerima (url, triggerElement). Dipanggil setelah route SPA berhasil diproses. |
fst.e(str) / fst.escape(str) | Utility untuk HTML escape string (mencegah XSS) sebelum dimasukkan ke dalam DOM (e.g. innerHTML). |
๐ถ๏ธ 12. Cookbook & Tips Lanjutan โ
Global Middleware (CORS / Auth) โ
Buat file di globals/ โ otomatis dimuat sebelum routing:
// globals/cors.php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-FST-Request");
if (fst_method() === 'OPTIONS') {
fst_status_code(200);
die();
}
function authenticate_api() {
$auth = getallheaders()['Authorization'] ?? '';
if (!str_starts_with($auth, 'Bearer ')) {
fst_json(['error' => 'Unauthorized'], 401);
}
return verify_jwt(substr($auth, 7));
}Request Interceptor (Frontend) โ
Sisipkan token atau header kustom ke setiap request SPA:
fst.setInterceptor((url, fetchOptions) => {
const token = localStorage.getItem('api_token');
if (token) fetchOptions.headers['Authorization'] = `Bearer ${token}`;
return fetchOptions;
});Database Migration โ
Buat file skrip terpisah, jalankan via terminal:
// tools/migrate.php
<?php require 'fullstuck.php';
fst_db('EXEC', "CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT NOT NULL,
email TEXT UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)");
echo "Migration done.\n";php tools/migrate.phpCustom Error Page โ
Buat file app/404.fst.php atau app/500.fst.php. Framework otomatis menggunakannya saat terjadi error. File error juga bisa diletakkan per-section (misal app/admin/404.fst.php).
Folder globals/ โ Apa yang Boleh & Tidak โ
| โ Boleh | โ Dilarang |
|---|---|
| Helper functions | File view/template |
| Konstanta | Logika routing |
| Koneksi database | File HTML |
| Middleware global |
Konvensi Arsitektur: HTML-over-the-wire (HOTW) โ
FullStuck menggunakan pola HTML-over-the-wire (Single Source of Truth). Dilarang merakit HTML (seperti Card atau Modal) dengan string literal di JavaScript karena rentan XSS dan menduplikasi kode.
Untuk memuat komponen dinamis, ambil render HTML langsung dari server via fst.go():
// Ambil komponen dari server, sisipkan ke #wadah
fst.go('/api/komponen/card', { target: '#wadah', history: false });Menghindari 404 pada SPA (Isomorphic Routing) โ
Rute SPA yang didaftarkan via fst.set('/path', ...) di client.js akan menghasilkan 404 Not Found jika URL diakses langsung (atau hard reload). Hal ini terjadi karena direktori tersebut tidak ada di sisi backend (PHP).
Solusi: URL publik wajib memiliki direktori pendamping di server. Contoh rute /dashboard/view/123:
1. Pendekatan CSR: Backend hanya memuat tata letak (layout) kosong. Data diambil terpisah oleh client.js.
<!-- app/dashboard/view/[id]/content.fst.php -->
@layout('dashboard')
<div id="detail-container">
<div class="skeleton-loader">Memuat...</div>
</div>2. Pendekatan SSR (Direkomendasikan): Backend merender tampilan utuh beserta data. Halaman langsung tampil seketika (first paint), memaksimalkan performa Hybrid FullStuck.
<!-- app/dashboard/view/[id]/content.fst.php -->
<?php
$task = fst_db_row('tasks', ['id' => fst_input('id')]);
if (!$task) fst_abort(404, 'Tugas tidak ditemukan');
?>
@layout('dashboard')
<div id="detail-container">
<h1>{{ $task['title'] }}</h1>
<p>{{ $task['description'] }}</p>
</div>โ๏ธ 13. Integrasi Eksternal: Monolith SPA (React/Vue/Svelte) โ
Jika Anda menggunakan framework JavaScript eksternal (React/Vue/Svelte), FullStuck bisa berperan sebagai REST API murni sekaligus menyajikan file statis SPA (Monolith), membebaskan Anda dari kendala CORS.
1. Penyesuaian fullstuck.json: Matikan FST-Agent dan ekspos direktori build frontend Anda (misal: public).
{
"agent_js": false,
"routing": { "public_folders": ["assets", "public"] }
}2. Struktur Direktori: Buat endpoint API Anda di dalam app/api/ (menggunakan action.php), lalu letakkan hasil build statis React/Vue/Svelte di public/.
3. Trik Catch-All Routing: Untuk mencegah halaman 404 saat pengguna melakukan hard reload pada rute React/Vue/Svelte, manfaatkan error handler bawaan melalui file app/404.fst.php:
<?php
// app/404.fst.php
// Berikan 404 asli jika request ini ditujukan ke API
if (str_starts_with(fst_uri(), '/api/')) fst_json(['error' => 'Not Found'], 404);
// Selain itu, beri status 200 dan serahkan kepada router React/Vue/Svelte
fst_status_code(200);
echo file_get_contents(FST_ROOT_DIR . '/public/index.html');
die();