Zum Hauptinhalt springen
Schneespur
Dokumentation durchblättern

Module entwickeln

Assets & Frontend

Wie ein Modul eigenes CSS und JavaScript ausliefert: Manifest, Symlink, Blade-Direktive und der Weg über Tailwind ohne eigenes CSS.

Ein Modul kann eigenes CSS und JavaScript mitbringen. Schneespur lädt diese Dateien während des Boot-Prozesses automatisch, ohne dass Sie Core-Dateien anfassen müssen. Dieser Abschnitt zeigt, wie der Weg von der gebauten Datei bis zum <link>- oder <script>-Tag im fertigen HTML verläuft — und warum es für die meisten Module sogar ganz ohne eigenes CSS geht.

So werden Modul-Assets geladen

Der Ablauf besteht aus fünf Schritten und läuft beim Booten des Moduls von selbst ab:

  1. Das Modul legt gebautes CSS/JS im Verzeichnis dist/ ab.
  2. Eine dist/manifest.json listet diese Dateien auf.
  3. Die ModuleAssetRegistry liest das Manifest beim Booten ein.
  4. Es entsteht ein Symlink: public/modules/{slug}/modules/{slug}/dist/.
  5. Die Blade-Direktive @moduleAssets gibt die <link>- und <script>-Tags aus.

Der Grund für diesen Umweg über Symlink und Manifest: Die Asset-Dateien bleiben im Modul-Verzeichnis liegen, werden aber unter dem öffentlichen public/-Pfad erreichbar. So bleibt jedes Modul in sich geschlossen, und der Core muss nicht wissen, welche Dateien ein Modul mitbringt — er liest sie aus dem Manifest.

Verzeichnisstruktur

modules/my-module/
  dist/
    manifest.json
    my-module-abc123.css
    my-module-abc123.js

manifest.json

Das Manifest ist eine schlichte Liste der auszuliefernden Dateien:

[
    {"type": "css", "file": "my-module-abc123.css"},
    {"type": "js", "file": "my-module-abc123.js"}
]

Jeder Eintrag braucht genau zwei Felder:

  • type: entweder "css" oder "js"
  • file: Dateiname innerhalb des dist/-Verzeichnisses

Der Hash-Anhang im Dateinamen (z. B. abc123) ist empfohlen, weil er für Cache-Busting sorgt: Ändert sich der Inhalt, ändert sich der Dateiname, und Browser laden die neue Datei statt einer veralteten aus dem Cache.

Asset-URLs

Ausgeliefert werden die Dateien unter /modules/{slug}/{file}:

/modules/my-module/my-module-abc123.css
/modules/my-module/my-module-abc123.js

Blade-Direktiven

@moduleAssets

Diese Direktive gibt alle registrierten Modul-CSS- und -JS-Dateien aus. Sie ist bereits in den Core-Layouts enthalten — Sie müssen sie also nicht selbst einbauen.

Erzeugt wird daraus:

<link rel="stylesheet" href="/modules/my-module/my-module-abc123.css">
<script src="/modules/my-module/my-module-abc123.js" defer></script>

Helfer module_asset()

Wenn Sie eine einzelne Asset-Datei innerhalb einer View ansprechen wollen, liefert module_asset() den passenden Pfad:

module_asset('my-module', 'my-module-abc123.css');
// Returns: '/modules/my-module/my-module-abc123.css'

@lifecycleFields

Hinzugekommen in 1.1.6. Diese Direktive rendert die Formularfelder, die Module in einen der vier Fahrer-Lebenszyklus-Momente einhängen — etwa beim Abschluss eines Einsatzes. Sie gibt aus, was Module zuvor über die LifecycleFieldRegistry für den jeweiligen Lebenszyklus-Punkt angemeldet haben:

@lifecycleFields(\App\Enums\LifecyclePoint::JobEnd)   {{-- render driver lifecycle fields --}}

Der Grund für diesen Erweiterungspunkt: Die Fahrer-Formulare des Cores sollen sich von Modulen ergänzen lassen, ohne dass Sie eine Core-View anfassen. Ein Modul registriert seine Felder an der Registry, und der Core rendert sie an der passenden Stelle. Wie bei @moduleAssets ist die Direktive bereits in den Core-Formularen platziert — ein Modul registriert Felder, es ruft die Direktive nicht selbst auf.

Das Registrieren solcher Felder beschreibt die LifecycleFieldRegistry, das zugehörige Feld-Interface die Interfaces-Referenz. Dieser Abschnitt deckt nur den Frontend-/Render-Teil ab.

Der Erweiterungspunkt selbst existiert ab dieser Version. Die Module, die ihn befüllen — geplant sind unter anderem ein Inventar- und ein Grünpflege-Modul —, sind noch nicht verfügbar.

Assets bauen

Die Kern-Anwendung nutzt Tailwind CSS. Für die eigenen Assets haben Sie drei Wege — vom einfachsten zum aufwändigsten.

Variante A: Reines CSS (am einfachsten)

Schreiben Sie CSS in eine .css-Datei und legen Sie sie in dist/ ab. Mehr ist nicht nötig.

Variante B: Build mit Vite/Webpack

Hat Ihr Modul aufwändigere Frontend-Anforderungen, bauen Sie die Assets mit einem Bundler. Das Manifest wird dann vom Build-Prozess erzeugt:

modules/my-module/
  resources/
    css/
      module.css
    js/
      module.js
  vite.config.js   (or webpack.config.js)
  package.json
  dist/
    manifest.json  (auto-generated by build)
    my-module-abc123.css
    my-module-abc123.js

Variante C: Tailwind-Klassen

Da der Core Tailwind bereits lädt, können die Blade-Views eines Moduls Tailwind-Klassen direkt verwenden — ganz ohne zusätzliches CSS:

<div class="bg-white rounded-lg shadow p-4 mb-4">
    <h3 class="text-sm font-medium text-gray-500">{{ $label }}</h3>
    <p class="mt-2 text-lg">{{ $value }}</p>
</div>

Für die meisten Modul-Views ist das der empfohlene Weg: Sie kommen ohne eigenes CSS, ohne Build-Schritt und ohne Manifest aus.

Den Symlink legt der ModuleManager automatisch an:

public/modules/my-module → modules/my-module/dist

Sie müssen ihn also nicht von Hand setzen. Für die Symlinks gilt:

  • Angelegt wird er, sobald ein Modul aktiviert oder gebootet wird.
  • Entfernt wird er, sobald ein Modul deaktiviert wird.
  • Das übergeordnete Verzeichnis public/modules/ wird bei Bedarf automatisch erstellt.

Beispiel: Widget-View mit Tailwind

Ein vollständiges, lauffähiges Widget — komplett mit Tailwind-Klassen, ohne eine einzige Zeile eigenes CSS:

{{-- resources/views/widgets/status-card.blade.php --}}
<div class="bg-white rounded-lg shadow p-4">
    <div class="flex items-center gap-3">
        <div class="flex-shrink-0">
            <svg class="w-8 h-8 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
            </svg>
        </div>
        <div>
            <h3 class="text-sm font-medium text-gray-500">{{ $label }}</h3>
            <p class="text-lg font-semibold text-gray-900">{{ $data['status'] ?? 'Unknown' }}</p>
        </div>
    </div>
</div>

Wie Sie ein solches Widget im Dashboard registrieren, steht unter Navigation & Dashboard. Die genaue API der hier erwähnten ModuleAssetRegistry und der übrigen Registries finden Sie unter Registries.