Modul-Vertrag¶
Diese Seite beschreibt, wie Module aufgebaut sind und welche Regeln zwischen ihnen gelten. Sie ist die Grundlage für Eigenes Modul.
Das Ziel¶
Ein Modul muss so gekapselt sein, dass sein Entfernen keinen Build-Bruch verursacht und möglichst wenig zurückbleibt. Ordner löschen, Schalter umlegen oder Filter setzen — alle drei Wege müssen zu einem funktionierenden Zustand führen.
Wo Module liegen¶
| Ort | Art |
|---|---|
includes/theme-options/default/{slug}/{slug}.php |
Kern-Modul |
includes/theme-options/add-ons/{slug}/{slug}.php |
Add-on-Modul |
includes/theme-options/woocommerce/{slug}/{slug}.php |
Shop-Modul |
Ein Modul ist ein Ordner mit einer gleichnamigen PHP-Datei. Gefunden wird es automatisch, ein Eintrag in einer Liste ist nicht nötig.
Wie ein Modul abgeschaltet wird¶
Vier Wege, in der Reihenfolge ihrer Angemessenheit:
- Theme → Module in der Administration — der Normalfall
- Die Option
twtheme_modulesübertwtheme_modules_apply_state()setzen - Die Filter
twtheme_addons_skipundtwtheme_default_addons_skipaus dem Child - Den Ordner löschen
Der Modulzustand¶
Quelle der Wahrheit ist eine gewöhnliche WordPress-Option auf der jeweiligen Seite:
[
'schema' => 1,
'managed_by' => 'local', // oder 'hub'
'source' => 'admin', // oder 'twhub', 'cli'
'updated_at' => '2026-08-05T12:00:00+00:00',
'modules' => ['megamenu' => true, 'slider' => false],
]
includes/theme-options/modules.php liest sie, bevor default/default.php und
add-ons/add-ons.php ihre Suche starten. Die Datenbank steht zu diesem Zeitpunkt
bereits zur Verfügung.
Fehlt ein Slug, gilt defaultEnabled aus der module.json. Ein neu ausgeliefertes
Modul ist damit automatisch an.
Nur über eine Funktion schreiben¶
Geschrieben wird ausschließlich über twtheme_modules_apply_state() — von der
Admin-Seite, vom TW Hub Agent und von WP-CLI gleichermaßen. Dort sitzen die
Prüfung gegen die Registry und die Auflösung der Abhängigkeiten.
Das Theme ruft dabei nirgends nach Hause. Es gibt keinen ausgehenden Request und keine Lizenzprüfung. Ein Hub ist nur einer von drei möglichen Schreibern dieser Option; fällt er weg, bleibt der Zustand bestehen und über Theme → Module änderbar.
Metadaten in der module.json¶
{
"name": "Megamenü",
"description": "Mehrspaltiges Hauptmenü auf Basis der WordPress-Menüs.",
"requires": ["hyperlinks"],
"required": false,
"defaultEnabled": true
}
| Feld | Bedeutung |
|---|---|
name |
Anzeigename auf der Modul-Seite |
description |
Erklärung auf der Modul-Seite |
requires |
Slugs, die aktiv sein müssen |
required |
nie abschaltbar |
defaultEnabled |
Zustand, wenn nichts gespeichert ist |
exclusive |
Gruppenname; pro Gruppe darf nur ein Modul aktiv sein |
tab |
Das Modul trägt nur einen Reiter und erscheint nicht in der Liste |
requires: Solange ein abhängiges Modul an ist, bleibt die Voraussetzung an und
ihr Schalter ist gesperrt — mit Begründung in der Oberfläche.
required: Gilt für die Struktur-Reiter general, website, system,
utilities und integration, an die andere Module ihre Felder hängen, sowie für
layout-blocks und meta-blocks.
exclusive: Derzeit zwei Gruppen — consent für Complianz und Cookiebot, maps
für Google Maps und OpenStreetMap. Beim gleichzeitigen Aktivieren gewinnt das neu
hinzugekommene Modul.
Abhängigkeiten zwischen Modulen¶
Hier steckt die häufigste Fehlerquelle. Da Module zur Laufzeit abschaltbar sind, ist ein ungeschützter Aufruf über Modulgrenzen hinweg ein Fatal Error, kein theoretisches Risiko.
Deshalb gilt: Aufrufe über Modulgrenzen nur weich, mit sinnvollem Rückfall.
if (function_exists('twtheme_icons_available') && twtheme_icons_available()) {
echo twtheme_inline_icon('phone');
}
Harte Voraussetzungen gehören zusätzlich in die module.json, damit die
Oberfläche den Schalter sperrt. Beides zusammen: der Filter für die Laufzeit, die
module.json für die Bedienbarkeit.
Beispiele im Theme:
- Search greift auf Loops & Filter nur über
twtheme_loops_filter_enqueue_frontend_assets()zu. - Die Block-Freigabeliste für den Editor nimmt nur registrierte
twtheme/*-Blöcke auf. - Für sechs häufig genutzte Funktionen gibt es Stubs in
includes/template-fallbacks.php— siehe Template-Funktionen.
Namen ohne Präfix beanspruchen¶
Ein Modul, das eine Funktion ohne Modulpräfix belegen will — etwa twtheme_map()
— darf sie nicht beim Laden deklarieren. Module laden mit der functions.php
des Parents, Projektdateien erst bei after_setup_theme. Eine ungeschützte
Deklaration wäre dann ein Fatal Error, kein Überschreiben.
Der richtige Weg: die Ausgabe unter eigenem Namen anbieten
(twtheme_gmaps_render()) und den historischen Namen erst bei
after_setup_theme mit Priorität 99 nachreichen, wenn er noch frei ist.
Der Build-Teil der module.json¶
Dieselbe Datei trägt die Build-Einträge:
{
"scss": [
["assets/foo.scss", "assets/foo.css"],
["assets/bar.scss", "theme:assets/css/bar.css"]
],
"js": [
{ "src": "assets/foo.js", "bundle": true },
{ "src": "source/admin.js", "outfile": "admin.js", "bundle": true }
]
}
| Regel | Bedeutung |
|---|---|
| Pfad ohne Präfix | relativ zum Modulordner |
Präfix theme: |
relativ zur Theme-Wurzel, für Ausgaben außerhalb des Moduls |
Gefunden werden die Einträge von scripts/module-discovery.mjs, genutzt von
sass-build.mjs und esbuild-frontend-js.mjs. Ein neues Modul mit Assets muss
also nirgends in der Build-Konfiguration eingetragen werden.
Fehlende Quelldateien werden übersprungen. Ein entferntes Modul bricht
npm run build:css und build:frontend-js nicht.
Feature-CSS außerhalb von main.css¶
Nicht jedes Stylesheet gehört ins Haupt-Bundle. News, Stock, Mockup, Breadcrumb
und Download liegen als source/scss/feature-*.scss und werden bedingt geladen,
gesteuert von feature-css.php.
Das Frontend-Bundle des Megamenüs liegt im Modul selbst:
megamenu/assets/megamenu-frontend.scss wird zu megamenu-frontend.css und in
megamenu.php eingebunden. Hülle und Offcanvas kommen weiter über das
style-Feld der block.json als megamenu-shell.css.
Diese Dateien dürfen nicht in source/scss/style.scss importiert werden —
sonst landen sie doch im Haupt-Bundle.
Editor-Assets und Frontend-Assets¶
Die häufigste Ursache dafür, dass Editor-CSS im Frontend landet.
| Ziel | Feld oder Hook |
|---|---|
| Nur Editor | editorScript, editorStyle (editor.css), enqueue_block_editor_assets |
| Editor-Iframe | enqueue_block_assets nur mit Guard wp_should_load_block_editor_scripts_and_styles() |
| Frontend | style, viewScript, wp_enqueue_scripts, Enqueue in render.php |
Verboten:
"script"in derblock.json— lädt im Editor und im Frontendeditor.cssoder*-editor.cssunter"style"- Gemeinsames CSS doppelt in
"style"und"editorStyle"; Gemeinsames gehört nur in"style"
Prüfen lässt sich das mit npm run audit:editor-assets — siehe
Qualität prüfen.
Versionen laufen automatisch¶
npm run release:package schreibt Kern-, Icon- und Modulpakete samt Manifest ins
Paketverzeichnis. Angehoben wird über Inhalts-Hashes, nicht von Hand:
| Was | Version | Anhebung |
|---|---|---|
| Modul | module.json → version |
Patch, sobald sich im Modulordner etwas ändert. Die module.json zählt mit; nur die eingesetzte version selbst bleibt außen vor. |
| Kern | style.css und TWTHEME__VERSION |
Patch, sobald sich am Kernpaket etwas ändert. Beide Dateien werden geschrieben und wollen committet werden. |
Eine von Hand gesetzte Kernversion gewinnt: Steht in style.css bereits mehr als
zuletzt ausgeliefert, springt sie nicht ein zweites Mal.
Ausgelöst wird das nach jedem Commit am Theme, über den Hook aus
scripts/git-hooks/. Einmalig einhängen:
Der Hook läuft im Hintergrund und protokolliert nach
.git/twtheme-release.log. Überspringen mit
TWTHEME_SKIP_RELEASE=1 git commit ….
Siehe Release-Pakete.
Prüfen¶
| Befehl | Findet |
|---|---|
audit:modules |
tote Build-Quellen, leere Add-ons, fehlende Pfade |
audit:editor-assets |
Editor-Assets, die ins Frontend gelangen, und ungeschützte enqueue_block_assets-Hooks |
Weiter¶
- Eigenes Modul — Schritt für Schritt
- Alle Module
- Module schalten — die Anwendersicht