Skip to content

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-mjml
bash
pnpm add @templatical/import-mjml
bash
yarn add @templatical/import-mjml
bash
bun add @templatical/import-mjml

Ohne 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);

Im Playground öffnen

convertMjmlTemplate arbeitet synchron und gibt ein ImportResult zurück mit:

  • content — das konvertierte TemplateContent, bereit für den Editor
  • report — ein Konvertierungsbericht mit dem Status jedes Quellelements (converted, approximated, html-fallback oder skipped)

Den Bericht lesen ​

Jeder Eintrag in report.entries beschreibt ein Quellelement:

StatusBedeutung
convertedJedes Attribut mit einer Templatical-Entsprechung wurde übernommen.
approximatedAuf den richtigen Block abgebildet, aber ein Wert musste auf einen begrenzten Wertebereich angepasst werden — note nennt den ursprünglichen Wert.
html-fallbackKeine Block-Entsprechung vorhanden; das ursprüngliche Markup bleibt in einem HtmlBlock erhalten.
skippedEs 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, ParagraphBlock usw.) 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:

  1. Öffnen Sie Ihre MJML-Quelle im Editor Ihrer Wahl.
  2. Öffnen Sie den Templatical-Editor (oder den Playground) daneben.
  3. Kompilieren Sie Ihr MJML einmal zu HTML und sehen Sie es sich an — das ist Ihr visuelles Ziel.
  4. Ziehen Sie die entsprechenden Templatical-Blöcke hinein (siehe Mapping-Tabelle unten).
  5. Kopieren Sie Textinhalte direkt. Bilder über Ihre Medienbibliothek neu hosten.
  6. 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-TagTemplatical-BlockHinweise
mj-section (mit mj-columns)SectionBlock mit columnsMehrspaltige 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-columnSection-SpalteEine Spalte hält eine Liste verschachtelter Blöcke.
mj-groupSectionBlock.stackOnMobile: falseKein eigener Block — markiert, dass die Spalten der Section auf Mobilgeräten nebeneinander bleiben, statt zu stapeln.
mj-textTitleBlock / TableBlock / MenuBlock / ParagraphBlockStrukturell 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-imageImageBlocksrc, 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-buttonButtonBlockhref, background-color, color, font-size, font-family, Padding, width (siehe Button-Breite), border-radius, border (siehe Rahmen und Ecken).
mj-dividerDividerBlockborder-color, border-width, width (siehe Divider-Breite), Padding.
mj-spacerSpacerBlockheight.
mj-social (mit mj-social-element)SocialIconsBlockJedes 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)MenuBlockJeder Link → MenuItemData.
mj-tableTableBlockBildet <tr>/<td>/<th>-Zeilen und -Zellen auf Templaticals Tabellen-Daten ab; eine führende <th>-Zeile setzt hasHeaderRow.
mj-rawHtmlBlockInneres Markup bleibt wortgetreu erhalten.
mj-wrapperSectionBlock.wrapperDas ä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-accordionHtmlBlockWird in einen HTML-Block mit erhaltenem Original-Markup konvertiert.
mjmlTemplate-settingslang → locale; dir → direction, nur wenn es von der Schreibrichtung abweicht, die die Sprache vorgibt.
mj-head-InhalteTemplate-settingsmj-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-dividerDividerBlock.widthStatus
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 kanndie px-Zahlconverted
px, so breit wie die Linie reichen kann oder breiter"full"converted
px, unter 0begrenzt auf 0approximated
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-buttonButtonBlock.widthStatus
fehltkeine, der Button passt sich seiner Beschriftung anconverted
100%"full"converted
px, über 0die px-Zahlconverted
ein Prozentwert unter 100%, etwa 50%der px-Wert, den er ergibtapproximated
über 100%"full"approximated
px oder ein Prozentwert, der 0px oder weniger ergibtkeine, der Button passt sich seiner Beschriftung anapproximated
jeder andere Wertkeine, der Button passt sich seiner Beschriftung anapproximated

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-radiusEckenradiusStatus
ein bis vier px-Werte, etwa 8px oder 8px 8px 0 0dieselben Ecken, eine einzelne Zahl, wenn alle vier gleich sindconverted
jeder andere Wert, etwa 50%, 1em oder 8px / 4pxeckigapproximated
border oder border-<side>Diese SeiteStatus
eine px-Breite, solid, dashed oder dotted und eine Farbedieselbeconverted
Stil none oder hidden, kein Stil oder Breite 0nicht gezeichnetconverted
Stil double, groove, ridge, inset oder outsetsolid gezeichnetapproximated
eine Breite, die nicht in px angegeben ist, etwa 2em oder thin, oder keine Breitenicht gezeichnetapproximated

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), wird cover; jede andere Länge oder Prozentangabe wird auto.
  • 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, round und space werden zu repeat.

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 — SocialIconsBlock unterstützt drei Größen (24px, 32px, 48px). Eine icon-size an einem mj-social-element außerhalb dieser drei wird auf die nächstliegende aufgelöst.
  • Überschriften-Ebenen — ein <h5> oder <h6> innerhalb von mj-text wird auf Überschriften-Ebene 4 begrenzt, die höchste, die ein TitleBlock unterstützt.
  • Divider-Breite und -Ausrichtung — eine width an einem mj-divider auß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 width an einem mj-button als Prozentwert außer 100% wird zum px-Wert, den sie ergibt, eine über 100% wird zu "full", und eine nicht lesbare oder eine, die 0px oder 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 einem mj-wrapper wird verworfen. Siehe Rahmen und Ecken.
  • Hintergrundbilder — ein background-size, background-position oder background-repeat ohne exaktes Templatical-Schlüsselwort wird auf das nächstliegende abgebildet. Siehe Hintergrundbilder.
  • Video-Blöcke — ein VideoBlock wird genauso gerendert wie ein verlinkter ImageBlock, sodass nichts im MJML ihn als Video kennzeichnet. Beim Import daraus entsteht ein ImageBlock mit demselben Vorschaubild und Link; der Inhalt bleibt erhalten, der Blocktyp nicht.
  • HTML-Blöcke — aus demselben Grund rendert der Inhalt eines HtmlBlock als reines mj-text-Markup ohne jede Kennzeichnung als HTML. Beim Import daraus entsteht ein ParagraphBlock mit 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 das path-Attribut nennt. Inkludierten Inhalt vor dem Import inlinen.
  • Custom MJML-Components — ein nicht erkanntes mj-*-Tag landet automatisch als HtmlBlock mit 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.