Plugin Assets
Plugin yang membawa Vue component harus menyalinnya ke aplikasi sebelum component tersebut dapat dirender. publishes() mendeklarasikan file apa yang harus disalin dan ke mana tujuannya, sedangkan php artisan panel:publish melakukan proses penyalinan. Gunakan halaman ini ketika plugin membawa file frontend dalam bentuk apa pun — component, stub, stylesheet, atau file lain yang harus masuk ke aplikasi.
Contoh minimal yang berfungsi
<?php
declare(strict_types=1);
namespace Acme\Reporting;
use PandaPanel\Core\Panel;
use PandaPanel\Plugins\Plugin;
final class ReportingPlugin extends Plugin
{
public function register(Panel $panel): void
{
$panel->widgets([Widgets\RevenueChart::class]);
}
/**
* @return array<string, string> absolute source => absolute destination
*/
public function publishes(): array
{
return [
__DIR__.'/../resources/js' => resource_path('js/pages/Panels/AcmeReporting'),
];
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
php artisan panel:publish acme-reporting
npm run build2
[acme-reporting] …/resources/js/pages/Panels/AcmeReporting/Widgets/RevenueChart.vue published
INFO Published 1 file(s).2
3
Mengapa file harus disalin
Setiap component registry pada framework ini menggunakan import.meta.glob terhadap tree milik aplikasi:
import.meta.glob('../../pages/Panels/**/Widgets/*.vue');Ini sengaja menjadi build-time allowlist. Nama component yang berasal dari tempat lain berarti nama yang tidak pernah dilihat build system. Component yang masih berada di vendor/acme/panda-reporting/resources/js/ tidak cocok dengan glob tersebut dan tidak dapat di-resolve, apa pun nama yang diberikan class PHP.
Karena itu plugin mem-publish component ke tree milik aplikasi. Sejak saat tersebut file menjadi bagian aplikasi: berada di repository aplikasi, masuk ke proses build aplikasi, dan dapat diedit. Ini bukan workaround, melainkan desain yang disengaja. Component yang source-nya tidak dapat dilihat developer adalah component yang sulit di-debug.
Lihat Component Registries untuk aturan penamaan lengkap.
publishes(): array
/**
* @return array<string, string> absolute source path => absolute destination path
*/
public function publishes(): array;2
3
4
Key adalah source dan value adalah destination; keduanya menggunakan absolute path. Bentuknya method, bukan configuration array statis, karena hanya plugin yang benar-benar mengetahui lokasi file miliknya sendiri — __DIR__ adalah informasi package-specific yang tidak dapat diketahui Panel.
Source maupun destination dapat berupa file atau directory:
public function publishes(): array
{
return [
// A directory, copied recursively, relative paths preserved.
__DIR__.'/../resources/js' => resource_path('js/pages/Panels/AcmeReporting'),
// A single file.
__DIR__.'/../resources/css/reporting.css' => resource_path('css/reporting.css'),
// Anywhere in the application, not only under resources/.
__DIR__.'/../stubs/report.stub' => base_path('stubs/report.stub'),
];
}2
3
4
5
6
7
8
9
10
11
12
13
Default pada PandaPanel\Plugins\Plugin adalah [], sesuai untuk sebagian besar plugin. Plugin yang mengimplementasikan PandaPanel\Contracts\PanelPlugin secara langsung harus menulis method kosong tersebut sendiri.
publishes() hanya dipanggil oleh panel:publish, tidak pernah saat request berjalan. Karena itu method ini aman menggunakan resource_path(), base_path(), dan helper path sejenis.
Lokasi file yang benar
Nama component adalah path di bawah pages/ tanpa extension, sedangkan registry mencocokkan Panels/**/{Kind}/*.vue. Artinya destination menentukan nama component yang harus dideklarasikan:
| Destination | Nama yang dideklarasikan di PHP |
|---|---|
resources/js/pages/Panels/AcmeReporting/Widgets/RevenueChart.vue | Panels/AcmeReporting/Widgets/RevenueChart |
resources/js/pages/Panels/AcmeReporting/Columns/Sparkline.vue | Panels/AcmeReporting/Columns/Sparkline |
resources/js/pages/Panels/AcmeReporting/Hooks/Banner.vue | Panels/AcmeReporting/Hooks/Banner |
| Kind directory | Registry | Dideklarasikan oleh |
|---|---|---|
Columns/ | tables | CustomColumn::component() |
Fields/, Schemas/, Entries/, Modals/ | forms | CustomField::component(), CustomComponent::make(), CustomEntry::component(), Modal::content() |
Widgets/ | widgets | CustomWidget::$component |
Hooks/ | hooks | Panel::renderHook() |
Shell/ | shell | Panel::sidebarComponent(), Panel::topbarComponent() |
Berikan plugin top-level directory miliknya sendiri di bawah Panels/ daripada mem-publish ke directory milik suatu Panel. Panels/AcmeReporting/ jelas milik plugin, mudah dikenali saat diff, dan tidak bertabrakan dengan Panel bernama acme-reporting yang mungkin dibuat aplikasi di kemudian hari. ** pada glob berarti kedalaman directory tidak penting; yang penting adalah kind directory tepat di atas file.
Full Inertia page milik class Page dari plugin di-resolve oleh import.meta.glob('./pages/**/*.vue') milik aplikasi, bukan registry Panel. Karena itu pages/Panels/AcmeReporting/Pages/Report.vue bekerja menggunakan aturan yang sama.
panel:publish
php artisan panel:publish [plugin] [--force]| Argument / option | Type | Default | Efek |
|---|---|---|---|
plugin | optional argument | semua plugin | Publish hanya plugin dengan ID tersebut, bukan nama class |
--force | flag | off | Timpa destination file yang sudah ada |
php artisan panel:publish # every plugin on every panel
php artisan panel:publish acme-reporting # one plugin
php artisan panel:publish acme-reporting --force2
3
Urutan pekerjaan command:
- Mengunjungi setiap Panel yang terdaftar dan setiap plugin pada Panel tersebut.
- Jika argument diberikan, melewati plugin yang
id()-nya tidak cocok. - Memanggil
publishes()melalui contractPanelPlugin, sehingga plugin yang dikirim sebagai package sendiri diperlakukan sama dengan plugin aplikasi. - Untuk setiap source: memberi warning lalu lanjut jika source tidak ada; menyalin recursive bila directory; menyalin sekali bila file.
- Membuat destination directory yang belum tersedia.
- Mencetak satu baris per file dan jumlah file di akhir.
Tidak ada file yang ditimpa tanpa --force
[acme-reporting] …/pages/Panels/AcmeReporting/Widgets/RevenueChart.vue exists, skippedPublished file yang sudah diedit aplikasi adalah pekerjaan developer. Menimpanya diam-diam berarti menghapus pekerjaan tersebut. Karena itu file dilewati dan statusnya dicetak, sehingga upgrade plugin yang membawa perubahan component terlihat jelas pada output.
Ketika semua file sudah ada, command tidak menulis apa pun:
INFO Nothing to publish.Pesan tersebut berarti "tidak ada file yang ditulis", bukan "plugin tidak memiliki file untuk dipublish". Perbedaannya terlihat dari baris exists, skipped sebelumnya.
Source yang hilang adalah warning, bukan failure
[acme-reporting] /path/that/does/not/exist does not exist.Command melanjutkan ke entry berikutnya dan tetap exit sukses. Path salah pada publishes() adalah bug plugin, tetapi bug satu plugin tidak seharusnya menghentikan proses publish plugin lainnya.
Build ulang setelah publish
npm run build # or npm run devimport.meta.glob dievaluasi saat build. Component yang baru dipublish belum ada di bundle sampai build berikutnya. Dev server dapat melihat file baru, tetapi production bundle yang dibuat sebelum file tersedia tidak mengandung component tersebut; renderer akan memakai fallback dan menampilkan console warning khusus development.
Vite entrypoint
Plugin yang membawa stylesheet atau script sendiri dapat menambahkannya ke entrypoint Panel:
public function register(Panel $panel): void
{
$panel->assets('resources/css/reporting.css');
}2
3
4
Panel::assets(string ...$entrypoints) bersifat accumulating, sehingga plugin menambahkan asset ke asset Panel, bukan menggantinya. Path yang sama juga harus muncul pada input di vite.config.ts; jika tidak, Vite tidak memiliki asset untuk dilayani dan page gagal dengan manifest error. Karena itulah setup ini sengaja membutuhkan dua edit. Edit kedua adalah tanggung jawab aplikasi dan harus dijelaskan pada installation instruction plugin. Lihat Panel Assets.
Hal yang perlu diperhatikan
- Plugin yang dipasang pada dua Panel dikunjungi dua kali.
panel:publishmelakukan loop Panel lalu plugin. Pass pertama menulis file, pass kedua biasanya menampilkanexists, skipped. Ini tidak berbahaya dan normal. panel:assetstidak melacak file plugin. Command tersebut dan.panel-assets.jsonhanya mencakup frontend bawaan framework. Published file plugin tidak dibandingkan terhadap versi upstream, sehingga plugin yang di-upgrade tidak dapat otomatis memberi tahu bahwa component berubah. Upgrade dilakukan dengan review diff lalupanel:publish --forcejika memang ingin menimpa.- Argument menggunakan ID, bukan class atau package. Plugin yang override
id()dipublish menggunakan ID hasil override. - Plugin harus diregistrasikan pada Panel agar dapat dipublish. Command membaca Panel, bukan Composer. Package yang terpasang tetapi tidak muncul di
plugins([...])mana pun tidak mem-publish apa pun. - Hanya child langsung di dalam kind directory yang terdaftar.
Widgets/Charts/Revenue.vuememang disalin, tetapi tidak cocok dengan registry karena glob berakhir di*.vuetepat di dalamWidgets/. - File upstream yang dihapus tidak dihapus dari aplikasi. Publishing hanya menyalin. Component yang sudah dihapus plugin tetap berada di aplikasi sampai developer menghapusnya sendiri.