xPulse
🇩🇪 DE

@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

@xpulse/template
└── @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.

src/
templates/
base.tpl.html ← Basis-Layout
platform.tpl.html ← {% extends 'base' %}
index.tpl.html ← {% extends 'platform' %}
info/
index.tpl.html ← {% extends 'platform' %}
bla.tpl.html ← {% extends 'platform' %}
contact/
index.tpl.html ← {% extends 'platform' %}
partials/
card.tpl.html ← Partial – kein extends
nav.tpl.html ← Partial – kein extends
template-methods/ ← Eigene Method-Registrierungen
custom.js

Template-Syntax

Zwei klar unterschiedliche Syntaxformen:

{{ variable }} ← Ausgabe – gibt einen Wert aus
{% 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.

<!-- src/templates/base.tpl.html -->
<!DOCTYPE html>
<html lang="de">
<head>
<title>{% slot 'title' %}xPulse{% endslot %}</title>
</head>
<body>
{% slot 'body' %}{% endslot %}
</body>
</html>
<!-- src/templates/platform.tpl.html -->
{% extends 'base' %}
{% slot 'title' %}{{ title }} – xPulse{% endslot %}
{% slot 'body' %}
<nav>
<a href="/" class="{% if _route.path === '/' %}active{% endif %}">Home</a>
<a href="/info" class="{% if _route.path === '/info' %}active{% endif %}">Info</a>
</nav>
<main>
{% slot 'content' %}{% endslot %}
</main>
{% endslot %}
<!-- src/templates/info/bla.tpl.html -->
{% extends 'platform' %}
{% slot 'content' %}
<h1>{{ title }}</h1>
<ul>
{% each items as item %}
<li>{{ item }}</li>
{% endeach %}
</ul>
{% endslot %}

Variablen

Ausgabe

{{ title }}
{{ user.name }}
{{ _route.path }}

Setzen und Überschreiben

<!-- Setzen mit Fallback – nur wenn nicht vorhanden -->
{% set lang = lang ?? 'de' %}
<!-- Setzen mit festem Wert -->
{% set label = 'Willkommen' %}
<!-- Überschreiben -->
{% set title = title ?? 'Standardtitel' %}
<!-- Ausgabe -->
{{ lang }}
{{ label }}

Methods

Methods erweitern Variablen um aufrufbare Transformationen. Zwei Aufrufarten – beide gleichwertig:

{{ variable.methodName }} ← Punkt-Notation
{% 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. @xpulse/template/src/template-methods/**/*.js ← Built-ins (immer geladen)
2. node_modules/@xpulse/*/src/template-methods/**/*.js ← aus anderen xPulse Packages
3. src/template-methods/**/*.js ← aus der eigenen App (überschreibt)

Lokal überschreibt Package – mit Info im Debug-Log.

Method Basis-Klasse

// src/template-methods/custom.js
import { Method } from '@xpulse/template';
export default class CustomMethods extends Method {
register() {
this.add('cool', (data) => data.split('').reverse().join(''));
this.add('currency', (data) => `€ ${Number(data).toFixed(2)}`);
this.add('slug', (data) => data.toLowerCase().replace(/\s+/g, '-'));
}
}

Im Template:

{{ title.cool }}
{{ price.currency }}
{{ title.slug }}
{% cool(title) %}
{% currency(price) %}

Include mit optionalen Parametern

<!-- Einfaches Include -->
{% include 'partials/card' %}
<!-- Include mit eigenen Parametern -->
{% include 'partials/card' { title: 'Foo', active: true } %}
<!-- Include in einer Iteration – jeder Durchlauf bekommt eigene Params -->
{% each tools as tool %}
{% include 'partials/card' { title: tool.name, id: tool.id, type: tool.type } %}
{% endeach %}

Parameter sind nur im Scope des Partials verfügbar – kein Leak in den Parent-Scope.

<!-- src/templates/partials/card.tpl.html -->
<div class="card">
<h2>{{ title }}</h2>
<span class="type">{{ type }}</span>
</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

import template from '@xpulse/template';
const html = await template.render('info/bla', {
title: 'Bla',
items: ['a', 'b', 'c'],
});

template.render() sucht automatisch in src/templates/ – der Pfad ist relativ dazu:

template.render('info/bla')
// → lädt src/templates/info/bla.tpl.html

_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

import event from '@xpulse/event';
// Globale Variablen injizieren – immer in jedem Template verfügbar
event.on('template:render:before', ({ view, data }) => {
data.appVersion = config.get('release.current');
data.env = config.get('app.env');
});
// HTML nachbearbeiten – z.B. minifyen, Kommentare entfernen
event.on('template:render:after', ({ view, html }) => {
// html mutieren
});

Render-Reihenfolge

1. template:render:before feuern ← data noch mutierbar
2. src/templates/info/bla.tpl.html laden
3. template:loaded feuern
4. {% extends 'platform' %} → src/templates/platform.tpl.html laden + template:loaded
5. {% extends 'base' %} → src/templates/base.tpl.html laden + template:loaded
6. Slots von innen nach außen befüllen
7. {% set %} Variablen auflösen
8. {% include %} Partials einbetten (mit eigenem Scope)
9. {% if %} / {% each %} Direktiven auflösen
10. Methods auflösen – {{ var.method }} + {% method(var) %}
11. {{ variablen }} ersetzen
12. template:render:after feuern ← html noch mutierbar
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.

"optionalDependencies": {
"@xpulse/debug": "^1.0.0"
}

Der TemplateCollector (name: 'template', icon: '📋') zeigt im Web Profiler:

Hauptbereiche (Areas):

Sub-Panels – eines pro gerendertem Template:

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

@xpulse/template/
src/
index.js ← default export: template-Objekt
loader.js.tpl.html Dateien laden + cachen
parser.js ← Template-Syntax parsen
renderer.js ← Slots, Variablen, Direktiven auflösen
resolver.js ← extends Kette auflösen
method.js ← Method Basis-Klasse + Discovery
template-methods/
string.js ← Built-in String Methods
array.js ← Built-in Array Methods
number.js ← Built-in Number Methods
test/
parser.test.js
renderer.test.js
resolver.test.js
method.test.js
docs/
index.md
api.md
_meta.json
Dockerfile
Makefile
README.md
package.json
xpulse.json
de/spec.md 2026-03-27