Monolit modern yang memadukan Laravel dan Inertia.js sering mengalami masalah silent drift: backend mengubah field DTO atau model, namun frontend TypeScript tetap menggunakan definisi tipe usang tanpa memicu compile error. Masalah ini baru terlihat saat aplikasi berjalan di production dan antarmuka menerima payload yang tidak sesuai.
Solusi yang teruji adalah memperlakukan backend PHP sebagai single source of truth, menghasilkan definisi tipe TypeScript secara otomatis (typegen), mengaitkannya ke reactive watcher pada Vite, serta memvalidasi sinkronisasi file hasil generasi pada CI pipeline.
1. Konfigurasi Backend: spatie/laravel-typescript-transformer
Instal paket transformer resmi untuk Laravel:
composer require spatie/laravel-typescript-transformerPublikasikan konfigurasi melalui artisan:
php artisan vendor:publish --tag=typescript-transformer-configEdit config/typescript-transformer.php. Tentukan direktori sumber DTO dan jalur output definisi TypeScript frontend:
return [
'searching_paths' => [
app_path('Data'),
app_path('DTO'),
],
'transformers' => [
Spatie\TypeScriptTransformer\Transformers\DtoTransformer::class,
],
'output_file' => resource_path('js/types/generated.d.ts'),
'writer' => Spatie\TypeScriptTransformer\Writers\TypeDefinitionWriter::class,
];Definisi DTO PHP
Gunakan atribut #[TypeScript] pada class representasi data untuk mengekspor definisi skema:
<?php
namespace App\Data;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;
#[TypeScript]
class UserData
{
public function __construct(
public int $id,
public string $name,
public string $email,
public ?string $avatarUrl,
/** @var array<string> */
public array $permissions,
) {}
}Jalankan sinkronisasi manual pertama:
php artisan typescript:transformHasil transformasi pada resources/js/types/generated.d.ts:
declare namespace App.Data {
export type UserData = {
id: number;
name: string;
email: string;
avatarUrl: string | null;
permissions: Array<string>;
};
}2. Otomasi DX: Vite Hot Watcher
Menjalankan perintah Artisan secara manual setiap kali memperbarui PHP DTO memperlambat alur kerja. Gunakan hook Vite bawaan untuk memantau perubahan file PHP di direktori target tanpa menambah dependensi watcher eksternal.
Tambahkan custom plugin minimal pada vite.config.ts:
import { defineConfig, Plugin } from 'vite';
import laravel from 'laravel-vite-plugin';
import { exec } from 'node:child_process';
function typegenWatcher(): Plugin {
return {
name: 'inertia-typegen-watcher',
handleHotUpdate({ file, server }) {
if (file.includes('/app/Data/') && file.endsWith('.php')) {
exec('php artisan typescript:transform', (error) => {
if (error) {
console.error('Typegen error:', error);
return;
}
server.ws.send({ type: 'full-reload' });
});
}
},
};
}
export default defineConfig({
plugins: [
laravel({
input: 'resources/js/app.ts',
refresh: true,
}),
typegenWatcher(),
],
});3. Pre-commit Verification: Husky & lint-staged
Cegah developer commit kode PHP tanpa meregenerasi file TypeScript. Gunakan lint-staged untuk mengeksekusi typegen jika ada perubahan pada file DTO yang di-stage.
Tambahkan konfigurasi di package.json:
{
"scripts": {
"typegen": "php artisan typescript:transform"
},
"lint-staged": {
"app/Data/**/*.php": [
"php artisan typescript:transform",
"git add resources/js/types/generated.d.ts"
]
}
}Daftarkan hook pre-commit pada .husky/pre-commit:
npx lint-staged4. Deteksi Drift di CI: GitHub Actions Pipeline
Pre-commit hook di lokal dapat dilewati menggunakan flag --no-verify. Pipeline CI menjadi gerbang validasi akhir yang bersifat mutlak.
Prinsip kerjanya: CI menjalankan php artisan typescript:transform, lalu mengecek apakah git working tree mendeteksi perubahan menggunakan git diff --exit-code. Jika ada diff, berarti pengembang mengubah backend tanpa menyertakan regenerasi file generated.d.ts terbaru.
Implementasi workflow pada .github/workflows/ci.yml:
name: CI Checks
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
type-safety:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, json
coverage: none
- name: Install Composer dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install NPM dependencies
run: npm ci
- name: Verify Type Definition Parity
run: |
php artisan typescript:transform
git diff --exit-code resources/js/types/generated.d.ts
- name: Run TypeScript typecheck
run: npx vue-tsc --noEmit5. Konsumsi Type pada Komponen Inertia
Gunakan type yang dihasilkan pada komponen frontend dengan memetakan props halaman secara eksplisit:
<script setup lang="ts">
interface Props {
user: App.Data.UserData;
}
const props = defineProps<Props>();
</script>
<template>
<div class="profile">
<h1>{{ props.user.name }}</h1>
<p>{{ props.user.email }}</p>
</div>
</template>Catatan Teknis dan Trade-offs
- Model Eloquent vs DTO: Hindari mengekspor Eloquent Model secara langsung menggunakan transformer. Model memiliki mutator, accessor, dan serialization logic dinamis yang sulit diurai secara statis. Gunakan DTO flat atau Spatie Laravel-Data sebagai kontrak data.
- Overhead Performa Watcher: Eksekusi
php artisanvia Nodeexecmenimbulkan overhead proses spawning baru (~100-300ms). Jangan pasang watcher pada path luas sepertiapp/**/*.php, batasi spesifik pada namespaceapp/Data. - CI Failures: Pastikan composer autoloader diperbarui sebelum step
typescript:transformdijalankan di CI untuk menghindari class not found saat scanning file PHP baru.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!