Deutsch
Installation
Aktive Entwicklung
Templatical wird aktiv weiterentwickelt und veröffentlicht häufig neue Versionen. Die öffentliche API stabilisiert sich — wir folgen SemVer, nutzen Changesets für jedes Release und dokumentieren Breaking Changes im Changelog. Pinnen Sie in Produktion eine Version und beobachten Sie die GitHub-Releases, um aktuell zu bleiben.
Feature-Wunsch oder rauer Kante begegnet? Diskussion eröffnen — Feedback formt die Roadmap.
Voraussetzungen
- Moderner Browser -- der Support hängt vom Mount-Modus ab:
- Standardmodus (
shadowDom: true, Shadow DOM) — Chrome 80+, Edge 80+, Firefox 101+, Safari 16.4+. Firefox- und Safari-Mindestversionen sind durch dieadoptedStyleSheets-API bestimmt, auf die der Shadow-Pfad angewiesen ist. - Opt-out-Modus (
shadowDom: false, Light DOM) — Chrome 80+, Edge 80+, Firefox 80+, Safari 14+. Verwenden Sie diesen Modus, wenn Sie ältere Firefox- oder Safari-Versionen unterstützen müssen oder Ihre Integration Light-DOM-Zugriff auf Editor-Interna benötigt. Siehe den Shadow-DOM-Leitfaden für die Kompromisse.
- Standardmodus (
- Container-Element -- muss eine definierte Höhe haben (der Editor füllt seinen Container aus). Im Standardmodus muss es ein Elementtyp sein, der einen Shadow Root hosten kann (z. B.
<div>,<section>,<article>). Siehe Container-Element-Anforderungen. - Kein
transformund kein Stacking-Kontext auf einem Vorfahren des Containers --transform,filter,perspective,will-change,opacityunter1,isolation,containund positionierte Elemente mitz-indexverändern jeweils, wo die Overlays des Editors gezeichnet oder positioniert werden. Das sind reine CSS-Regeln, keine Templatical-spezifischen Einschränkungen; sie betreffen jede Bibliothek, die Overlays mitposition: fixedpositioniert. Was jede Eigenschaft konkret bricht und wie Sie es umgehen, steht unter Den Editor einbetten. - Keine erforderlichen Peer-Dependencies -- Vue, TipTap und alle internen Bibliotheken sind im Editor gebündelt. Sie müssen weder Vue noch eine andere Framework-Runtime installieren, unabhängig davon, welches Framework Ihre App verwendet.
@templatical/qualityist eine optionale Abhängigkeit des Editors und wird standardmäßig mitinstalliert. Sie können es weglassen: Der Editor funktioniert ohne das Paket und blendet den Tab „Probleme“ aus. Es aktiviert das Panel „Probleme“ und die Lint-Badges;@templatical/rendererundpusher-jsbleiben optionale Peers. Siehe Optionale Peers.
Netzwerk-Anfragen
Der Editor sendet keine Anfragen an Templatical. Es gibt keinen Lizenzschlüssel, keine Client-ID, keinen Aktivierungsaufruf, keine Berechtigungsprüfung und keine Telemetrie. Nichts am Editor wird aus der Ferne freigeschaltet oder deaktiviert — eine installierte Kopie funktioniert unbegrenzt weiter.
Genau eine Anfrage an Dritte findet dennoch statt, und Sie sollten sie vor dem Deployment kennen:
| Anfrage | Ausgelöst durch | Wann |
|---|---|---|
https://fonts.bunny.net/css?family=geist:400,500,600 | Ein CSS-@import am Anfang des Editor-Stylesheets | Bei jedem Parsen des Stylesheets, in beiden DOM-Modi |
Geist ist die Standard-UI-Schrift des Editors. Wird die Anfrage zum Laden blockiert oder schlägt sie fehl, funktioniert der Editor normal weiter — der Text fällt lediglich auf die nächste Schriftart im Stack zurück. In zwei Fällen könnte das auffallen:
- Strikte Content Security Policy — eine Richtlinie wie
style-src 'self'blockiert den@import. Ergänzen Siehttps://fonts.bunny.netinstyle-srcundfont-src, oder nehmen Sie die Fallback-Schrift in Kauf. - Air-Gapped- oder Offline-Deployments — die Anfrage schlägt fehl und die Fallback-Schrift wird verwendet.
Damit der Editor nicht auf Geist angewiesen ist, überschreiben Sie das Schrift-Token:
css
.tpl,
#ihr-editor-container {
--tpl-user-font-family: system-ui, sans-serif;
}Der @import bleibt dabei im Stylesheet erhalten, die Anfrage wird also weiterhin versucht. Um sie vollständig zu entfernen, hosten Sie Geist selbst und entfernen den @import in einem Build-Schritt aus Ihrer Kopie von dist/style.css. Die vollständige Schrift-Token-Oberfläche finden Sie unter Theming.
Der Container des Editors
Der Container, den Sie an init() übergeben, unterliegt einigen CSS-Einschränkungen. Ein Vorfahre mit transform, overflow: hidden oder einem eigenen Stacking-Kontext kann die Dialoge des Editors verschieben oder beschneiden.
Was jede Eigenschaft bricht und wie Sie es umgehen, steht unter Den Editor einbetten.
npm
bash
npm install @templatical/editorbash
pnpm add @templatical/editorbash
yarn add @templatical/editorbash
bun add @templatical/editor@templatical/editor ist der visuelle Editor. Um Templates in MJML zu konvertieren, installieren Sie zusätzlich @templatical/renderer:
bash
npm install @templatical/rendererbash
pnpm add @templatical/rendererbash
yarn add @templatical/rendererbash
bun add @templatical/rendererDer Renderer ist optional. Installieren Sie ihn dort, wo Sie MJML-Ausgabe benötigen:
- Im Browser, neben dem Editor – wenn Sie
editor.toMjml()aufrufen, um aus der Sitzung des Nutzers zu exportieren. - In Node.js / auf dem Server – wenn Sie nur gespeichertes Template-JSON haben und es serverseitig in MJML umwandeln möchten. Dafür benötigen Sie den Editor nicht; installieren Sie nur den Renderer.
Wenn Sie editor.toMjml() aufrufen, ohne dass der Renderer installiert ist, wird ein klarer Fehler ausgelöst, der das fehlende Paket benennt.
Paketübersicht
| Paket | Beschreibung | Erforderlich |
|---|---|---|
@templatical/editor | Visueller Drag-and-Drop-Editor und init()-Einstiegspunkt | Ja |
@templatical/types | Gemeinsame TypeScript-Typen, Block-Factory-Funktionen, Type Guards | Nur für Headless-Nutzung außerhalb des Editors; Quality installiert seine eigene Types-Abhängigkeit |
@templatical/core | Framework-agnostische Editor-Logik (State, History) | Nur für Headless-Nutzung außerhalb des Editors |
@templatical/renderer | Rendert Templates zu MJML | Optional – installieren, wo Sie editor.toMjml() (Browser) oder renderToMjml() (Node.js, Server) aufrufen |
@templatical/quality | Template-Linter (Barrierefreiheit, Struktur, Links, Zustellbarkeit, Kompatibilität) für das Panel „Probleme“ des Editors und Headless- / CI-Checks | Optionale Abhängigkeit des Editors, standardmäßig mitinstalliert |
@templatical/media-library | Eigenständiges Medien-SDK (init(), useMediaLibrary, MediaLibraryModal in einer Vue-App). Die Durchsuchen-Oberfläche des Editors ist in @templatical/editor gebündelt. | Optional – nur für eigenständige Nutzung, nicht für init({ media }) oder Clouds Speicher |
@templatical/template-tools | CLI und Bibliothek zum Validieren, Rendern, Bearbeiten, Importieren und Live-Vorschauen von Templates außerhalb des Editors | Optional – per npx ausführen, nichts zu installieren; für CI-Checks, Skripte oder das Erzeugen von Templates ohne gemounteten Editor |
@templatical/ai-kit | KI über Ihr eigenes Backend: createHttpAiProvider für init({ ai }), dazu ein Server-Kit und ein Test-Kit für Ihr Backend. MIT. | Optional – dort installieren, wo Sie KI nutzen (Client und Server) |
@templatical/import-beefree | Konvertiert BeeFree-JSON-Templates in das Templatical-Format | Optional |
@templatical/import-unlayer | Konvertiert Unlayer-JSON-Design-Templates in das Templatical-Format | Optional |
@templatical/import-html | Konvertiert bestehende HTML-E-Mail-Templates (Tabellen-basiert) in das Templatical-Format | Optional |
@templatical/import-mjml | Konvertiert MJML-E-Mail-Templates in das Templatical-Format | Optional |
@templatical/import-topol | Konvertiert Topol-JSON-Templates in das Templatical-Format | Optional |
@templatical/import-stripo | Konvertiert Stripo-Plugin-HTML und kompilierte Datei→HTML-Exporte in das Templatical-Format | Optional |
@templatical/import-chamaileon | Konvertiert Chamaileon-getDocument()-JSON in das Templatical-Format | Optional |
@templatical/import-easy-email-pro | Konvertiert Easy-Email-Pro-Persist-JSON in das Templatical-Format | Optional |
Der Editor bündelt Vue, TipTap, @templatical/core und @templatical/types in seinem JavaScript. Quality benötigt zusätzlich @templatical/types als eigene Laufzeitabhängigkeit.
Optionale Peers
Der Editor lädt optionale Pakete per dynamischem import(), abhängig vom genutzten Feature. Quality wird standardmäßig installiert; Renderer und pusher-js bleiben optionale Peers:
| Peer | Wann geladen | Installieren, wenn Sie |
|---|---|---|
@templatical/renderer | Erster Aufruf von editor.toMjml() | MJML-Export aus dem Browser benötigen |
@templatical/quality | Beim Mounten des Editors (Panel „Probleme“) | Optionale Abhängigkeit des Editors, standardmäßig mitinstalliert |
pusher-js | Cloud-Realtime-Verbindung | initCloud() verwenden |
@templatical/quality ist eine optionale Abhängigkeit des Editors und wird standardmäßig mitinstalliert. Sie können es weglassen: Der Editor funktioniert ohne das Paket und blendet den Tab „Probleme“ aus.
Ohne pusher-js bleibt Cloud-Realtime aus. Ohne Renderer oder Rendering-Provider wirft editor.toMjml() einen klaren Fehler, der das fehlende Paket nennt. Durchsuchen braucht kein Extra-Paket — das Modal ist ein Lazy-Chunk des Editors.
Hinweis zur Bundler-Ausgabe
Der Editor funktioniert mit allen modernen Bundlern out of the box — unabhängig davon, welche optionalen Peers Sie installieren, ist keinerlei Konfiguration auf Consumer-Seite erforderlich. Vite, esbuild und Rolldown behandeln die optionalen dynamischen Imports stillschweigend. Rollup gibt eine Warnung Unresolved dependencies aus, die jeden nicht installierten optionalen Peer nennt, und behandelt ihn als extern. Webpack 5 ist etwas gesprächiger: Es analysiert jeden import()-Aufruf statisch und gibt für jeden nicht installierten optionalen Peer eine harmlose Module not found-Warnung aus. Der Build ist trotzdem erfolgreich und der Editor läuft korrekt — diese Warnungen sind rein kosmetisch.
Wenn Sie eine saubere Webpack-Ausgabe bevorzugen, können Sie die Warnungen optional über ignoreWarnings ausblenden:
js
// webpack.config.js — optional, nur falls die Warnungen stören
module.exports = {
ignoreWarnings: [
{
module: /@templatical[\\/]editor/,
message:
/Can't resolve '(pusher-js|@templatical\/(quality|renderer))'/,
},
],
};Framework-Integration
Templatical wird in jedes beliebige DOM-Element eingebunden. Intern erstellt es seine eigene isolierte Anwendung und funktioniert daher mit jedem Framework – oder ganz ohne Framework.
ts
import { init } from "@templatical/editor";
import "@templatical/editor/style.css";
const editor = await init({
container: "#editor",
onChange(content) {
console.log("Content changed", content);
},
});
// Später, beim Entfernen des Editors:
editor.unmount();tsx
import { useRef, useEffect } from "react";
import { init } from "@templatical/editor";
import "@templatical/editor/style.css";
import type { TemplaticalEditor } from "@templatical/editor";
export function EmailEditor() {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<TemplaticalEditor | null>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
let cancelled = false;
let instance: TemplaticalEditor | null = null;
(async () => {
const ed = await init({
container,
onChange(content) {
console.log("Content changed", content);
},
});
if (cancelled) {
ed.unmount();
return;
}
instance = ed;
editorRef.current = ed;
})();
return () => {
cancelled = true;
instance?.unmount();
editorRef.current = null;
};
}, []);
return <div ref={containerRef} style={{ height: "100vh" }} />;
}vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
import { init } from "@templatical/editor";
import "@templatical/editor/style.css";
import type { TemplaticalEditor } from "@templatical/editor";
const container = ref<HTMLElement>();
let editor: TemplaticalEditor | null = null;
let unmounted = false;
onMounted(async () => {
if (!container.value) return;
const instance = await init({
container: container.value,
onChange(content) {
console.log("Content changed", content);
},
});
if (unmounted) {
instance.unmount();
return;
}
editor = instance;
});
onUnmounted(() => {
unmounted = true;
editor?.unmount();
});
</script>
<template>
<div ref="container" style="height: 100vh" />
</template>svelte
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { init } from '@templatical/editor';
import '@templatical/editor/style.css';
import type { TemplaticalEditor } from '@templatical/editor';
let containerEl: HTMLElement;
let editor: TemplaticalEditor | null = null;
let destroyed = false;
onMount(async () => {
const instance = await init({
container: containerEl,
onChange(content) {
console.log('Content changed', content);
},
});
if (destroyed) {
instance.unmount();
return;
}
editor = instance;
});
onDestroy(() => {
destroyed = true;
editor?.unmount();
});
</script>
<div bind:this={containerEl} style="height: 100vh;" />ts
import {
Component,
ElementRef,
OnDestroy,
OnInit,
ViewChild,
} from "@angular/core";
import { init } from "@templatical/editor";
import "@templatical/editor/style.css";
import type { TemplaticalEditor } from "@templatical/editor";
@Component({
selector: "app-email-editor",
standalone: true,
template: `<div #editorContainer style="height: 100vh"></div>`,
})
export class EmailEditorComponent implements OnInit, OnDestroy {
@ViewChild("editorContainer", { static: true })
containerRef!: ElementRef<HTMLElement>;
private editor: TemplaticalEditor | null = null;
private destroyed = false;
async ngOnInit(): Promise<void> {
const instance = await init({
container: this.containerRef.nativeElement,
onChange(content) {
console.log("Content changed", content);
},
});
if (this.destroyed) {
instance.unmount();
return;
}
this.editor = instance;
}
ngOnDestroy(): void {
this.destroyed = true;
this.editor?.unmount();
}
}Eine Komponente kann ausgebunden werden, während init() noch aussteht, etwa bei einem Routenwechsel oder bei bedingtem Rendern. Dieser Aufruf mountet seinen Editor trotzdem, sobald er auflöst, also erst nach dem Cleanup der Komponente; deshalb setzt jedes Framework-Snippet in seinem Cleanup ein Flag (cancelled, unmounted oder destroyed) und unmountet den Editor, der zu spät ankommt.
Wichtig
Rufen Sie immer unmount() auf, wenn Sie den Editor von der Seite entfernen. Dadurch werden Event-Listener, Timer und DOM-Elemente aufgeräumt. Dies ist besonders wichtig in Single-Page-Anwendungen, bei denen Komponenten während der Navigation ein- und ausgebunden werden.
Lauffähige Beispiel-Apps, in CI bei jeder SDK-Änderung getestet: React (Vite), Next.js, React Router, Nuxt und SvelteKit.
TypeScript-Unterstützung
Alle Pakete werden mit vollständigen TypeScript-Typdefinitionen ausgeliefert. Konfigurationsoptionen, Callback-Payloads, Blocktypen und Instanzmethoden sind vollständig typisiert:
ts
import { init, unmount } from "@templatical/editor";
import type {
TemplaticalEditor,
TemplaticalEditorConfig,
} from "@templatical/editor";
import type {
TemplateContent,
Block,
ThemeOverrides,
FontsConfig,
} from "@templatical/types";Release-Tarballs
Jedes GitHub-Release enthält dieselben Tarballs, die auch an npm gehen — einen pro Paket. Installieren Sie daraus, wenn ein Build die Registry nicht erreichen kann oder wenn Ihre Abhängigkeiten aus URLs stammen müssen, die Sie selbst prüfen.
json
{
"dependencies": {
"@templatical/renderer": "https://github.com/templatical/sdk/releases/download/v<version>/templatical-renderer-<version>.tgz"
}
}<version> ist die Paketversion, das Tag dieselbe mit einem vorangestellten v. Jede veröffentlichte Version hat eines auf der Releases-Seite. Es ist dieselbe Datei, die npm ausgeliefert hätte — das Paket verhält sich also identisch.
Drei Dinge sollten Sie wissen:
Pinnen Sie auch die Templatical-Pakete, die Sie indirekt nutzen. Ein Tarball verweist auf seine Geschwisterpakete nur über eine Versionsnummer, Ihr Paketmanager sucht diese Version also weiterhin in der Registry. Eine Standardinstallation von @templatical/editor installiert @templatical/quality, das von @templatical/types abhängt. @templatical/core, @templatical/renderer, @templatical/media-library und die Importer hängen ebenfalls von @templatical/types ab. Verweisen Sie jedes eingebundene Paket auf seinen versionierten Release-Tarball; pinnen Sie für die Standardinstallation des Editors sowohl Quality als auch Types:
yaml
# pnpm-workspace.yaml
overrides:
'@templatical/quality': https://github.com/templatical/sdk/releases/download/v<version>/templatical-quality-<version>.tgz
'@templatical/types': https://github.com/templatical/sdk/releases/download/v<version>/templatical-types-<version>.tgznpm und Yarn erledigen dasselbe über overrides bzw. resolutions in der package.json.
Fremdabhängigkeiten stammen weiterhin aus einer Registry. @templatical/types, @templatical/renderer, @templatical/import-beefree, @templatical/import-unlayer, @templatical/import-topol, @templatical/import-chamaileon und @templatical/import-easy-email-pro kommen zur Laufzeit ohne weitere Pakete aus. Die übrigen ziehen Pakete nach, die nicht von uns stammen: @templatical/core benötigt @vue/reactivity, @templatical/quality benötigt htmlparser2, @templatical/import-html, @templatical/import-mjml und @templatical/import-stripo benötigen cheerio und domhandler, und @templatical/media-library benötigt @lucide/vue, @vueuse/core und vue-advanced-cropper. Um diese ohne Registry zu installieren, brauchen Sie auch dafür einen Mirror.
Die Quellcode-Archive auf derselben Seite sind kein Ersatz. „Source code (zip)“ und „Source code (tar.gz)“ sind Momentaufnahmen des Repositories, ebenso wie eine github:templatical/sdk-Abhängigkeit. Keines von beiden enthält ein gebautes dist/, und beide verweisen auf Geschwisterpakete als workspace:*, was außerhalb dieses Repositories ins Leere läuft.
CDN
Wenn Sie keinen Paketmanager verwenden möchten, können Sie den Editor direkt über Script-Tags laden:
html
<link
rel="stylesheet"
href="https://unpkg.com/@templatical/editor/dist/cdn/editor.css"
/>
<script type="module">
import { init } from "https://unpkg.com/@templatical/editor/dist/cdn/editor.js";
const editor = await init({
container: "#editor",
});
</script>
<div id="editor" style="height: 100vh;"></div>Der CDN-Build ist vollständig eigenständig – alle Abhängigkeiten sind gebündelt. Schwere Bibliotheken (TipTap, Vue, Pusher usw.) werden per Code-Splitting in separate Chunks aufgeteilt und bei Bedarf nachgeladen.
Eine Version pinnen
Das Beispiel oben ist absichtlich unversioniert: unpkg löst eine URL ohne Versionsangabe bei jeder Anfrage auf die zuletzt veröffentlichte Version auf — praktisch, um den Editor auszuprobieren, bedeutet aber, dass sich der genaue Code, den Ihre Seite lädt, ändern kann, ohne dass sich auf Ihrer Seite etwas geändert hat. Pinnen Sie in Produktion eine exakte Version, und laden Sie diese von jsDelivr statt von unpkg:
html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@templatical/editor@<version>/dist/cdn/editor.css"
/>
<script type="module">
import { init } from "https://cdn.jsdelivr.net/npm/@templatical/editor@<version>/dist/cdn/editor.js";
const editor = await init({
container: "#editor",
});
</script>Der Host ändert sich zusammen mit der URL-Form, nicht nur mit der Versionsnummer. Der CDN-Build wird per Code-Splitting in Chunk-Dateien mit Hash-Namen aufgeteilt, die der Einstiegspunkt bei Bedarf nachlädt — Einstiegspunkt und jeder nachgeladene Chunk müssen sich also auf dieselbe veröffentlichte Version auflösen. Der Redirect von unpkg bei einer unversionierten URL stellt das sicher, aber das Anfordern der Chunks einer bereits gepinnten Version über unpkg hat diese gelegentlich mit dem falschen Content-Type und einem fehlschlagenden CORS-Preflight ausgeliefert. jsDelivr liefert die Chunks einer gepinnten Version zuverlässig aus — pinnen Sie also dort, und belassen Sie die unversionierte Form bei unpkg.