Deutsch
Migration von MJML
Diese Anleitung richtet sich an Teams, die E-Mail-Templates bisher in rohem MJML erstellt haben (mit Editoren wie VS Code, einem internen CLI oder einer selbstgebauten Build-Pipeline) und auf Templaticals visuellen Editor wechseln möchten. @templatical/import-mjml konvertiert ein MJML-Dokument direkt in Templaticals TemplateContent-Format — installieren Sie es, führen Sie es aus, und nutzen Sie die folgenden Abschnitte, um alles nachzuarbeiten, was es nicht automatisch abbilden kann.
Installation
bash
npm install @templatical/import-mjmlbash
pnpm add @templatical/import-mjmlbash
yarn add @templatical/import-mjmlbash
bun add @templatical/import-mjmlOhne Build-Schritt (CDN)
Sie können es auch von einem CDN laden:
html
<script type="module">
import { convertMjmlTemplate } from 'https://cdn.jsdelivr.net/npm/@templatical/import-mjml/+esm';
// ...dann konvertieren wie im Abschnitt „Verwendung" unten
</script>Verwendung
ts
import { convertMjmlTemplate } from '@templatical/import-mjml';
// Den rohen MJML-Quelltext einer E-Mail laden
const res = await fetch('/path/to/email.mjml');
const mjml = await res.text();
// In das Templatical-Format konvertieren
const { content, report } = convertMjmlTemplate(mjml);
// Im Editor verwenden
const editor = await init({
container: '#editor',
content,
});
// Konvertierungsbericht auf Auffälligkeiten prüfen
console.log(report);convertMjmlTemplate arbeitet synchron und gibt ein ImportResult zurück mit:
content— das konvertierteTemplateContent, bereit für den Editorreport— ein Konvertierungsbericht mit dem Status jedes Quellelements (converted,approximated,html-fallbackoderskipped)
Den Bericht lesen
Jeder Eintrag in report.entries beschreibt ein Quellelement:
| Status | Bedeutung |
|---|---|
converted | Jedes Attribut mit einer Templatical-Entsprechung wurde übernommen. |
approximated | Auf den richtigen Block abgebildet, aber ein Wert musste auf einen begrenzten Wertebereich angepasst werden — note nennt den ursprünglichen Wert. |
html-fallback | Keine Block-Entsprechung vorhanden; das ursprüngliche Markup bleibt in einem HtmlBlock erhalten. |
skipped | Es wurde nichts erzeugt (templaticalBlockType: null). |
ts
console.log(report.summary);
// { total: 24, converted: 21, approximated: 2, htmlFallback: 1, skipped: 0 }
for (const entry of report.entries) {
if (entry.status === 'approximated') {
console.warn(`<${entry.sourceTag}> approximiert:`, entry.note);
}
}
// <mj-section> approximiert: Column widths 40%, 60% have no exact Templatical layout; resolved to "1-2".Ein note bei einem approximated-Eintrag nennt immer den ersetzten Wert — ein Diff von report.entries zwischen zwei Durchläufen zeigt so genau, was eine Migration verändert hat.
Konvertierungsmodell
Diese Migration ist etwas kontraintuitiv. Templaticals Renderer erzeugt MJML als Ausgabe — auf den ersten Blick sehen MJML und Templatical identisch aus. Aber:
- MJML ist eine Markup-Sprache. Sie schreiben XML-ähnliche Tags (
<mj-section>,<mj-column>,<mj-text>) und der MJML-Compiler verwandelt das in tabellenbasiertes HTML. - Templatical speichert Templates als JSON-Baum mit typisierten Blöcken (
SectionBlock,ParagraphBlockusw.) und rendert diesen Baum beim Export zu MJML.
Um ein MJML-Template in Templatical zu bringen, parsen Sie das MJML und bauen einen äquivalenten JSON-Baum auf — einschließlich der Auflösung von MJMLs eigenen Attribut-Vererbungsregeln (mj-all, Tag-Defaults, mj-class), bevor jedes Element abgebildet wird. Genau das leistet @templatical/import-mjml; die Mapping-Tabelle unten zeigt, was es umsetzt.
Pfad 1 — Visuell mit dem MJML als Referenz neu aufbauen
Bei einer Handvoll Templates ist der Neuaufbau von Hand neben Ihrer MJML-Quelle oft schneller, als ein Paket zu installieren:
- Öffnen Sie Ihre MJML-Quelle im Editor Ihrer Wahl.
- Öffnen Sie den Templatical-Editor (oder den Playground) daneben.
- Kompilieren Sie Ihr MJML einmal zu HTML und sehen Sie es sich an — das ist Ihr visuelles Ziel.
- Ziehen Sie die entsprechenden Templatical-Blöcke hinein (siehe Mapping-Tabelle unten).
- Kopieren Sie Textinhalte direkt. Bilder über Ihre Medienbibliothek neu hosten.
- Bilden Sie Styling über Templaticals Design-Tokens ab, statt über inline
mj-attributes.
Die meisten MJML-Templates sind in 10–20 Minuten umgezogen, sobald Sie eines oder zwei gemacht haben. Bei größeren Mengen führen Sie zuerst @templatical/import-mjml aus und nutzen diesen Pfad nur, um nachzuarbeiten, was als HTML-Fallback-Block gelandet ist.
Pfad 2 — Templaticals Renderer zur Verifikation nutzen
Sobald ein Template in Templatical vorliegt — importiert oder von Hand nachgebaut:
ts
import { renderToMjml } from '@templatical/renderer';
const mjml = await renderToMjml(content);
// Vergleichen Sie dieses MJML mit Ihrem ursprünglichen MJML-Quelltext.Ein Diff zwischen Original und dem von Templatical erzeugten MJML zeigt strukturelle Unterschiede. Eine sinnvolle Sanity-Prüfung vor einer Bulk-Migration.
MJML-Tag-Mapping
| MJML-Tag | Templatical-Block | Hinweise |
|---|---|---|
mj-section (mit mj-columns) | SectionBlock mit columns | Mehrspaltige Layouts funktionieren gleich; Spaltenbreiten kommen aus MJMLs width-Attribut oder werden gleichmäßig verteilt. Ebenso border-radius und border (siehe Rahmen und Ecken). background-url mit Größe, Position und Wiederholung wird zu backgroundImage (siehe Hintergrundbilder). |
mj-column | Section-Spalte | Eine Spalte hält eine Liste verschachtelter Blöcke. |
mj-group | SectionBlock.stackOnMobile: false | Kein eigener Block — markiert, dass die Spalten der Section auf Mobilgeräten nebeneinander bleiben, statt zu stapeln. |
mj-text | TitleBlock / TableBlock / MenuBlock / ParagraphBlock | Strukturell aufgelöst: Eine einzelne Überschrift als Wurzelelement wird zu TitleBlock, eine einzelne <table> zu TableBlock, durch <span> getrennte Top-Level-Anker ohne Paragraph-Wrapper und ohne Nicht-Whitespace-Textknoten als Geschwister zu MenuBlock, ein Satz Fließtext plus ein abschließendes <a> zu ParagraphBlock, alles andere zu ParagraphBlock. align, color und font-size eines Absatzes landen in seinem Markup: text-align auf jedem <p>, Farbe und Größe auf einem <span> darin (ParagraphBlock hat dafür keine Felder). Eine Ausrichtung an der Startkante (left, in einem RTL-Template right) und eine Größe von 14px entfallen. |
mj-image | ImageBlock | src, alt, href, width (siehe Bildbreite), Padding, border-radius, border (siehe Rahmen und Ecken). fluid-on-mobile bleibt bei einem Bild erhalten, das seine Spalte füllt; bei einem schmaleren ist es approximated, weil das Bild auf dem Smartphone seine Breite behält. |
mj-button | ButtonBlock | href, background-color, color, font-size, font-family, Padding, width (siehe Button-Breite), border-radius, border (siehe Rahmen und Ecken). |
mj-divider | DividerBlock | border-color, border-width, width (siehe Divider-Breite), Padding. |
mj-spacer | SpacerBlock | height. |
mj-social (mit mj-social-element) | SocialIconsBlock | Jedes mj-social-element → ein SocialIcon-Eintrag. Die Plattform kommt aus name, dem src-Dateinamen (Pack-Suffixe wie -round-outlined werden abgetrennt) oder alt. |
mj-navbar (mit mj-navbar-link) | MenuBlock | Jeder Link → MenuItemData. |
mj-table | TableBlock | Bildet <tr>/<td>/<th>-Zeilen und -Zellen auf Templaticals Tabellen-Daten ab; eine führende <th>-Zeile setzt hasHeaderRow. |
mj-raw | HtmlBlock | Inneres Markup bleibt wortgetreu erhalten. |
mj-wrapper | SectionBlock.wrapper | Das äußere Band der Section, keine eigene Section. Eine einzelne Section darin fließt in deren wrapper; mehrere teilen sich dasselbe Band und werden als approximated markiert. background-color, background-url (mit Größe, Position und Wiederholung), padding und border-radius werden übernommen; ein border wird verworfen und gemeldet. |
mj-hero, mj-carousel, mj-accordion | HtmlBlock | Wird in einen HTML-Block mit erhaltenem Original-Markup konvertiert. |
mjml | Template-settings | lang → locale; dir → direction, nur wenn es von der Schreibrichtung abweicht, die die Sprache vorgibt. |
mj-head-Inhalte | Template-settings | mj-preview → preheaderText; mj-attributes/mj-font/mj-style setzen Schriftart, Textfarbe und Link-Farbe/-Unterstreichung des Dokuments. mj-title hat keine Entsprechung in den Settings und wird mit einer Warnung verworfen. |
Bildbreite
Eine px-width wird "full", sobald sie den Platz des Bildes erreicht: seine Spalte abzüglich seines eigenen seitlichen Paddings und Rahmens, wo MJML es begrenzt. Eine schmalere px-Breite bleibt px, und ein Bild ohne width ist "full".
Divider-Breite
width am mj-divider | DividerBlock.width | Status |
|---|---|---|
fehlt, oder 100% | "full" | converted |
ein Prozentwert unter 100%, etwa 50% | derselbe Prozentwert auf zwei Nachkommastellen, "50%" | converted |
unter 0% oder über 100% | begrenzt auf "0%" bzw. "full" | approximated |
| px, schmaler als die Linie reichen kann | die px-Zahl | converted |
| px, so breit wie die Linie reichen kann oder breiter | "full" | converted |
px, unter 0 | begrenzt auf 0 | approximated |
| jeder andere Wert | "full" | approximated |
Die Linie reicht über ihre Spalte abzüglich des seitlichen Paddings der Spalte und ihres eigenen; so zeichnet mj-divider 100%. Die Breite einer Spalte ist ihr Anteil an der width von mj-body, gemäß dem Spaltenlayout der Section.
Templatical zentriert jeden Divider. Ein Divider mit Teilbreite, den MJML links oder rechts ausrichtet (align), ist approximated; seine note nennt die Ausrichtung.
Button-Breite
width am mj-button | ButtonBlock.width | Status |
|---|---|---|
| fehlt | keine, der Button passt sich seiner Beschriftung an | converted |
100% | "full" | converted |
px, über 0 | die px-Zahl | converted |
ein Prozentwert unter 100%, etwa 50% | der px-Wert, den er ergibt | approximated |
über 100% | "full" | approximated |
px oder ein Prozentwert, der 0px oder weniger ergibt | keine, der Button passt sich seiner Beschriftung an | approximated |
| jeder andere Wert | keine, der Button passt sich seiner Beschriftung an | approximated |
Ein Prozentwert bemisst sich an der Breite, die der Button füllen kann: seiner Spalte abzüglich des seitlichen Paddings der Spalte und seines eigenen; an dieser Box bemisst mj-button ihn. ButtonBlock.width kennt keine Prozentwerte, daher skaliert der resultierende px-Wert nicht mehr mit der Spalte, und die note des Eintrags weist darauf hin. Eine px-Breite bleibt px, gleich wie breit sie ist, weil Templaticals Renderer "full" als 100% schreibt.
Rahmen und Ecken
mj-section, mj-image und mj-button behalten ihren border-radius und ihren Rahmen; mj-wrapper behält seinen border-radius.
border-radius | Eckenradius | Status |
|---|---|---|
ein bis vier px-Werte, etwa 8px oder 8px 8px 0 0 | dieselben Ecken, eine einzelne Zahl, wenn alle vier gleich sind | converted |
jeder andere Wert, etwa 50%, 1em oder 8px / 4px | eckig | approximated |
border oder border-<side> | Diese Seite | Status |
|---|---|---|
eine px-Breite, solid, dashed oder dotted und eine Farbe | dieselbe | converted |
Stil none oder hidden, kein Stil oder Breite 0 | nicht gezeichnet | converted |
Stil double, groove, ridge, inset oder outset | solid gezeichnet | approximated |
eine Breite, die nicht in px angegeben ist, etwa 2em oder thin, oder keine Breite | nicht gezeichnet | approximated |
Ein border-<side> einer Seite hat wie in MJML Vorrang vor border. Eine fehlende Farbe wird als nicht gesetzt importiert und wie in MJML in der geerbten Textfarbe (currentColor) gezeichnet; dasselbe gilt für eine Farbe, die der Importer nicht lesen kann. Der Wrapper in Templatical hat keinen Rahmen, daher wird ein border an einem mj-wrapper verworfen, und die note seines Eintrags nennt ihn.
Hintergrundbilder
background-url an einer mj-section wird zum backgroundImage der Section, an einem mj-wrapper zum wrapper.backgroundImage des Bands. background-size, background-position (sowie -x / -y) und background-repeat werden mitgenommen, und ein Attribut, das das Element weglässt, übernimmt MJMLs eigenen Standard (auto, top center, repeat) — das Bild erscheint also dort, wo es war.
Ein Wert ohne exaktes Templatical-Schlüsselwort wird auf das nächstliegende abgebildet, und der Eintrag ist approximated mit einer Notiz:
- Größe — eine Größe, die eine Seite füllt (
100%,100% auto,auto 100%oder eine px-Breite mindestens so groß wie die Section), wirdcover; jede andere Länge oder Prozentangabe wirdauto. - Position — eine Prozentangabe rastet auf das nächste von links, Mitte und rechts (oben, Mitte und unten) ein; eine Länge ist ein Versatz vom linken bzw. oberen Rand und wird dort verankert.
- Wiederholung —
repeat-x,repeat-y,roundundspacewerden zurepeat.
mj-column kennt in MJML kein background-url, es gibt also kein Spaltenbild zu importieren. mj-hero wird weiterhin als HTML-Block importiert.
Wo das Mapping verlustbehaftet ist
MJML, das Templaticals eigener Renderer erzeugt hat, durchläuft den Importer ohne Näherungen bei Layout, Styling oder Anzeigebedingungen. Die einzige Lücke betrifft den Blocktyp: Ein VideoBlock und ein HtmlBlock rendern beide als reines MJML ohne Kennzeichnung ihrer Herkunft, sodass beim erneuten Import jeweils ein anderer Blocktyp entsteht (siehe unten). Handgeschriebenes MJML konvertiert für jedes Tag in der Mapping-Tabelle oben sauber; alles, was die Tabelle nicht abdeckt, landet als HtmlBlock mit dem ursprünglichen Markup. Innerhalb dessen, was die Tabelle abdeckt, sind einige Konvertierungen Näherungen statt exakter Treffer:
- Spalten-Geometrie — Templatical unterstützt fünf Spalten-Layouts (
1,2,3,2-1,1-2). MJML erlaubt beliebig viele Spalten in beliebigem Verhältnis, daher wird ein Verhältnis außerhalb dieser fünf auf das nächstliegende Layout aufgelöst, und der Inhalt einer vierten oder weiteren Spalte fließt in die letzte Spalte. - Social-Icon-Größen —
SocialIconsBlockunterstützt drei Größen (24px, 32px, 48px). Eineicon-sizean einemmj-social-elementaußerhalb dieser drei wird auf die nächstliegende aufgelöst. - Überschriften-Ebenen — ein
<h5>oder<h6>innerhalb vonmj-textwird auf Überschriften-Ebene 4 begrenzt, die höchste, die einTitleBlockunterstützt. - Divider-Breite und -Ausrichtung — eine
widthan einemmj-divideraußerhalb von 0–100 % oder unter 0px wird in den gültigen Bereich begrenzt, eine nicht lesbare wird zu"full", und ein links oder rechts ausgerichteter Divider mit Teilbreite wird zentriert. Siehe Divider-Breite. - Button-Breite — eine
widthan einemmj-buttonals Prozentwert außer100%wird zum px-Wert, den sie ergibt, eine über100%wird zu"full", und eine nicht lesbare oder eine, die0pxoder weniger ergibt, lässt den Button sich seiner Beschriftung anpassen. Siehe Button-Breite. - Rahmen und Ecken — ein
border-radius, der nicht in px angegeben ist, wird als eckig importiert, ein Rahmenstil außer solid, dashed oder dotted wird solid gezeichnet, eine Rahmenseite ohne px-Breite bleibt ungezeichnet, und ein Rahmen an einemmj-wrapperwird verworfen. Siehe Rahmen und Ecken. - Hintergrundbilder — ein
background-size,background-positionoderbackground-repeatohne exaktes Templatical-Schlüsselwort wird auf das nächstliegende abgebildet. Siehe Hintergrundbilder. - Video-Blöcke — ein
VideoBlockwird genauso gerendert wie ein verlinkterImageBlock, sodass nichts im MJML ihn als Video kennzeichnet. Beim Import daraus entsteht einImageBlockmit demselben Vorschaubild und Link; der Inhalt bleibt erhalten, der Blocktyp nicht. - HTML-Blöcke — aus demselben Grund rendert der Inhalt eines
HtmlBlockals reinesmj-text-Markup ohne jede Kennzeichnung als HTML. Beim Import daraus entsteht einParagraphBlockmit demselben Markup. - Block-IDs — jeder importierte Block erhält eine neu generierte ID. IDs erscheinen nirgends im gerenderten MJML, daher überlebt nichts, das an einer ID hängt — zum Beispiel ein Cloud-Kommentarthread — einen Durchlauf durch Export und Re-Import.
Nicht übertragene Merkmale
mj-include— der Importer liest einen einzelnen MJML-String ohne Dateisystemzugriff, daher wird ein nicht auflösbares<mj-include>übersprungen (skipped), mit einer Warnung, die daspath-Attribut nennt. Inkludierten Inhalt vor dem Import inlinen.- Custom MJML-Components — ein nicht erkanntes
mj-*-Tag landet automatisch alsHtmlBlockmit dem gerenderten Markup. Implementieren Sie es als Templatical Custom Block, wenn Sie es stattdessen als nativen Block editierbar haben möchten.
Wenn diese Anleitung etwas nicht abdeckt
Eröffnen Sie eine Diskussion mit einem geschwärzten Ausschnitt Ihres MJMLs und was Sie erreichen wollen. Wir nutzen diese Rückmeldungen, um die Abdeckung von @templatical/import-mjml zu verbessern.