@xpulse/template – Component Spec
Status: CONCEPT · Erstellt März 2026
Übergeordnet: GLOBAL_concept-ecosystem.md, GLOBAL_concept-web.md
Übersicht
Template-Engine für das xPulse Ökosystem. Rendert .tpl.html Dateien
zu fertigem HTML.
Eine Aufgabe: Templates rendern – nicht mehr.
Kennt kein HTTP, keinen Controller, kein Routing.
Nimmt einen Template-Pfad und ein Daten-Objekt entgegen – gibt HTML zurück.
Dependencies
1 @xpulse /template2 └── @xpulse /event ← template :render :before/after, template :loaded etc. feuern
Dateistruktur im xPart
Kein getrennter views/ Ordner – alles liegt in src/templates/.
Der Controller referenziert Templates direkt über ihren Pfad.
1 src/ 2 templates/ 3 base.tpl.html ← Basis-Layout 4 platform .tpl.html ← 5 index .tpl.html ← 6 info/ 7 index .tpl.html ← 8 bla.tpl.html ← 9 contact/ 10 index .tpl.html ← 11 partials/ 12 card.tpl.html ← Partial – kein extends 13 nav.tpl.html ← Partial – kein extends 14 template-methods/ ← Eigene Method -Registrierungen 15 custom .js
Template-Syntax
Zwei klar unterschiedliche Syntaxformen:
1 {{ variable }} ← Ausgabe – gibt einen Wert aus 2 {% direktive %} ← Logik – Steuerung, Vererbung, Includes
Template-Vererbung: extends + slots
Ein Template kann genau ein anderes Template extends – beliebig tief stapelbar.
Slots werden von innen nach außen befüllt.
1 2 <!DOCTYPE html > 3 <html lang ="de" > 4 <head > 5 <title > {% slot 'title' %}xPulse{% endslot %}</title > 6 </head > 7 <body > 8 {% slot 'body' %}{% endslot %} 9 </body > 10 </html >
1 2 {% extends 'base' %} 3 4 {% slot 'title' %}{{ title }} – xPulse{% endslot %} 5 6 {% slot 'body' %} 7 <nav > 8 <a href ="/" class ="{% if _route.path === '/' %}active{% endif %}" > Home</a > 9 <a href ="/info" class ="{% if _route.path === '/info' %}active{% endif %}" > Info</a > 10 </nav > 11 <main > 12 {% slot 'content' %}{% endslot %} 13 </main > 14 {% endslot %}
1 2 {% extends 'platform' %} 3 4 {% slot 'content' %} 5 <h1 > {{ title }}</h1 > 6 <ul > 7 {% each items as item %} 8 <li > {{ item }}</li > 9 {% endeach %} 10 </ul > 11 {% endslot %}
Variablen
Ausgabe
1 {{ title }} 2 {{ user.name }} 3 {{ _route.path }}
Setzen und Überschreiben
1 2 {% set lang = lang ?? 'de' %} 3 4 5 {% set label = 'Willkommen' %} 6 7 8 {% set title = title ?? 'Standardtitel' %} 9 10 11 {{ lang }} 12 {{ label }}
Methods
Methods erweitern Variablen um aufrufbare Transformationen.
Zwei Aufrufarten – beide gleichwertig:
1 {{ variable.methodName }} ← Punkt-Notation 2 {% methodName(variable) %} ← Direktiven-Notation
Built-in Methods (String)
Method
Beispiel
Beschreibung
upper
{{ title.upper }}
Großschreibung
lower
{{ title.lower }}
Kleinschreibung
trim
{{ text.trim }}
Whitespace entfernen
length
{{ text.length }}
Länge
reverse
{{ text.reverse }}
Umkehren
slice(a, b)
{{ text.slice(0, 50) }}
Teilstring
replace(a, b)
{{ text.replace('foo', 'bar') }}
Ersetzen
includes(a)
{{ text.includes('foo') }}
Enthält – gibt true/false
startsWith(a)
{{ text.startsWith('http') }}
Beginnt mit
endsWith(a)
{{ text.endsWith('.de') }}
Endet mit
split(sep)
{{ text.split(',') }}
Aufteilen
Built-in Methods (Array)
Method
Beispiel
Beschreibung
length
{{ items.length }}
Anzahl Elemente
first
{{ items.first }}
Erstes Element
last
{{ items.last }}
Letztes Element
contains(v)
{{ items.contains('foo') }}
Enthält Wert
join(sep)
{{ items.join(', ') }}
Zusammenfügen
reverse
{{ items.reverse }}
Umkehren
Built-in Methods (Number)
Method
Beispiel
Beschreibung
toFixed(n)
{{ price.toFixed(2) }}
Dezimalstellen
round
{{ score.round }}
Runden
floor
{{ score.floor }}
Abrunden
ceil
{{ score.ceil }}
Aufrunden
abs
{{ diff.abs }}
Absolutwert
Method Discovery
@xpulse/template discovert Methods dreistufig – analog zu Controller Discovery:
1 1. @xpulse /template /src/template -methods*.js ← Built-ins (immer geladen)2 2. node_modules/@xpulse *.js ← aus anderen xPulse Packages3 3. src/template -methods*.js ← aus der eigenen App (überschreibt)
Lokal überschreibt Package – mit Info im Debug-Log.
Method Basis-Klasse
1 2 import { Method } from '@xpulse/template' ;3 4 export default class CustomMethods extends Method {5 register ( ) { 6 this .add ('cool' , (data ) => data.split ('' ).reverse ().join ('' )); 7 this .add ('currency' , (data ) => `€ ${Number (data).toFixed(2 )} ` ); 8 this .add ('slug' , (data ) => data.toLowerCase ().replace (/\s+/g , '-' )); 9 } 10 }
Im Template:
1 {{ title.cool }} 2 {{ price.currency }} 3 {{ title.slug }} 4 5 {% cool(title) %} 6 {% currency(price) %}
Include mit optionalen Parametern
1 2 {% include 'partials/card' %} 3 4 5 {% include 'partials/card' { title: 'Foo', active: true } %} 6 7 8 {% each tools as tool %} 9 {% include 'partials/card' { title: tool.name, id: tool.id, type: tool.type } %} 10 {% endeach %}
Parameter sind nur im Scope des Partials verfügbar – kein Leak in den Parent-Scope.
1 2 <div class ="card" > 3 <h2 > {{ title }}</h2 > 4 <span class ="type" > {{ type }}</span > 5 </div >
Direktiven-Übersicht
Direktive
Beschreibung
{% extends 'name' %}
Dieses Template erweitert ein anderes
{% slot 'name' %}...{% endslot %}
Slot definieren oder befüllen
{% set var = value %}
Variable setzen oder überschreiben
{% if condition %}...{% endif %}
Bedingte Ausgabe
{% if condition %}...{% else %}...{% endif %}
Bedingte Ausgabe mit Fallback
{% each items as item %}...{% endeach %}
Iteration über Array
{% each items as item, index %}...{% endeach %}
Iteration mit Index
{% include 'pfad/partial' %}
Partial einbinden
{% include 'pfad/partial' { key: val } %}
Partial mit eigenen Parametern
{% methodName(variable) %}
Custom/Built-in Method aufrufen
API
Rendern
1 import template from '@xpulse/template' ;2 3 const html = await template.render ('info/bla' , {4 title : 'Bla' , 5 items : ['a' , 'b' , 'c' ], 6 });
template.render() sucht automatisch in src/templates/ –
der Pfad ist relativ dazu:
1 template.render ('info/bla' ) 2
_route wird nicht manuell übergeben – @xpulse/controller injiziert es
automatisch via template:render:before Event (siehe unten).
Gefeuerte Events
Event
Payload
Wann
template:render:before
{ view, data }
vor dem Rendern – data noch mutierbar
template:render:after
{ view, html, duration }
nach dem Rendern – html noch mutierbar
template:loaded
{ path, cached }
wenn eine .tpl.html Datei geladen wird
template:methods:discovered
{ count, sources }
nach Method Discovery
template:method:registered
{ name, source }
wenn eine einzelne Method registriert wird
Middleware via Events
1 import event from '@xpulse/event' ;2 3 4 event.on ('template:render:before' , ({ view, data } ) => { 5 data.appVersion = config.get ('release.current' ); 6 data.env = config.get ('app.env' ); 7 }); 8 9 10 event.on ('template:render:after' , ({ view, html } ) => { 11 12 });
Render-Reihenfolge
1 1. template:render:before feuern ← data noch mutierbar 2 2. src/templates/info/bla.tpl.html laden 3 3. template:loaded feuern 4 4. {% extends 'platform' %} → src/templates/platform.tpl.html laden + template:loaded 5 5. {% extends 'base' %} → src/templates/base.tpl.html laden + template:loaded 6 6. Slots von innen nach außen befüllen 7 7. {% set %} Variablen auflösen 8 8. {% include %} Partials einbetten (mit eigenem Scope) 9 9. {% if %} / {% each %} Direktiven auflösen 10 10. Methods auflösen – {{ var.method }} + {% method (var) %} 11 11. {{ variablen }} ersetzen 12 12. template:render:after feuern ← html noch mutierbar 13 13. Fertiges HTML zurückgeben
Debug Integration
Wenn @xpulse/debug als devDependency installiert ist, stellt @xpulse/template
automatisch einen DataCollector bereit. @xpulse/debug discovert ihn via
node_modules/@xpulse/template/src/datacollectors/ – keine Konfiguration nötig.
1 "optionalDependencies" : { 2 "@xpulse/debug" : "^1.0.0" 3 }
Der TemplateCollector (name: 'template', icon: '📋') zeigt im Web Profiler:
Hauptbereiche (Areas):
Summary (kv): Anzahl Renders, Total Rendering Time
Extends Chain (list): Vererbungskette des ersten Renders (nur wenn vorhanden)
Shared Template Data (list): Daten-Keys die in allen Renders des Requests vorkommen (nur bei mehreren Renders)
Sub-Panels – eines pro gerendertem Template:
Info (kv): View-Name, Duration, Resolved Path
Template Data / Unique Data (kv): Template-Daten mit Werten – bei mehreren Renders nur die eindeutigen Keys des jeweiligen Templates
Für jedes Template in der Extends-Chain gibt es zusätzlich ein Sub-Panel mit
Pfad-Info und dem Hinweis, dass Duration und Daten beim Kind-Template gemessen werden.
Außerdem schreibt der TemplateCollector einen Waterfall-Span pro Render
(kind: 'template', Dauer aus payload.duration).
Badge in der Toolbar: Anzahl der Renders (z.B. 3)
Paket-Struktur
1 @xpulse/template/ 2 src / 3 index.js ← default export: template-Objekt 4 loader.js ← .tpl .html Dateien laden + cachen 5 parser.js ← Template-Syntax parsen 6 renderer.js ← Slots, Variablen, Direktiven auflösen 7 resolver.js ← extends Kette auflösen 8 method.js ← Method Basis-Klasse + Discovery 9 template-methods/ 10 string.js ← Built-in String Methods 11 array.js ← Built-in Array Methods 12 number.js ← Built-in Number Methods 13 test/ 14 parser.test .js 15 renderer.test .js 16 resolver.test .js 17 method.test .js 18 docs/ 19 index.md 20 api.md 21 _meta.json 22 Dockerfile 23 Makefile 24 README.md 25 package.json 26 xpulse.json