Skip to content

Plugins ​

Un plugin es la manera que tiene un paquete de Composer de añadir páginas, scripts, hojas de estilo, textos y rutas a un panel, sin que la aplicación tenga que copiar nada. La aplicación decide en qué paneles lo activa. Disponible desde la 1.2.

php
Panel::make('admin')
    ->plugins([BillingPlugin::make()])

Un mismo plugin no se puede registrar dos veces en el mismo panel: Arrel lanza una excepción.

Escribir un plugin ​

Un plugin extiende Arrel\Plugins\Plugin. Solo hace falta id(); el resto de métodos devuelven una lista vacía si no los sobrescribes.

php
use Arrel\Pages\CustomPage;
use Arrel\Plugins\Plugin;

final class BillingPlugin extends Plugin
{
    public static function make(): self
    {
        return new self;
    }

    public function id(): string
    {
        return 'billing';
    }

    public function customPages(): array
    {
        return [
            CustomPage::make('billing', __('billing::billing.navigation_label'))
                ->icon('credit-card')
                ->visible(fn (): bool => Gate::allows('billing.manage')),
        ];
    }

    public function scripts(): array
    {
        return [asset('vendor/billing/billing.js')];
    }

    public function stylesheets(): array
    {
        return [asset('vendor/billing/billing.css')];
    }

    public function translations(): array
    {
        return ['title' => __('billing::billing.title')];
    }
}
MétodoQué aporta
id()El identificador del plugin. Hace de prefijo de sus textos y de sus rutas.
customPages()Páginas personalizadas, con su entrada de navegación.
scripts()Scripts que el panel carga después de los suyos.
stylesheets()Hojas de estilo que el panel carga después de las suyas.
translations()Textos para el frontend.

El panel lee estos métodos cada vez que sirve una página, no cuando se registra el plugin. Por eso el orden de plugins() y customPages() en la definición del panel no importa, y __() ya devuelve el texto en el idioma del usuario.

La página ​

El script del plugin registra su página igual que lo haría la aplicación, normalmente con registerMountablePage y su propia instancia de Vue. Si el paquete usa el kit de componentes de Arrel, lo incorpora a su propio build.

Si construyes el script con Vite en modo librería, define process.env.NODE_ENV. Vite no lo sustituye en ese modo y Vue lo lee: sin el define, el script falla al cargarse y la página nunca se registra.

js
// vite.config.js
export default defineConfig({
    define: {
        'process.env.NODE_ENV': JSON.stringify('production'),
    },
    build: {
        lib: { entry: 'resources/js/main.ts', formats: ['es'], fileName: () => 'billing.js' },
    },
})

Textos ​

Los textos de translations() llegan a window.Arrel.translations con el prefijo del identificador del plugin. Así, title de un plugin billing es billing.title, y nunca sustituye un texto del panel.

ts
const title = window.Arrel.translations?.['billing.title']

Los textos de PHP (mensajes de validación, la navegación) los carga el paquete con loadTranslationsFrom(), como cualquier paquete de Laravel. Arrel no interviene.

Rutas ​

ArrelRoutes::plugin() registra las rutas del paquete con la misma pila de middleware que la API autenticada de Arrel, incluidos arrel.middleware y arrel.authenticated_middleware. Si tu aplicación identifica el tenant en ese middleware, las rutas del plugin ya lo tienen.

php
use Arrel\Http\ArrelRoutes;

ArrelRoutes::plugin('billing', function (): void {
    Route::get('/', [BillingController::class, 'context'])->name('context');
    Route::post('checkout', [BillingController::class, 'checkout'])->name('checkout');
});

Las rutas quedan en /arrel-plugins/{id} y se llaman arrel.plugins.{id}.*: el ejemplo sirve GET /arrel-plugins/billing con el nombre arrel.plugins.billing.context.

El prefijo está fuera de /arrel/api y del prefijo del panel a propósito. Las rutas de Arrel que cuelgan de ahí capturan cualquier ruta, y el orden en que Laravel arranca los paquetes no está garantizado: una ruta que un paquete registrara después quedaría tapada.

Como en cualquier endpoint propio, autoriza cada petición. Que visible() oculte la entrada de navegación no impide que alguien llame a la ruta a mano.