Templating mit JS Template Literals
Für meinen kleinen Static-Site Generator easto benutze ich keine Templating-Engine, sondern mache nur Suchen-und-Ersetzen in den Templates. Das sah bisher so aus:
Gut genug: String-Replacement
<div class="post__meta"> <span class="post__category">#{{ META_TAGS }}</span> <time datetime="{{ META_DATE }}">{{ META_DATELABEL }}</time> </div> <h1 class="post__title">{{ META_TITLE }}</h1> {{ CONTENT }}
Im JavaScript habe ich dann über eine Liste von Metadaten iteriert und diese im Template ersetzt:
template.replace('{{ CONTENT }}', fileContentHtml) for (var key in metadata) { const re = new RegExp('{{ META_' + key.toUpperCase() + ' }}', 'g') if ( key === 'tags') { const tagsString = metadata[key].join(', #') template = template.replace(re, tagsString) } else { template = template.replace(re, metadata[key]) } }
... im Prinzip. In echt war dann alles noch etwas umständlicher. Die Regex Nummer ist nötig, weil nur das erste Vorkommen ersetzt wird, wenn man einen String als Parameter verwendet. In Teasern benutze ich etwas andere Ausgaben als im Post oder den Feeds. Usw.
Besser: Template Literals
Bei einem anderen Projekt wurde ich wieder daran erinnert, dass man in modernem JavaScript Template Literals zur Verfügung hat. Wenn Strings in Back Ticks (ˋ) gefasst sind, werden enthaltene Ausdrücke ausgeführt.
var kitty = "Cheddar"; console.log(`Meine Katze heißt ${kitty}.`); => Meine Katze heißt Cheddar.
Meine Template-Dateien sehen nun etwas anders aus. Erstens werden sie komplett mit Back Ticks umschlossen. Und zweitens verwende ich Ausdrücke mit Dollarzeichen und geschweiften Klammern anstelle der selbst ausgedachten Marker fürs Suchen-und-Ersetzen.
`<div class="post__meta"> <span class="post__category">#${ meta.tags.join(', #') }</span> <time datetime="{{ META_DATE }}">${ meta.datelabel }</time> </div> <h1 class="post__title">${ meta.title }</h1> ${ content } `
Im JavaScript stecke ich ein Objekt zusammen das alles enthält, was das Template braucht.
const output = eval_template(template, { 'blogmeta': CONFIG, 'meta': metadata, 'content': fileContentHtml })
Damit das mit mehreren Variablen und Objekten funktioniert, habe ich im Netz eine magische Funktion gefunden (deren Quelle ich leider nicht mehr weiß).
const eval_template = (s, params) => { return Function(...Object.keys(params), "return " + s) (...Object.values(params)) }
Voilá
Diese Änderung hat mehrere Vorteile.
- verschlankt den Code
- kleine Operatioenn wie
join
,toUpper
oderif
kann ich im Template machen. - Platzhalter im Template, für die es keine Variablen/Werte gibt, werfen jetzt Fehler (vorher wurden sie einfach nicht ersetzt).
- Fallbacks im Template
<html lang="${ meta.language || 'de' }">
- Template-Code-Beispiele aus dem Inhalt wurden vorher behandelt wie Templates selbst – und auch ersetzt. Das passiert jetzt nicht mehr.
Außerdem sieht das professioneller und sexier aus.
Geht da noch mehr?
Auf jeden Fall. Insbesondere Nesting Tempaltes und Tagged Templates sehen vielversprechend aus, um z.B. verschiedene Block-Typen zu rendern. (Im Content steht ein Objekt wie quote = {text:"Niemand hat das Recht zu gehorchen", name:"Hannah Arendt"}
), und das Blog rendert eine schöne Blockquote.