Plugin-Oberfläche
Ein Plugin bringt eigene Ansichten mit und hängt sich an bestehende. Beides läuft über eine einzige Datei: frontend/plugin.manifest.ts.
Das Manifest
Sie exportiert ein Objekt. Jeder Schlüssel ist optional — ein Plugin ohne Oberfläche lässt die Datei ganz weg.
import type { OctibizPluginManifest } from '@/plugins/manifest'
const manifest: OctibizPluginManifest = {
routes: [
{
path: 'acme-shipping',
name: 'acme-shipping',
component: () => import('./views/ListView.vue'),
meta: { permission: 'acme.shipping.view' },
},
],
routeModules: { 'acme-shipping': 'acme-shipping' },
localeLoaders: {
de: () => import('./locales/de'),
en: () => import('./locales/en'),
},
}
export default manifest
Gefunden wird die Datei beim Bau, aus drei Quellen: eingecheckte Plugins, per Paket installierte und über den Paket-Manager eingebundene. Alle drei werden in dieselbe Oberfläche gemischt.
Vier Regeln, die nicht verhandelbar sind
1. Kern-Bausteine nur über @/sdk. Das ist die stabile Fläche. Tiefe Verweise auf andere Kern-Module werden abgewiesen, weil sie beim nächsten Umbau brechen. Zusätzlich erlaubt sind reine Typ-Verweise auf die Manifest-Fläche.
2. Komponenten immer als Nachlader, also () => import('./views/FooView.vue'). Ein direkter Verweis zieht die Komponente in das Startpaket jedes Nutzers, auch wenn er das Modul nie öffnet.
3. Beim Zusammenführen gewinnt der Erste. Der Kern vor den Plugins, Plugins in stabiler Reihenfolge. Ein Konflikt erzeugt eine Warnung, keinen Abbruch: Ein fehlerhaftes Plugin darf die Oberfläche nie zerlegen.
4. Kein eigenes Aussehen. Es gibt einen verbindlichen Baustein-Katalog. Ein Plugin, das eigene Knöpfe definiert, sieht nach zwei Systemupdates falsch aus.
Die wichtigsten Schlüssel
| Schlüssel | Wofür |
|---|---|
routes | Eigene Seiten, Pfade ohne führenden Schrägstrich |
nav | Eine neue Navigationsgruppe |
navLinks | Ein Link in eine bestehende Gruppe, auch die des Kerns |
routeModules | Modul-Sperre für eigene Routen |
settingsTiles | Kachel in den Einstellungen |
widgets | Baustein für das Start-Dashboard |
viewExtensions | Beiträge zu Andockstellen fremder Ansichten |
columnExtensions | Zusätzliche Spalten in fremden Listen |
filterExtensions | Zusätzliche Filter in fremden Listen |
viewOverrides | Eine Ansicht ersetzen oder verbergen |
searchTypes | Eigene Objekte in der globalen Suche |
tagScopes | Eigene Objekte als Ziel für Schlagworte |
localeLoaders | Übersetzungen, siehe Übersetzungen |
portalRoutes, portalNav | Beiträge zum Kundenportal |
Die Modul-Sperre der Oberfläche
routeModules: { 'acme-shipping': 'acme-shipping' }
Links steht das Routen-Präfix, rechts der Kurzname des Modul-Schalters ohne module..
Die beiden verwechselt zu haben ist ein realer, teuer gewordener Fehler. Trifft der rechte Wert keinen echten Schalter, steht er nie in der Liste der abgeschalteten Module — und die Sperre hält das Modul immer für eingeschaltet. Sie wirkt dann nie.
Andockstellen
Der Kern rendert benannte Stellen, an die Plugins Beiträge hängen. Ohne Beitrag rendert die Stelle nichts.
viewExtensions: {
'customer-detail.tabs': [
{
id: 'acmeShipping.customerShipments',
component: () => import('./components/CustomerShipmentsTab.vue'),
permission: 'acme.shipping.view',
module: 'acme-shipping',
order: 40,
meta: { labelKey: 'acmeShipping.tab.shipments' },
},
],
}
Jeder Beitrag ist doppelt gesperrt: über das Recht und über den Modul-Schalter. Beides fail-closed. Ein abgeschaltetes Plugin verschwindet aus der Oberfläche, ohne dass die Kern-Ansicht davon weiß.
Die Namen folgen einem festen Schema: <objekt>-detail.actions, .tabs, .panels für Detailansichten, <objekt>-list.actions für Listen. Die vollständige Liste aller 84 Stellen mit ihren übergebenen Werten steht in der Andockstellen-Referenz. Sie wird aus dem Code erzeugt und kann deshalb nicht veralten.
Spalten, Zeilen-Aktionen, Filter, Formularfelder
Listen und Dialoge haben eigene Stellen mit eigenen Verträgen:
- Spalten über
columnExtensions, die Zeile wird übergeben. - Aktionen je Zeile über
viewExtensionsauf<liste>.row-actions. - Filter über
filterExtensions. Der Wert fließt in dieselbe Filterstruktur wie ein Kern-Filter. - Formularfelder über
viewExtensionsauf<objekt>-form.fields. Der Beitrag liest und
schreibt das Formularmodell direkt.
Ersetzen statt ergänzen
viewOverrides: {
'customer-detail.panels': { mode: 'hide' },
}
Ein Override ist autoritativ: Er verdrängt alle additiven Beiträge an dieser Stelle. replace ersetzt durch eine eigene Komponente, hide blendet aus.
Das ist ein scharfes Werkzeug. Zwei Plugins, die dieselbe Stelle überschreiben, vertragen sich nicht — hier gewinnt wieder der Erste.
Das Kundenportal ist anders gesperrt
Portal-Beiträge laufen über eigene Stellen und ohne die Sperre über Recht und Modul. Das Portal hat eine eigene Anmeldung; es kennt weder die Rechte der Mitarbeitenden noch die Liste abgeschalteter Module.
Dort sperrt sich der Beitrag selbst: Er ruft seinen eigenen Endpunkt und blendet sich still aus, wenn der nichts liefert. Bei abgeschaltetem Modul antwortet der Endpunkt ohnehin mit 404.
Die stabile Oberflächen-Schnittstelle
Alles, was ein Plugin vom Kern braucht, kommt aus @/sdk: Bausteine, Formularelemente, Datenabruf, Berechtigungsprüfung, Benachrichtigungen, Formatierung.
Was dort nicht steht, ist bewusst nicht Teil der Zusage. Wenn dir etwas fehlt, ist das ein Grund, die Fläche zu erweitern, kein Grund für einen tiefen Verweis.
Übersetzungen nachladen, nicht importieren
localeLoaders: {
de: () => import('./locales/de'),
en: () => import('./locales/en'),
}
Nicht als direkter Verweis. Eine Messung hat gezeigt, dass statische Sprachdateien 651 KB komprimiert zum ersten Bildaufbau beitrugen, für Sprachen, die die meisten Nutzer nie sehen. Details in Übersetzungen.
Was schiefgeht, und woran es liegt
| Symptom | Ursache |
|---|---|
| Die Sperre wirkt nie, das Modul gilt immer als an | Im routeModules steht rechts das Routen-Segment statt des Schalter-Kurznamens |
| Der Beitrag erscheint nicht | Recht fehlt, Modul aus, oder ein Override verdrängt ihn |
| Das Startpaket ist plötzlich groß | Komponente oder Sprachdatei direkt verwiesen statt nachgeladen |
| Der Bau bricht mit einem abgewiesenen Verweis ab | Tiefer Zugriff auf ein Kern-Modul statt über @/sdk |
| Nach einem Systemupdate sieht das Plugin fremd aus | Eigene Ausgabe-Elemente statt der Bausteine des Katalogs |
Weiter
- Andockstellen-Referenz — alle 84 Stellen mit ihren Werten
- Manifest-Referenz — jeder Schlüssel im Detail
- Übersetzungen
- Backend — die Gegenseite