Deutsch
React (Vite)
examples/react-vite ist eine Single-Page-App mit Vite und React 19, die den Editor enthält und kein Backend hat. Sie hält das Template in localStorage, speichert gespeicherte Blöcke mit dem createLocalStorageSavedBlocksProvider() des Editors und rendert MJML im Browser mit @templatical/renderer. Sie dient als Ausgangspunkt für ein React-Frontend, dessen Backend nicht JavaScript ist. CI baut die App und führt sie bei jeder Änderung am SDK in einem Browser aus.
Ausführen des Beispiels
Oder kopieren Sie es in ein neues Verzeichnis:
bash
npx degit templatical/sdk/examples/react-vite my-app
cd my-app
npm install
npm run devDie Editor-Komponente
Die Komponente bindet den Editor in einem Effect ein, und ihr Cleanup unmountet ihn – auch einen Editor, der erst fertig lädt, nachdem React StrictMode den Effect aufgeräumt hat. onChange schreibt jede Änderung nach localStorage, und der nächste Mount übergibt sie als content zurück an den Editor. Startet der Editor nicht, zeigt die Werkzeugleiste die Fehlermeldung an. Die Werkzeugleiste zeigt auch Fehler, die der Editor über onError meldet, etwa gespeicherte Blöcke, die er nicht aus localStorage lesen kann, und einen fehlgeschlagenen Export; der nächste Export entfernt sie.
src/email-editor.tsx
tsx
import { useEffect, useRef, useState } from "react";
import {
createLocalStorageSavedBlocksProvider,
init,
type TemplaticalEditor,
} from "@templatical/editor";
import type { TemplateContent } from "@templatical/types";
import "@templatical/editor/style.css";
// Everything here runs in the browser: the template is kept in localStorage,
// and saved blocks use the editor's own localStorage provider. To store them
// on a server instead, use the fetch-based providers from any full-stack
// example (lib/templatical/providers.ts in examples/nextjs).
const CONTENT_KEY = "templatical-example:content";
function storedContent(): TemplateContent | undefined {
try {
const raw = localStorage.getItem(CONTENT_KEY);
return raw ? (JSON.parse(raw) as TemplateContent) : undefined;
} catch {
return undefined;
}
}
const messageOf = (error: unknown) =>
error instanceof Error ? error.message : String(error);
export function EmailEditor() {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<TemplaticalEditor | null>(null);
const [mjml, setMjml] = useState("");
const [problem, setProblem] = useState<string | null>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
let cancelled = false;
let instance: TemplaticalEditor | null = null;
(async () => {
// In development, React StrictMode runs this effect, its cleanup and this
// effect again in one synchronous pass. Two init() calls on one
// container can finish in either order, and each replaces whatever the
// container holds when it finishes, so a cancelled run that finishes
// last leaves the container empty. Yielding once lets that cleanup
// cancel the first run before it calls init().
await Promise.resolve();
if (cancelled) return;
const ed = await init({
container,
content: storedContent(),
onChange(content) {
localStorage.setItem(CONTENT_KEY, JSON.stringify(content));
},
savedBlocks: createLocalStorageSavedBlocksProvider(),
onError: (error) => {
if (!cancelled) setProblem(error.message);
},
});
if (cancelled) {
ed.unmount();
return;
}
instance = ed;
editorRef.current = ed;
})().catch((error: unknown) => {
if (!cancelled) setProblem(messageOf(error));
});
return () => {
cancelled = true;
instance?.unmount();
editorRef.current = null;
};
}, []);
async function exportMjml() {
const ed = editorRef.current;
// No editor yet, or it failed to start: nothing to export, and a start
// failure keeps its alert.
if (!ed) return;
// A new export replaces an earlier failure.
setProblem(null);
try {
setMjml(await ed.toMjml());
} catch (error) {
setProblem(messageOf(error));
}
}
return (
<>
<div className="toolbar">
<button type="button" data-testid="export-mjml" onClick={exportMjml}>
Export MJML
</button>
{problem && <span role="alert">{problem}</span>}
</div>
<div className="workspace">
<div ref={containerRef} className="editor" />
{mjml && (
<pre data-testid="export-output" className="output">
{mjml}
</pre>
)}
</div>
</>
);
}Die Provider
Dieses Beispiel übergibt savedBlocks, erzeugt mit createLocalStorageSavedBlocksProvider(). Um Templates und gespeicherte Blöcke auf einem Server zu halten, übergeben Sie die fetch-basierten Provider aus dem Next.js-Beispiel und implementieren Sie die Routen, die sie aufrufen, in einer beliebigen Sprache.
Wechsel in die Produktion
- Übergeben Sie einen
templates-Provider, damit Templates auf Ihrem Server liegen statt imlocalStorageeines einzelnen Browsers. - Ersetzen Sie
createLocalStorageSavedBlocksProvider()durch einen Provider, der auf Ihrem Server aufsetzt, damit gespeicherte Blöcke dem Benutzer über Browser hinweg folgen. - Kompilieren Sie die Ausgabe von
editor.toMjml()auf Ihrem Server mit dem Paketmjmlzu HTML, oder übergeben Sie einenrender-Provider, damiteditor.toHtml()Ihren Server aufruft. Rendering & Export behandelt beides.