PageKit — der selbst gehostete GrapesJS-Website-Builder, als Quellcode. Early Access sichern

GrapesJS + TypeScript

GrapesJS TypeScript: Vollständiger Integrationsleitfaden

Lerne, GrapesJS mit TypeScript zu verwenden: mit typisierten Editor-APIs und Events arbeiten, eigene Komponenten und Plugins erstellen, Storage anbinden und GrapesJS in React-, Next.js-, Vue- oder Angular-Anwendungen integrieren.

Typen im PaketGetypter Editor APIsIndividuelle KomponentenPlugin-EntwicklungHTML/CSS-ExportReact · Vue · Angular · Next.js
Die kurze Antwort

Unterstützt GrapesJS TypeScript?

Ja – und die Typen werden im Paket versandt.

GrapesJS 0.23.6 veröffentlicht seine eigene Deklarationsdatei unter dist/index.d.ts, referenziert aus dem eigenen Typenfeld des Pakets. Die Installation von grapesjs liefert den Editor und seine Typen in einer Abhängigkeit, sodass das Importieren von Editor aus 'grapesjs' ohne zusätzliche Einrichtung und ohne zweites Paket aufgelöst wird.

  • Installiere @types/grapesjs nicht. Dieses Paket ist überhaupt nicht auf npm veröffentlicht – die Installation schlägt mit E404 fehl, anstatt dir leise veraltete Typen zu liefern.
  • Es gibt keine GrapesJS v1.x. Die aktuelle Version ist 0.23.6, lizenziert als BSD-3-Clause. Tutorials, die über eine 1.x-Reihe sprechen, beschreiben eine Version, die nicht existiert.
  • TypeScript 5.0 ist das Minimum. Die Deklarationsdatei verwendet einen Const-Typparameter, sodass 4.9 und darunter ihn nicht parsen können – und melden den Fehler als "Modul 'grapesjs' werden nicht gefunden", was die meisten Leute dazu bringt, nach einem Typpaket zu suchen, das sie nicht benötigen.

Was die Typen abdecken, ist die API-Oberfläche des Editors – die Editor-Instanz, deren Manager, Komponenten, Blöcke, Ereignisse und Projektdaten. Sie beschreiben nicht das eigene Modell Ihres Produkts, und dieser Leitfaden dient hauptsächlich dazu, diese beiden Dinge voneinander zu trennen.

Installiere es und starte
Die Strecke

Was du lernen wirst

Zwölf Schritte, in der Reihenfolge. Jeder führt zu dem Abschnitt, der ihn behandelt, sodass du dort starten kannst, wo dein Projekt tatsächlich steht.

  1. GrapesJS mit TypeScript installierenEine Abhängigkeit, kein Typpaket und der Unterschied zwischen einem Laufzeitimport und einem reinen Typimport.
  2. TypeScript konfigurierenDie vier Compiler-Optionen, die für Editor-Arbeit wichtig sind – und warum strictNullChecks die eigentliche Arbeit macht.
  3. Den Editor typisierengrapesjs.init gibt Editor zurück. Wenn man es als Editor | null hält, verhindert man den häufigsten Absturz.
  4. Arbeit mit KomponentenComponent, ComponentDefinition und die addType-Signatur, die die meisten Tutorials falsch bekommen.
  5. Blöcke typisierenBlockProperties wurde vom Editor getrennt deklariert, sodass ein Block für sich genommen überprüft wird.
  6. Events verwalteneditor.on leitet seine Rückbelehnung aus dem Namen des Events ab – und bleibt für eigene Events offen.
  7. Bau-PluginsPlugin<Options> gibt beide Parameter ein, und das Exportieren der Optionsoberfläche macht es nutzbar.
  8. Erstellen Sie benutzerdefinierte KomponentenEin komponiertes Hero, einmal als Schnittstelle beschrieben und von Editor, API und Renderer wiederverwendet.
  9. Storage und API-Daten typisierenProjectData ist das JSON des Editors. ProjectRecord ist deine Reihe. Sie sind nicht vom gleichen Typ.
  10. Verwende GrapesJS mit ReactDie offizielle Verpackung und die manuelle useEffect-Version – mit der Reinigung, die StrictMode erfordert.
  11. Verwende GrapesJS mit Next.jsWo die Client-Grenze verläuft und warum der Editor nicht darüber erstellt werden kann.
  12. Strukturierung eines ProduktionseditorsEine Naht zwischen deiner Anwendung und dem Editor, sodass keine in die andere übergeht.
Schritt 1

1. GrapesJS mit TypeScript installieren

Es gibt ein Paket. Die Typen kommen mit, sodass es keine zweite Installation und keinen @types Eintrag in deinem devDependencies gibt.

Installationbash
npm install grapesjs

Der Befehl, nicht zu fliehen

@types/grapesjs ist weder veraltet, ersetzt noch optional – es fehlt im npm-Register. Das Ausführen dieses Tutorials gibt einen 404 zurück, und wenn man es in einem älteren Tutorial sieht, ist das ein zuverlässiges Signal, dass der Rest dieses Tutorials auch älter ist als die mitgelieferten Typen.

bash
# Don't. This package is not published — npm returns E404.
npm install --save-dev @types/grapesjs

Laufzeit-Importe vs. Typ-only-Importe

grapesjs selbst ist ein Wert: Sie rufen grapesjs.init() auf. Editor, Component und Block sind Typen: Sie existieren nur während der Kompilierung. Wenn Sie sie mit import type markieren, wird das explizit und garantiert, dass der Import gelöscht wird und nicht in Ihr Bundle gezogen wird – was im Framework-Code am wichtigsten ist, wo ein zufälliger Laufzeit-Import des Editors ihn in ein Server-Rendering ziehen kann.

ts
// Runtime import: the value you actually call.
import grapesjs from 'grapesjs';

// Type-only import: erased at compile time, ships nothing to the bundle.
import type { Editor, Component, Block } from 'grapesjs';

// Editor styles. Without them the canvas renders unstyled.
import 'grapesjs/dist/css/grapes.min.css';
Als Nächstes: Konfigurieren Sie den Compiler
Schritt 2

2. TypeScript konfigurieren

Du brauchst keine spezielle Konfiguration für GrapesJS. Du musst vier Optionen richtig eingestellt haben – der Rest deiner tsconfig kann das bleiben, was dein Projekt bereits nutzt.

tsconfig.jsonjson
{
  "compilerOptions": {
    // GrapesJS's bundled .d.ts uses const type parameters, a TypeScript 5.0
    // feature. On 4.9 and below the file fails to *parse*, and every import
    // from 'grapesjs' reports "Cannot find module".
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",

    // The editor manipulates real DOM nodes: container elements, iframes,
    // drag events. Without the DOM lib none of that type-checks.
    "lib": ["ES2020", "DOM", "DOM.Iterable"],

    // strict is what makes the typings worth having. In particular
    // strictNullChecks is what forces you to handle "editor not created yet",
    // which is the single most common GrapesJS runtime crash.
    "strict": true,

    "skipLibCheck": true,
    "esModuleInterop": true
  }
}
Ziel und Modul
Alles ab ES2020 und aufwärts. Die Deklarationsdatei verwendet Template-Literal-Typen und Const-Typ-Parameter, sodass die Constraint die TypeScript-Version ist, nicht das Emitt-Ziel.
Bibliothek muss DOM enthalten
Der Editor arbeitet mit realen Elementen, iframes und Drag-Events. Ohne die DOM-Bibliothek kontrolliert grapesjs.init({ container: element }) nicht den Typ, und der Fehler sieht eher wie ein GrapesJS-Problem als ein Konfigurationsproblem aus.
streng – speziell strictNullChecks
Das ist die Option, die sich ihren Platz verdient. Sie zwingt dich, das Fenster zu verwalten, in dem der Editor noch nicht existiert: vor Init, nach Destroy und beim ersten Rendering einer Ref. Diese Lücke ist die mit Abstand häufigste Ursache für Laufzeitfehler bei Framework-Integrationen.
moduleResolution
bundler für Vite, Next.js und die meisten modernen Setups; node16 oder nodenext, wenn du mit dem eigenen Algorithmus von Node auflösst. Beide finden das Typfeld des Pakets.
Schritt 3

3. Verständnis von GrapesJS-Typen

Das sind die Namen, die Sie tatsächlich importieren werden. Jeder wurde mit der Deklarationsdatei von grapesjs 0.23.6 überprüft, anstatt von der Dokumentationsseite kopiert zu werden, die JavaScript API beschreibt und nicht immer die gleichen Namen verwendet.

TypRepräsentiertGebräuchliche Verwendung
EditorKlasseDie Editor-InstanzAlles: Lebenszyklus, Manager, Export, Events
EditorConfigInterfaceDas Objekt wurde an init weitergegebenAufbau einer Konfiguration abseits des Aufrufstandorts
ComponentKlasseEin Knoten im Canvas-BaumLesen und Aktualisieren eines ausgewählten Elements
ComponentDefinitionInterfaceEine deklarierte Komponente, keine InstanzKinder einer Komponente; Inhalt eines Blocks
ComponentPropertiesInterfaceDie Modellfelder einer KomponenteDie Defaults typisieren, die du an addType übergibst
AddComponentTypeOptionsInterfaceDas Argument addType nimmt tatsächlichRegistrierung eines benutzerdefinierten Komponententyps
BlockKlasseEin Block im Block ManagerDer Rücklaufwert von Blocks.add
BlockPropertiesInterfaceErklärung eines BlocksBlöcke in einem eigenen Modul deklarieren
TraitKlasseEin Feld im EinstellungsfeldBenutzerdefinierte Eigenschaftstypen und Eigenschaftsbetreuer
ProjectDataInterfaceDer Editor hat JSON gespeichertSpeicher, Laden und Speichern; Ihre Datenbankspalte
Plugin<T>InterfaceEine Plugin-Funktion mit typisierten OptionenEin Plugin typisieren, das du schreibst oder verwendest
PluginOptionsTypaliasDie Einschränkung der Optionen eines PluginsGenerische Plugin-Helfer und Wrapper

Alle diese sind mit Namen importierbar: import type { Editor, Component, BlockProperties } von 'grapesjs'. Nur grapesjs selbst benötigt einen Laufzeit-Import.

Drei Namen, die man nicht importieren kann

Die Manager-Klassen werden in der Datei deklariert, aber nie exportiert, sodass das Importieren nach Namen mit TS2614 fehlschlägt – ein verwirrender Fehler, da die Klasse offensichtlich existiert, wenn man danach sucht. Indexiere stattdessen in Editor: Der Rückgabetyp des Getters ist dieselbe Klasse, und der Alias ist versionsübergreifend stabil.

BlockManager
Stattdessen verwendenEditor['Blocks']
StorageManager
Stattdessen verwendenEditor['Storage']
ComponentManager
Stattdessen verwendenEditor['Components']
Schritt 4

4. Den GrapesJS Editor typisieren

grapesjs.init() gibt einen Editor zurück. Das Annotieren der Variablen ist optional – die Schlussfolgerung macht es bereits richtig – aber die Benennung des Typs ermöglicht es dir, den Editor über Modulgrenzen hinweg zu führen, ohne ihn auf irgendwelche zu erweitern.

src/editor/createEditor.tsts
import grapesjs from 'grapesjs';
import type { Editor } from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';

const editor: Editor = grapesjs.init({
  container: '#gjs',
  height: '100vh',
  storageManager: false,
});

// Both return values are typed, and they are not the same shape:
// getHtml() always returns a string, getCss() can return undefined.
const html: string = editor.getHtml();
const css: string | undefined = editor.getCss();
  • Autovervollständigung folgt der Instanz: Editor. listet jeden Manager auf, und jeder Manager listet seine eigenen Methoden mit seinen echten Signaturen auf.
  • getHtml() gibt String zurück; getCss() gibt String | undefiniert zurück. Der Unterschied ist real, und der strenge Modus lässt dich damit umgehen.
  • destroy() ist Teil des API, kein Zusatz. Die Framework-Bereinigung hängt davon ab.

Nullable Referenzen, bei denen der Wert ist

In einem Framework existiert der Editor beim ersten Rendern noch nicht und darf nach dem Unmount nicht mehr existieren. Die Variable, die ihn hält, als Editor | null zu typisieren zwingt den Compiler, an jeder Aufrufstelle nach beiden Momenten zu fragen. Sie als Editor zu typisieren und das per Assertion wegzuräumen verschiebt diese Frage in die Produktion.

ts
import type { Editor } from 'grapesjs';

// Not `let editor: Editor` — before init there is no editor, and the type
// should say so. Every call site is then forced to handle the empty case.
let editor: Editor | null = null;

export function exportHtml(): string {
  if (!editor) throw new Error('Editor is not initialised yet');
  return editor.getHtml(); // narrowed to Editor here
}

export function destroy(): void {
  editor?.destroy();
  editor = null;
}

Erreichen der Manager

Jeder Manager hängt am Editor ab – editor.Blocks, editor.Components, editor.Storage, editor.Commands – und jeder Getter wird typisiert, sodass Autocomplete von der Instanz nach unten funktioniert. Was du nicht tun kannst, ist, die Manager-Klassen nach Namen zu importieren; sie stattdessen über Editor alias.

ts
import type { Editor } from 'grapesjs';

// These names are NOT exported from 'grapesjs' — importing them by name is a
// compile error. Index into Editor instead and you get the same classes.
type BlockManager = Editor['Blocks'];
type StorageManager = Editor['Storage'];
type ComponentManager = Editor['Components'];

export function countBlocks(blocks: BlockManager): number {
  return blocks.getAll().length;
}
Als Nächstes: Reagieren Sie auf das, was der Editor tut
Schritt 5

5. Arbeit mit GrapesJS-Events in TypeScript

editor.on ist generisch über dem Ereignisnamen, sodass die Callback-Signatur aus dem String, den du passierst, abgeleitet wird. In der Praxis bedeutet das, dass du fast nichts annotieren solltest – die Inferenz liefert dir die richtigen Parametertypen, und eine Annotation, die nicht übereinstimmt, ist ein Kompilierungsfehler und kein stilles Fehlmatch.

SRC/Editor/events.tsts
import type { Editor } from 'grapesjs';

export function wireEditorEvents(editor: Editor): void {
  // No annotation needed. `component` is inferred as Component and
  // `options` carries `action`, which tells add from move from clone.
  editor.on('component:add', (component, options) => {
    console.log(component.get('type'), options.action);
  });

  // A different event, a different payload — the callback signature changes
  // with the event name, so a wrong parameter list is a compile error.
  editor.on('component:selected', (component) => {
    console.log(component.getId());
  });

  editor.on('storage:end:store', () => {
    console.log('Project saved');
  });
}

Die Eventfamilien, die Sie nutzen werden

Component-Events
component:add, component:remove, component:update, component:selected, component:mount. Das erste Argument ist das Component; component:add erhält außerdem ein Optionsobjekt, dessen Aktion einen Add von einem Zug eines Klons unterscheidet.
Editor-Lebenszyklus
Laden, sobald der Editor bereit ist, bei jeder Änderung am Projekt aktualisieren, beim Teardown zerstören. Hier hängst du deinen eigenen Speicherindikator oder die Dirty-State-Flagge an.
Speicherereignisse
storage:start:store, storage:end:store, storage:error. Gerade deshalb sind sie nützlich, weil sie um deine eigene Speicherimplementierung herum laufen, sodass ein fehlerhafter Speicherstand im UI statt in der Konsole auftauchen kann.
Block-Events
block:drag:start, block:drag:stop und das eigene Hinzufügen und Entfernen des Block Manager. Praktisch für Analysen darüber, welche Blöcke man tatsächlich angreifen sollte.

Wo die Schlussfolgerung endet

Der Ereignisname ist eine offene String-Union, absichtlich: Plugins definieren ihre eigenen Kanäle, die sich ständig kompilieren müssen. Der Nachteil ist, dass ein Tippfehler im Kern-Ereignisnamen weiterhin gültig ist als TypeScript – der Rückruf fällt einfach auf eine lose Signatur zurück und wird nie ausgelöst. Wenn ein Event Handler auf mysteriöse Weise nichts tut, überprüfen Sie die Rechtschreibung, bevor Sie die API überprüfen.

ts
import type { Editor } from 'grapesjs';

export function wireCustomEvents(editor: Editor): void {
  // Your own events are allowed — the event name is a string union that stays
  // open, so plugins can define their own channels.
  editor.on('my-plugin:published', (...args: unknown[]) => {
    console.log(args);
  });

  // Which is also the trade-off: this typo compiles. The callback simply falls
  // back to (...args: any[]) and never fires.
  // editor.on('component:selcted', (component) => { ... });
}
Als Nächstes: die Komponenten, die diese Ereignisse enthalten
Schritt 6

6. Typsichere GrapesJS Components

Ein Komponententyp registriert neues Verhalten auf der Canvas: ein Tag, sein traits, was es als Kinder akzeptiert, wie es erkannt wird, wenn HTML wieder eingelesen wird. Hier findet die meiste Arbeit in benutzerdefinierten Editoren statt, und dort muss die Grenze zwischen den GrapesJS-Typen und deinen gezogen werden.

Die richtige Unterschrift

editor.Components.addType (Typ, Optionen) nimmt AddComponentTypeOptions – model, view, isComponent, extend – nicht ein ComponentDefinition. ComponentDefinition beschreibt einen deklarierten Knoten innerhalb eines Baums: die Kinder einer Komponente oder den Inhalt eines Blocks. Tutorials, die einen ComponentDefinition an addType weitergeben, zitieren eine ältere Form, und der Fehler, den man daraus erhält, ist nicht offensichtlich.

src/editor/components.tsts
import type { Editor, Component } from 'grapesjs';

// YOUR domain model. GrapesJS knows nothing about it, and that is the point:
// this is the shape your API, your database and your React props agree on.
export interface HeroContent {
  headline: string;
  subheadline?: string;
  ctaLabel: string;
  ctaHref: string;
}

const HERO_DEFAULTS: HeroContent = {
  headline: 'Your headline',
  ctaLabel: 'Get started',
  ctaHref: '#',
};

// addType takes AddComponentTypeOptions — model / view / isComponent — not a
// ComponentDefinition. Tutorials that pass a ComponentDefinition here are
// describing an API that no longer exists.
export function registerHero(editor: Editor): void {
  editor.Components.addType('hero', {
    isComponent: (el) => el.dataset?.gjsType === 'hero',
    model: {
      defaults: {
        tagName: 'section',
        droppable: false,
        attributes: { 'data-gjs-type': 'hero' },
        traits: [
          { type: 'text', name: 'headline', label: 'Headline' },
          { type: 'text', name: 'ctaLabel', label: 'Button label' },
          { type: 'text', name: 'ctaHref', label: 'Button link' },
        ],
        ...HERO_DEFAULTS,
      },
    },
  });
}

// The bridge back to your model. component.get() is intentionally loose —
// this function is where that looseness stops and HeroContent begins.
export function readHero(component: Component): HeroContent {
  return {
    headline: component.get('headline') ?? HERO_DEFAULTS.headline,
    subheadline: component.get('subheadline'),
    ctaLabel: component.get('ctaLabel') ?? HERO_DEFAULTS.ctaLabel,
    ctaHref: component.get('ctaHref') ?? HERO_DEFAULTS.ctaHref,
  };
}
  • Eigenschaften sind das Einstellungspanel. Jeder Eintrag nennt ein Modellfeld, sodass das Panel und deine Benutzeroberfläche im Einklang bleiben.
  • isComponent ist der Weg, wie eine gespeicherte Seite beim Laden erkannt wird. Ohne es verwandelt das Neuladen deinen benutzerdefinierten Abschnitt wieder in eine einfache Div.
  • droppable und draggable sind Booleans oder Selektoren – hier verhindert man, dass jemand einen hero in einen Knopf legt.

Zwei Typensysteme, absichtlich getrennt

GrapesJS-Typen beschreiben den Editor API. Deine eigenen Schnittstellen sollten das Modell deines Produkts beschreiben. Sie zu mischen fühlt sich etwa eine Woche lang effizient an: Dann hat ein Feld, das deine Datenbank braucht, keinen Platz mehr außer einem Komponentenattribut, und die Internen einer Komponente werden Teil deines Schemas. Behalte eine Funktion wie readHero oben als einzige Treffpunkt, an dem sich die beiden begegnen – alles nachgelagert nimmt deine Schnittstelle, nicht ein Component.

Als Nächstes: Stell es auf das Blockregal
Schritt 7

7. GrapesJS Blocks typisieren

Ein Block ist das, was im linken Regal erscheint und was ein Nutzer zieht. Es ist keine Komponente – es ist eine Erklärung, was eingefügt werden soll. Es lohnt sich, die beiden frühzeitig im Gleichgewicht zu halten, da ihre Typen nicht austauschbar sind und die Fehlermeldung dies nicht angibt.

SRC/Editor/blocks.tsts
import type { Editor, Block, BlockProperties } from 'grapesjs';

// BlockProperties is exported, so the block can be declared away from the
// editor and checked on its own — label, category, media, content.
const heroBlock: BlockProperties = {
  label: 'Hero',
  category: 'Sections',
  media: '<svg viewBox="0 0 24 24"><rect width="24" height="24" /></svg>',
  // A block's content can be a component definition rather than an HTML
  // string, which is how a block and a custom component type stay in sync.
  content: { type: 'hero' },
};

export function addHeroBlock(editor: Editor): Block {
  // Blocks.add(id, props) returns the created Block.
  return editor.Blocks.add('hero', heroBlock);
}

Was eine Blockerklärung betrifft

id
Erstes Argument zu Blocks.add, nicht zu einem Feld. Einzigartig pro Editor; das Wiederhinzufügen derselben ID ersetzt den Block.
Label
Was der Nutzer im Regal liest. Das eine Feld hier, das in deine Locale-Dateien gehört.
Kategorie
Gruppiert das Regal. Eine Zeichenkette oder ein Objekt, wenn du es standardmäßig kollabieren möchtest.
Inhalt
Was eingefügt wird: ein HTML-String oder eine Komponentendefinition. Bevorzuge die Definition – sie hält den Block an einen Komponententyp gebunden, statt an einen Ausschnitt des Markups.
Medien
Das Thumbnail, als Inline-SVG. Nichts hindert dich daran, einen <img> zu verwenden, aber Inline-SVG folgt dem Thema des Editors.
Eigenschaften
Angewandt auf das Regalelement selbst – nützlich für Test-IDs und Analyse-Hooks, nicht für das eingefügte Element.

Blocks.add gibt das erstellte Block zurück, sodass Sie es später erfassen und das Regal anpassen können – also durch Neuordnung, das Ausstecken von Blöcken, die der Nutzerplan nicht enthält, oder das Tauschen eines Kategorielabels zur Laufzeit.

Als Nächstes: Paketiere alles als Plugin
Schritt 8

8. Baue einen GrapesJS Plugin mit TypeScript

Ein GrapesJS-Plugin ist eine Funktion, die den Editor und ein Options-Objekt empfängt. Das ist der ganze Vertrag – weshalb Plugins die natürliche Einheit für alles sind, was man über Editoren hinweg wiederverwenden, an ein anderes Team verschicken oder verkaufen möchte.

Eine Form, die Wachstum überdauert

Eine Datei pro Registrierungsart. Der Grund ist nicht die Ordentlichkeit: blocks.ts exportiert BlockProperties-Objekte, die ohne Editor im Umfang typprüfen, sodass sie unit-getestet und wiederverwendet werden können, ohne überhaupt einen Editor zu starten.

my-grapesjs-plugin/ ├── src/ │ ├── index.ts # the Plugin<Options> function, and only that │ ├── types.ts # the exported Options interface │ ├── blocks.ts # BlockProperties, one per block │ ├── components.ts # editor.Components.addType calls │ └── commands.ts # editor.Commands.add calls ├── tsconfig.json ├── package.json # "types": "dist/index.d.ts" └── README.md
SRC/index.tsts
import type { Editor, Plugin } from 'grapesjs';

// Export the options type. A consumer cannot configure your plugin safely if
// the shape of `options` lives only inside your implementation.
export interface SectionsPluginOptions {
  category?: string;
  blockPrefix?: string;
}

// Plugin<T> is (editor: Editor, config: T) => PluginResult. Typing the
// function as Plugin<SectionsPluginOptions> checks both parameters for you.
const sectionsPlugin: Plugin<SectionsPluginOptions> = (editor, options) => {
  // Defaults belong here, not in the type — an optional field plus a
  // destructured default is what makes the call site free to omit them.
  const { category = 'Sections', blockPrefix = 'sec' } = options;

  editor.Blocks.add(`${blockPrefix}-hero`, {
    label: 'Hero',
    category,
    content: { type: 'hero' },
  });

  editor.Commands.add(`${blockPrefix}:reset`, {
    run(ed: Editor) {
      ed.setComponents('');
    },
  });
};

export default sectionsPlugin;

Was der Typ dir kauft

Optionen
Exportiere die Benutzeroberfläche. Ein Verbraucher, der die Form deiner Optionen nicht sehen kann, muss deine Quellcode lesen, um dein Plugin zu konfigurieren.
Standardeinstellungen
Optionale Felder im Typ, Standardwerte destrukturiert im Body. Standardmäßig in den Typ einzufügen, macht stattdessen jedes Feld am Callsite erforderlich.
Der Editor-Parameter
Für Sie von Plugin<T> typisiert. Alles, was Sie darin registrieren – Blöcke, Komponententypen, Befehle – wird mit den echten Manager-Signaturen überprüft.
Registrierung
Blocks, Komponententypen, Befehle und Ereignishandler gehören alle zur gleichen Funktion. Ein Plugin, das zum Aufruf nichts registriert und auf ein Ereignis wartet, ist ebenfalls in Ordnung.

Registrierung

Eine Closure zu übergeben hält deine Optionen an der Aufrufstelle typisiert. Die Alternative – das Plugin in plugins und seine Einstellungen in pluginsOpts aufzulisten – typisiert diese Einstellungen als loses Record, sodass ein falsch geschriebener Schlüssel kompiliert und stillschweigend nichts tut.

src/editor/createEditor.tsts
import grapesjs from 'grapesjs';
import sectionsPlugin, { type SectionsPluginOptions } from './my-grapesjs-plugin';

const options: SectionsPluginOptions = { category: 'Marketing' };

grapesjs.init({
  container: '#gjs',
  // Passing a closure keeps the options typed at the call site. The alternative
  // — plugins: [sectionsPlugin] with pluginsOpts — types options as
  // Record<string, any>, so a misspelled key compiles and silently does nothing.
  plugins: [(editor) => sectionsPlugin(editor, options)],
});
Als Nächstes: Die Komponenten, die ein Plugin ausliefert
Schritt 9

9. Baue benutzerdefinierte Components mit TypeScript

Ein Design-System-Abschnitt ist keine einzelne Komponente – es ist ein kleiner Baum mit einer Form, für die Ihr Produkt bereits einen Namen hat. Diese Form einmal als Schnittstelle zu beschreiben, ist das, was verhindert, dass Editor, API und Renderer auseinanderdriften.

Ein Abschnitt, vier Typen

Hero ← one component type, one interface ├── Heading ← extends 'text' ├── Description ← extends 'text' └── Button ← extends 'link', traits: label + href
SRC/Editor/design-system.tsts
import type { Editor, ComponentDefinition } from 'grapesjs';

// The composed shape, described once. Every layer below — the editor default,
// the API payload, the renderer — is checked against this one interface.
export interface HeroContent {
  headline: string;
  description: string;
  ctaLabel: string;
  ctaHref: string;
}

// ComponentDefinition is what goes *inside* a tree: the children of a
// component, or the `content` of a block. It is not what addType takes.
const heroChildren = (content: HeroContent): ComponentDefinition[] => [
  { type: 'text', tagName: 'h1', content: content.headline },
  { type: 'text', tagName: 'p', content: content.description },
  {
    type: 'link',
    content: content.ctaLabel,
    attributes: { href: content.ctaHref },
  },
];

export function registerDesignSystem(
  editor: Editor,
  defaults: HeroContent
): void {
  editor.Components.addType('hero', {
    model: {
      defaults: {
        tagName: 'section',
        droppable: false,
        // Children are declared, not hand-written as an HTML string, so a
        // renamed field is a compile error rather than a silently stale block.
        components: heroChildren(defaults),
        traits: [
          { type: 'text', name: 'headline', label: 'Headline' },
          { type: 'text', name: 'ctaHref', label: 'Button link' },
        ],
      },
    },
  });
}

Was typisiert werden sollte – und was nicht

Die Inhaltsoberfläche
Deins. Schlagzeile, Beschreibung, Aufruf zum Handeln – die Felder, die ein Marketer ausfüllt, und dein API speichert.
Der Bauteiltyp
GrapesJS's. Einmal bei addType registriert und das Tag, das traits und die Kinder deklariert.
Merkmale
Die Brücke. Jedes Merkmal benennt ein Feld im Modell, daher sollte das Umbenennen eines Feldes in deiner Benutzeroberfläche die Eigenschaftsliste zerstören – und mit den Standardwerten ist das auch so.
Kinder
ComponentDefinition-Objekte statt eines HTML-Strings. Ein String kompiliert, egal was man eingibt; eine Definition wird überprüft.
Eigenschaften
Wo der Data-GJS-Typ lebt, was isComponent beim Nachladen übereinstimmt.

Der Vorteil ist heute nicht weniger Fehler – sondern dass sechs Monate später das Hinzufügen eines Feldes zu HeroContent eine Liste aller Stellen erzeugt, die geändert werden müssen, anstatt im gesamten Code nach dem String 'Headline' zu suchen.

Als Nächstes: Bringen Sie es in Ihre Datenbank ein und aus Ihrer Datenbank
Schritt 10

10. GrapesJS-Storage und API-Daten typisieren

Speicher ist der Ort, an dem sich die beiden Typen von Systemen auf die folgenschwerste Weise treffen, weil dies die Grenze ist, die in Ihrer Datenbank landet. Fehler zu machen ist später teuer; Richtig zu machen sind etwa zwanzig Zeilen.

Zwei Formen, nicht eine

ProjectData ist das eigene JSON des Editors. Seine interne Struktur gehört zu GrapesJS, ändert sich zwischen Versionen und ist nicht etwas, das man manuell migrieren kann. Dein ProjectRecord ist eine Zeile: eine ID, ein Besitzer, ein Name, eine Version, Zeitstempel – plus dieser undurchsichtige Blob in einer Spalte. Speichere es, lade es und lasse das Innere in Ruhe.

SRC/Typen/projects.tsts
import type { ProjectData } from 'grapesjs';

// The editor's own JSON. ProjectData is deliberately open — its internal shape
// is GrapesJS's business and changes between versions, so treat it as opaque:
// store it, load it, never reach into it or migrate it by hand.

// YOUR row. This is the type your API returns and your database stores, and it
// is not a GrapesJS type. Keeping the two apart is what lets you add a column,
// change a version scheme or move providers without touching editor code.
export interface ProjectRecord {
  id: string;
  name: string;
  userId: string;
  version: number;
  updatedAt: string;
  projectData: ProjectData;
}
  1. GrapesJS
  2. Storage API
  3. Application
  4. Database

Der Editor erzeugt Projektdaten. Dein Speicheradapter ist der einzige Code, der beide Seiten berührt.

src/editor/storage.tsts
import grapesjs from 'grapesjs';
import type { Editor, ProjectData } from 'grapesjs';
import type { ProjectRecord } from './types/projects';

export function createEditor(projectId: string): Editor {
  const editor = grapesjs.init({
    container: '#gjs',
    storageManager: {
      // The id of the storage you register below.
      type: 'remote-api',
      autosave: true,
      stepsBeforeSave: 5,
    },
  });

  editor.Storage.add('remote-api', {
    async load(): Promise<ProjectData> {
      const res = await fetch(`/api/projects/${projectId}`);
      // Throwing here is what makes the editor emit storage:error. Returning
      // an empty object instead loses the reader's work without telling them.
      if (!res.ok) throw new Error(`Load failed: ${res.status}`);
      const record = (await res.json()) as ProjectRecord;
      return record.projectData;
    },

    async store(data: ProjectData): Promise<void> {
      const res = await fetch(`/api/projects/${projectId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ projectData: data }),
      });
      if (!res.ok) throw new Error(`Save failed: ${res.status}`);
    },
  });

  editor.on('storage:error', (error) => console.error(error));

  return editor;
}

Was der Adapter bewältigen muss

Last
Geben Sie den ProjectData für das aktuelle Projekt zurück. Eine fehlgeschlagene Antwort ist der Grund, warum der Editor storage:error ausgibt, anstatt lautlos eine leere Canvas zu öffnen.
Laden
Senden Sie die Daten so, wie sie sind. Formen Sie sie nicht beim Aussteigen neu – was auch immer Sie entfernen, der Editor erwartet beim Laden eine Wiederbewahrung.
Autosave
autosave mit stepsBeforeSave-Batches ändert sich. Jeder Tastendruck ist keine Anfrage, und die Nummer gehört dir, um sie einzustellen.
Versionierung
Eine Versionsspalte in deiner Zeile, serverseitig erhöht. Version des Datensatzes, niemals des JSON des Editors.
Mehrfach-Mietverhältnisse
Die Projekt-ID ist eine Variable aus der Closure des Adapters, und die Zugehörigkeit wird serverseitig geprüft. Der Editor kennt keine Benutzer und sollte auch keine bekommen.
Als Nächstes: Setze den Editor in ein Framework ein
Schritt 11

11. GrapesJS mit React und TypeScript

Es gibt einen offiziellen React-Wrapper – @grapesjs/react, MIT-lizenziert, derzeit 2.0.0 – und er liefert eine eigene Deklarationsdatei. Er rendert keine React-Komponenten innerhalb der Canvas; er mountet den Editor und übergibt dir die Instanz.

app/editor/PageEditor.tsxtsx
'use client';

import { useRef } from 'react';
import grapesjs from 'grapesjs';
import type { Editor, ProjectData } from 'grapesjs';
import GjsEditor from '@grapesjs/react';
import 'grapesjs/dist/css/grapes.min.css';

interface PageEditorProps {
  projectId: string;
  onSave: (projectId: string, data: ProjectData) => void;
}

export default function PageEditor({ projectId, onSave }: PageEditorProps) {
  const editorRef = useRef<Editor | null>(null);

  return (
    <GjsEditor
      // Required. The wrapper does not import grapesjs itself — you pass the
      // module (or a CDN URL), which is what lets you control the version.
      grapesjs={grapesjs}
      options={{ height: '100vh', storageManager: false }}
      onEditor={(editor) => {
        editorRef.current = editor;
      }}
      // projectData is typed as ProjectData, so it lines up with the record
      // type your save endpoint expects.
      onUpdate={(projectData) => onSave(projectId, projectData)}
    />
  );
}

Das grapesjs-Requisit ist erforderlich. Der Wrapper importiert absichtlich den Editor selbst nicht, sodass die Version in deinem Bundle die in deinem package.json bleibt – und du kannst sie stattdessen auf einen CDN-Build verweisen.

Oder ohne die Verpackung

Der Wrapper ist eine Bequemlichkeit, keine Voraussetzung. Ein useEffect mit Ref erledigt denselben Zweck in etwa fünfzehn Zeilen und ist auch dann verständlich, wenn man den Wrapper verwendet, weil er die beiden Regeln explizit macht: den Ref bewachen und beim Aufräumen zerstören.

app/editor/PageEditor.tsxtsx
'use client';

import { useEffect, useRef } from 'react';
import grapesjs from 'grapesjs';
import type { Editor } from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';

export default function PageEditor() {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const editorRef = useRef<Editor | null>(null);

  useEffect(() => {
    // strictNullChecks forces this guard, and it is not ceremony: the ref is
    // null on the first render, before React has attached the div.
    if (!containerRef.current) return;

    const editor = grapesjs.init({
      container: containerRef.current,
      height: '100vh',
      storageManager: false,
    });
    editorRef.current = editor;

    // Without destroy(), React 18's development StrictMode double-mount leaves
    // two editors bound to one container.
    return () => {
      editor.destroy();
      editorRef.current = null;
    };
  }, []);

  return <div ref={containerRef} />;
}
  • Die Container-Ref ist beim ersten Rendern null. strictNullChecks lässt dich sagen, was dann passiert.
  • Gib eine Cleanup zurück, die destroy() aufruft. React 18s Entwicklungs-StrictMode mountet zweimal, und ohne sie hast du zwei Editoren in einem Div.
  • Der Peer-Bereich des Wrappers ist React ^18.0.0 || ^19.0.0. Bei React 17 befindet man sich auf dem manuellen Weg.
Für eine vollständige React-Integrationsanleitung siehe GrapesJS + React. Als Nächstes: Dasselbe gilt für eine Servergrenze
Schritt 12

12. GrapesJS mit Next.js und TypeScript

Die ganze Next.js-Frage ist eine einzige Grenze. grapesjs.init benötigt ein echtes DOM-Element, plus Dokument und Fenster; ein Server Component hat keine davon. Der Editor legt also einen Client Component, und alles darüber kann auf dem Server bleiben.

Wo die Linie verläuft

'use client' ist oben in der Komponente, die den Editor erstellt, und nirgendwo höher. Die Seite darüber bleibt ein Server Component: Es wartet auf Params, überprüft die Sitzung, lädt das Projekt und gibt einfache Requisiten weiter. Diese Aufteilung ist es wert, geschützt zu werden, denn wenn man 'use client' um eine Datei nach oben verschiebt, wird das Datenladen leise zu Client-Code.

app/editor/[id]/editor-client.tsxtsx
// app/editor/[id]/editor-client.tsx
'use client';

import { useEffect, useRef } from 'react';
import grapesjs from 'grapesjs';
import type { Editor } from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';

export default function EditorClient({ projectId }: { projectId: string }) {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const editorRef = useRef<Editor | null>(null);

  // grapesjs.init needs a real element, document and window. Calling it in a
  // module body — or in a Server Component — runs it during the server render,
  // where none of those exist. useEffect only runs in the browser, which is
  // the whole requirement.
  useEffect(() => {
    if (!containerRef.current) return;
    const editor = grapesjs.init({
      container: containerRef.current,
      height: '100vh',
    });
    editorRef.current = editor;
    return () => {
      editor.destroy();
      editorRef.current = null;
    };
  }, [projectId]);

  return <div ref={containerRef} />;
}
app/editor/[id]/page.tsxtsx
// app/editor/[id]/page.tsx  — a Server Component, no 'use client'
import EditorClient from './editor-client';

interface PageProps {
  params: Promise<{ id: string }>;
}

export default async function EditorPage({ params }: PageProps) {
  const { id } = await params;

  // Auth, data loading and permissions stay on the server, fully typed.
  // Only the editor itself crosses into the client.
  return <EditorClient projectId={id} />;
}
useEffect, nicht der Modulkörper
Das Importieren von grapesjs auf dem Server ist harmlos – es ist das Aufrufen von init(), das fehlschlägt. useEffect läuft nur im Browser, was die gesamte Voraussetzung ist.
Dynamischer Import ist optional
next/dynamic mit ssr: false ist eine Bundle-Size-Entscheidung, keine Korrektheitsentscheidung, und es ist in einem Server Component nicht verfügbar. Greifen Sie dazu, wenn der Editor nur ein kleiner Teil einer großen Seite ist; überspringen Sie ihn auf einer Route, die nur der Editor ist.
CSS
Importiere grapesjs/dist/css/grapes.min.css aus der Client-Komponente. Ohne sie rendert die Canvas unstyled und sieht eher kaputt als unstylet aus.
Aufräumarbeiten
Dasselbe wie bei React: destroy() bei der Rückkehr des Effekts. Routenübergänge demounten die Komponente, und ein Editor, der seinen Container überlebt, leakt seine Hörer.
Für den vollständigen Next.js-Build siehe den Next.js-Seiten-Builder-Leitfaden. Als Nächstes: Vue und Angular
Schritt 13

13. GrapesJS mit Vue und Angular

Beide funktionieren und folgen der gleichen Form wie die manuelle React-Version: eine Vorlagenreferenz, Initialisierung nach dem Vorhandensein des Elements, Zerstörung beim Zerlegen. Die framework-spezifischen Mechaniken haben hier eigene Leitfäden, nicht eine komprimierte Version.

In beiden Fällen sind die Typen dieselben, die diese Seite verwendet hat: Editor, Component, ProjectData. Das Framework ändert, wo init() genannt wird, nicht das, was es zurückgibt.

Schritt 14

14. TypeScript-Architektur für eine Produktions-GrapesJS Editor

Alles oben liegt in einer Datei. Es passt nicht mehr bei etwa der dritten benutzerdefinierten Komponente, und entscheidend darüber, ob das nächste Jahr angenehm wird, ist, wo du die Naht zwischen deiner Anwendung und dem Editor platzierst.

Eine Struktur, die Bestand hat

src/ ├── editor/ # everything that touches the Editor instance │ ├── createEditor.ts # grapesjs.init, one place, returns Editor │ ├── plugins.ts # Plugin<T> registrations │ ├── components.ts # Components.addType calls │ ├── blocks.ts # BlockProperties definitions │ ├── commands.ts # Commands.add calls │ └── storage.ts # Storage.add, ProjectData in and out │ ├── types/ │ ├── editor.ts # aliases over GrapesJS types you use a lot │ ├── components.ts # HeroContent and friends — YOUR model │ ├── projects.ts # ProjectRecord — your database row │ └── api.ts # request/response shapes │ └── app/ # imports from types/, never from editor/ internals

Eine Naht, erklärt

Anwendungscode sollte nicht direkt von 'grapesjs' importiert werden. Gib ihm ein einziges Modul, das die wenigen Editor-Typen, die dein Produkt wirklich kennt, erneut exportiert, die Manager, die nicht exportiert werden, und die enge Schnittstelle deklariert, auf die dein UI tatsächlich angewiesen ist. Dann nimmt ein Toolbar-Button diese Schnittstelle ein, nicht ein ganzes Editor – und kann nicht einmal versehentlich in die Editor-Internen eindringen.

SRC/Typen/editor.tsts
// src/types/editor.ts — the single seam between your app and the editor.
import type { Editor, ProjectData } from 'grapesjs';

// Re-export what your application is allowed to know about.
export type { Editor, ProjectData };

// Manager classes are not exported by name; alias them here once so no other
// file has to remember that.
export type BlockManager = Editor['Blocks'];
export type StorageManager = Editor['Storage'];

// The surface your UI actually depends on. Application code takes this, not a
// full Editor, so a toolbar button cannot quietly reach into editor internals.
export interface EditorFacade {
  getHtml(): string;
  getCss(): string;
  save(): Promise<void>;
  destroy(): void;
}
Schichten

Die Schichten und in welche Richtung die Abhängigkeiten zeigen

Deine Anwendung oben, deine Typen in der Mitte, der Editor darunter. Abhängigkeiten zeigen nach unten und gehen nie zurück.

  1. Anwendung

    Deins. Weiß nichts über GrapesJS.

    • Routing
    • Authentifizierung
    • Anwendungszustand
    • Dein UI
  2. Typen

    Die Naht. Der einzige Ort, an dem beide Welten benannt sind.

    • Domänenmodell
    • Projektunterlagen
    • API-Verträge
  3. Editor-Schicht

    Deiner. Der einzige Code, der aus dem Editor importiert wird.

    • createEditor
    • Plugins
    • Component-Typen
    • Speicheradapter
  4. GrapesJS

    Der Lektor. Nach dem Paket typisiert.

    • Canvas
    • Trainer
    • Veranstaltungen
Das in das Produkt von jemand anderem einbetten?
Das Gesamtbild

15. TypeScript-Architektur für eine SaaS-Seite Builder

Ein Page-Builder-Produkt ist meist kein Page Builder. GrapesJS deckt ein Feld in dieser Kette ab; der Rest ist eine Anwendung, die du sowieso schreiben wolltest, und das Tippen der Joins zwischen ihnen ist das, worauf dieser Guide hinausgearbeitet hat.

  1. SaaS application
  2. Authentication
  3. GrapesJS editor
  4. Typed components
  5. Typed plugins
  6. Storage API
  7. Database
  8. Publishing

GrapesJS besitzt die Bearbeitungsfläche. Authentifizierung, Speicherung, Mietverhältnisse, Abrechnung und Veröffentlichung gehören Ihnen.

Das Gesamtbild

Wem gehört was

GrapesJS besitzt die Bearbeitungsfläche. Authentifizierung, Speicherung, Mietverhältnisse, Abrechnung und Veröffentlichung gehören Ihnen.

Your product

Du gibst

Alles, worüber der Editor keine Meinung hat.

  • Konten, Sitzungen und Berechtigungen
  • Miet- und Tarifgrenzen
  • Die Datenbank und ihre Migrationen
  • Veröffentlichung, Domains und Hosting
  • Abrechnung und Nutzung
GrapesJS

GrapesJS bietet

Alles innerhalb der Canvas, typisiert durch das Paket.

  • Die Canvas und Drag-and-Drop
  • Blocks, Styles, Layers, traits, Assets
  • HTML- und CSS-Ausgaben
  • Projekt JSON über den Storage Manager
  • Component-Typen, Plugins und Befehle

Der Editor ist ein Bestandteil deines Produkts, kein Ersatz dafür.

Schritt 16

16. Häufige GrapesJS-Fehler bei TypeScript

Elf Fehlschläge, die wiederholt auftreten, die meisten sind spezifisch für dieses Paar und nicht für TypeScript im Allgemeinen. Jeder ist ein Symptom, gegen das du dich abgleichen kannst, und die Änderung, die es behebt.

Installation von @types/grapesjs

Symptom

npm install fails mit E404 oder ein Tutorial sagt dir, es hinzuzufügen, und du gehst davon aus, dass dein Register defekt ist.

Behebung

Entfernen Sie es. Die Typen befinden sich innerhalb von grapesjs selbst, referenziert durch das Typ-Feld des Pakets. Es gibt kein separates Typ-Paket und es gab keines für die aktuelle Leitung.

Den Editor als any typisieren

Symptom

Let Editor: Any, meist hinzugefügt, um einen Fehler während der Einrichtung auszuschalten, und nie entfernt.

Behebung

Editor von 'grapesjs'. One at the root propagiert an jeden Manager, jede Event-Payload und jeden Exportaufruf – du behältst die Kompilierungszeit-Kosten von TypeScript und verlierst den gesamten Nutzen.

Plugin-Optionen ohne Typen lassen

Symptom

Ein Plugin nimmt opts: any oder Record<string, unknown>; Konsumenten raten Feldnamen und Tippfehler bewirken nichts.

Behebung

Deklarieren und exportieren Sie eine Optionsschnittstelle und geben Sie dann die Funktion als Plugin<YourOptions> ein. Beide Parameter werden dann überprüft, auch am Aufrufstandort.

Verwirrung von Block und Component

Symptom

Ein Block zu passieren, wo ein Component erwartet wird, oder einen Block zu stylen und festzustellen, dass er keine Stile hat.

Behebung

Ein Block ist ein Regaleintrag, der beschreibt, was eingefügt werden soll. Eine Komponente ist ein Knoten in der Canvas. Blocks.add nimmt BlockProperties; Components.addType nimmt AddComponentTypeOptions.

ComponentDefinition an addType übergeben

Symptom

Ein Komponententyp registriert sich, verhält sich aber wie ein normaler Div – kein traits, keine Einschränkungen.

Behebung

addType nimmt AddComponentTypeOptions: model, view, isComponent, extend. ComponentDefinition beschreibt einen Knoten innerhalb eines Baums – die Kinder einer Komponente oder des Inhalts eines Blocks.

Angenommen, jedes Ereignis hat dieselbe Nutzlast

Symptom

Ein für component:add geschriebener Handler wird für component:remove wiederverwendet, und das zweite Argument ist undefiniert.

Behebung

Die Nutzlasten unterscheiden sich je nach Ereignis, und die Typen geben das bereits an. Lassen Sie die Inferenz Ihnen die Parameter angeben, anstatt sie von einem anderen Handler zu annotieren.

Koppelung von Datenbankmodellen an Editor-Interns

Symptom

Dein ProjectRecord hat Spalten, die Felder im JSON des Editors spiegeln, und ein GrapesJS-Upgrade bedeutet eine Migration.

Behebung

Behandle ProjectData als undurchsichtig. Eine Spalte enthält es; alles, worauf du suchst – Besitzer, Name, Version, Zeitstempel – befindet sich daneben, nicht darin.

Ignorieren von nullablen Editor-Referenzen

Symptom

"Null-Eigenschaften können nicht lesen" bei einer schnellen Navigation, einem Hot-Reload oder dem ersten Rendering einer Route.

Behebung

Editor | null, und die Null zu übernehmen. Nicht-null Assertions auf einer Ref verschieben das Problem von deinem Terminal auf deinen Fehlertracker.

Vor-Typen-Tutorials folgen

Symptom

Benannte Importe, die nicht aufgelöst werden, Manager-Methoden, die nicht existieren, eine Editor-Version, die nie veröffentlicht wurde.

Behebung

Vergleichen Sie die API mit der Deklarationsdatei in Ihrem node_modules und nicht mit einem Blogbeitrag. Es gibt keine GrapesJS v1.x; die aktuelle Version ist 0.23.6.

Mischen von Versionen über Pakete hinweg

Symptom

Der Editor des Wrappers und dein Editor sehen identisch aus, sind aber strukturell inkompatibel, und der Fehler nennt zwei Wege.

Behebung

Ein grapesjs im Baum. Überprüfe npm ls grapesjs – eine verschachtelte Kopie unter einem Plugin ist die übliche Ursache.

Überbreitende benutzerdefinierte Schnittstellen

Symptom

Eine Schnittstelle, die die Hälfte des Editors neu als API deklariert, damit du es weitergeben kannst und bei jeder Veröffentlichung von den echten Typen abweichst.

Behebung

Alias das, was existiert – Editor['Blocks'] – und deklariere nur die enge Fassade, die dein eigenes UI braucht. Das API des Editors in deinen eigenen Typen zu wiederholen, ist eine Wartung, für die du dich nicht anmelden musst.

Keines davon sind TypeScript-Probleme. Es sind Stellen, an denen das Modell des Editors und das Modell deines Produkts miteinander verwechselt werden, und das Typsystem ist nur das, was die Verwirrung frühzeitig sichtbar macht.

Schritt 17

17. Fehlerbehebung bei GrapesJS TypeScript-Fehlern

Vier Fehlerformen decken fast alles ab. In jedem Fall ist der nützliche Schritt, die Deklarationsdatei in node_modules zu betrachten, anstatt nach einer zu greifen – die Antwort ist dort enthalten, und jede verschiebt nur die Frage.

Kann das Modul 'grapesjs' oder seine entsprechenden Typdeklarationen nicht finden

Du siehst

TS2307 auf der Importzeile, obwohl das Paket eindeutig installiert ist und der Editor zur Laufzeit einwandfrei läuft.

Prüfe

Zuerst deine TypeScript-Version. Unter 5.0 kann die Deklarationsdatei nicht geparst werden und der Fehler wird als fehlendes Modul gemeldet – eine wirklich irreführende Nachricht. Dann moduleResolution: Es muss bundler, node16 oder nodenext sein, nicht klassisch. Das Hinzufügen von @types/grapesjs hilft nicht; dieses Paket existiert nicht.

Das Modul 'grapesjs' hat kein exportiertes Mitglied 'X'

Du siehst

TS2614 auf einem Namen, den du in der Deklarationsdatei sehen kannst, meistens StorageManager oder BlockManager.

Prüfe

Die Manager-Klassen werden deklariert, aber nicht exportiert. Verwenden Sie das indexierte Zugriffsalias — Editor['Storage'], Editor['Blocks'], Editor['Components'] — das sich auf dieselbe Klasse auflöst. Wenn der Name etwas anderes ist, grep node_modules/grapesjs/dist/index.d.ts: Wenn er nicht vorhanden ist, gehört er zu einer älteren Version.

Argument vom Typ ... ist nicht dem Parameter zugeordnet

Du siehst

Ein Ereignishandler, ein addType-Aufruf oder eine Blockdeklaration, die exakt mit einem Tutorial übereinstimmt und trotzdem nicht kompiliert.

Prüfe

Die Signatur, in der Deklarationsdatei. Ereignisse tragen unterschiedliche Payloads pro Name; addType nimmt AddComponentTypeOptions statt ComponentDefinition. Fähre die Methode in deinem Editor mit der Maus – die echte Signatur ist direkt vorhanden, und sie hat meist eine andere Form als der Artikel, von dem du kopiert hast.

Zwei inkompatible Editor-Typen

Du siehst

Ein React- oder Next.js-Build, bei dem die Editor des Wrappers und deine sich weigern, sich zu vereinheitlichen, und die Nachricht zwei node_modules-Pfade nennt.

Prüfe

Doppelte Installationen. npm ls grapesjs zeigt die zweite Kopie an, die üblicherweise von einem Plugin mit einem schmalen Peer-Bereich geladen wird. Deduplizieren oder ausrichten Sie die Versionen; der eigene Peer-Bereich des Wrappers ist ^0.22.5.

Für framework-spezifische Typfehler gehen die React- und Next.js-Anleitungen tiefer als diese Seite.

Schritt 18

18. GrapesJS + TypeScript Kompatibilität

Exakte Versionen, die mit dem Register und der installierten Deklarationsdatei überprüft wurden. Der TypeScript-Floor wurde insbesondere gemessen, indem er mit jeder Version kompiliert wurde und nicht aus einem Changelog abgeleitet wurde.

PaketVerifiziertWas es bedeutet
grapesjs0.23.6Versandt eigene Typen auf dist/index.d.ts. Es existiert kein separates Typenpaket.
typescript>= 5.0Die Etage. 4,9 und darunter können die Deklarationsdatei nicht analysieren und als fehlendes Modul melden.
typescript7.0.2Die aktuelle Version und die Version, mit der die Beispiele dieses Leitfadens kompiliert wurden. Alles ab Version 5.0 funktioniert.
@grapesjs/react2.0.0Der offizielle React-Wrapper, MIT-lizenziert, mit eigenen gebündelten Typen.
react^18.0.0 || ^19.0.0Der React-Peer-Bereich des Wrappers. Auf React 17 initialisieren Sie den Editor manuell mit useEffect.
grapesjs (peer)^0.22.5Der grapesjs-Peer-Bereich des Wrappers – breit genug, dass der aktuelle Kern sie erfüllt.
node>=20.9.0GrapesJS ist eine Browser-Bibliothek und deklariert kein Engine-Feld. Der Boden, den du tatsächlich erreichst, stammt aus deinem Framework; Next.js 16.3.4 verlangt das.

Verifizierte 2026-09-03 mit registry.npmjs.org und node_modules/grapesjs/dist/index.d.ts. Jeder Codebeispiel auf dieser Seite wurde vor der Veröffentlichung im strengen Modus gegen diese Versionen kompiliert.

Es gibt hier kein "funktioniert mit jeder TypeScript-Version", denn das stimmt nicht: 5.0 ist ein harter Untergrund und der darunterliegende Fehlermodus ist verwirrend genug, um genau genannt zu werden.

Schritt 19

19. GrapesJS mit TypeScript-kompatiblen Plugins erweitern

Sobald du den Kern von API verstehst, können Plugins zusätzliche Funktionen bieten, ohne dass du jedes Feature von Grund auf neu bauen musst. Dies sind aktuelle Einträge auf GJS.Market, gruppiert nach dem Teil der typisierten Oberfläche, die jeder berührt.

Eine Sache wird diese Seite dir nicht sagen: Keines dieser Einträge wirbt mit gebündelten TypeScript-Deklarationen, also behandle Typunterstützung als unverifiziert und prüfe die eigene README des Plugins. Was unabhängig davon zutrifft, ist, dass der Editor, den ein Plugin erhält, vom Kernpaket typisiert wird – also wird dein Integrationscode um jedes Plugin herum überprüft, selbst wenn das Plugin selbst nur JavaScript ist.

Schritt 20

20. Baust du dein eigenes Plugin oder verwendest du ein bestehendes?

Die Zeile bezieht sich nicht auf die Schwierigkeit. Es geht darum, ob das Verhalten spezifisch für Ihr Produkt ist – denn das entscheidet, wer es in zwei Jahren warten muss.

AnforderungSelbst bauenVerwende ein Plugin
Verhalten, das auf Ihr Unternehmen zugeschnitten istJa – niemand sonst wird es bauen—
Gemeinsame Editor-Funktionalität—Ja – schon gelöst
Volle Kontrolle über den CodeJaJa, für Open-Source-Plugins
Versuch zur ersten funktionierenden VersionHöherUnterer Ausgangspunkt
Wer hält sieDein TeamPlugin-Autor, plus deine Integration
Wie weit du es ändern kannstSo weit wie du willstKommt auf das Plugin an

In der Praxis sind die meisten Editoren beides: eine Handvoll Plugins für die Teile, die jeder Editor benötigt, und eigene typisierte Komponententypen für die Teile, die das Produkt zu Ihrem machen.

Mach weiter

Lerne GrapesJS weiter.

Wohin als Nächstes, je nachdem, ob du den Editor noch lernst, ihn in ein Framework einbindest oder ein Produkt darum herum baust.

Individuelle Entwicklung

Einen Produktions-GrapesJS Editor bauen?

Wenn der Architekturbereich der eigentliche Standort Ihres Projekts ist, besteht die verbleibende Arbeit meist aus Integration und nicht aus Editor-Funktionen. Das ist es, was wir tun.

  • TypeScript-Plugins
  • Benutzerdefinierte Bauteiltypen
  • React-Integration
  • Next.js-Integration
  • Speicher- und API-Integration
  • SaaS-Editoren
  • White-Label-Editoren
  • Benutzerdefinierter Editor UI
  • Migrationen von älteren Versionen
  • Produktionsarchitektur-Review
Sprich mit einem GrapesJS-Experten
Fragen

Häufig gestellte Fragen

Unterstützt GrapesJS TypeScript?

Ja. GrapesJS 0.23.6 veröffentlicht eine Deklarationsdatei mit dem Paket und referenziert sie aus einem eigenen Typenfeld, sodass das Importieren von Typen sofort nach der Installation des Pakets funktioniert. Die Typen decken die Editor-Instanz, deren Manager, Komponenten, Blöcke, traits, Ereignisse und Projektdaten ab.

Beinhaltet GrapesJS TypeScript-Definitionen?

Ja – bei dist/index.d.ts im grapesjs-Paket. Du kannst es direkt in node_modules lesen, was die zuverlässigste Methode ist, um jede API-Frage auf dieser Seite mit der Version abzugleichen, die du tatsächlich installiert hast.

Brauche ich @types/grapesjs?

Nein, und du kannst es nicht installieren: Das Paket ist nicht auf npm veröffentlicht und die Installation schlägt mit einem 404 fehl. Wenn ein Tutorial dir sagt, dass du es hinzufügen sollst, ist dieses Tutorial älter als die gebündelten Typen und die anderen Ratschläge sind wahrscheinlich ebenfalls veraltet.

Wie installiere ich GrapesJS mit TypeScript?

npm install grapesjs. Das ist die gesamte Installation – eine Abhängigkeit, inklusive Typen. Dann importiere grapesjs aus 'grapesjs' für den Laufzeitwert und import type { Editor } aus 'grapesjs' für die Typen.

Wie typisiere ich den GrapesJS-Editor?

grapesjs.init() gibt ein Editor zurück, also trifft die Schlussfolgerung es bereits richtig. Wichtig ist, die Instanz zu halten: Geben Sie sie als Editor | null in einer Referenz oder einem Klassenfeld ein, weil der Editor vor oder nach der Zerstörung tatsächlich nicht existiert, und strictNullChecks lässt jede Aufrufstelle das übernehmen.

Wie typisiere ich GrapesJS-Komponenten?

Component ist der Canvas-Knoten. ComponentDefinition beschreibt einen deklarierten Knoten innerhalb eines Baums – die Kinder einer Komponente oder des Inhalts eines Blocks. Das Registrieren eines neuen Typs verwendet editor.Components.addType (Typ, Optionen), das AddComponentTypeOptions: model, view, isComponent und extend verwendet.

Wie typisiere ich GrapesJS-Blöcke?

BlockProperties wird exportiert, sodass ein Block in einem eigenen Modul deklariert und ohne Editor im Scope überprüft werden kann. editor.Blocks.add(id, props) nimmt dieses Objekt und gibt das erstellte Block zurück.

Wie gehe ich mit GrapesJS-Ereignissen mit TypeScript um?

editor.on ist generisch über dem Ereignisnamen und leitet daraus die Callback-Signatur ab, daher sollten Sie die Parameter selten annotieren. Beachten Sie, dass der Ereignisname eine offene String-Union ist, sodass Plugins ihre eigenen Ereignisse definieren können – was bedeutet, dass ein Tippfehler im Kernereignisnamen trotzdem kompiliert und einfach nie ausgelöst wird.

Wie erstelle ich ein TypeScript GrapesJS-Plugin?

Ein Plugin ist eine Funktion, die den Editor und ein Options-Objekt umfasst. Exportiere eine Optionsoberfläche und gib die Funktion als Plugin<YourOptions> ein – beide Parameter werden dann überprüft, und die Nutzer können sehen, wie sie konfiguriert werden müssen, ohne deine Quellcode zu lesen.

Kann ich mit TypeScript eigene GrapesJS-Komponenten erstellen?

Ja, und dort zahlen sich die Typen am meisten aus. Beschreiben Sie die Inhaltsform als Ihre eigene Schnittstelle, registrieren Sie den Komponententyp mit addType und lassen Sie die traits-Felder auf dieser Schnittstelle benennen – also die Umbenennung einer Feldoberfläche als Kompilierungsfehler statt als leerer Abschnitt in der Produktion.

Kann ich GrapesJS mit React und TypeScript verwenden?

Ja. @grapesjs/react 2.0.0 ist der offizielle Wrapper, MIT-lizenziert, mit eigenen Typen; er verlangt, dass du grapesjs als Requisit durchgibst. Sein React-Peer-Bereich ist ^18.0.0 || ^19.0.0. Beim React 17, oder wenn du keinen Wrapper möchtest, erledigt ein useEffect mit Ref und destroy()-Cleanup denselben Zweck.

Kann ich GrapesJS mit Next.js und TypeScript verwenden?

Ja. Setze 'use client' auf die Komponente, die den Editor erstellt, und nirgendwo höher, und rufe grapesjs.init innerhalb von useEffect auf – es braucht ein echtes Element, ein echtes Dokument und ein Fenster, von denen während eines Serverrenderings keines existiert. Die Seite darüber kann ein Server Component bleiben und weiterhin Daten auf dem Server laden.

Kann ich GrapesJS mit Vue und TypeScript verwenden?

Ja: eine Vorlagenreferenz, onMounted zum Initialisieren, onBeforeUnmount zum Zerstören. Halte Editor aus ref() oder reactive() fern – das Wrapping macht Vue zu einem Proxy zu einem Objekt, das seine eigenen Internen verwaltet. Es gibt keinen offiziellen Vue-Wrapper.

Kann ich GrapesJS mit Angular und TypeScript verwenden?

Ja: @ViewChild für den Container, ngAfterViewInit zum Initialisieren, ngOnDestroy zum Zerlegen und runOutsideAngular, damit die Ereignisschleife des Editors die Änderungserkennung nicht steuert. Es gibt keinen offiziellen Angular-Wrapper; die Pakete auf npm sind Drittanbieter.

Kann ich GrapesJS mit einem TypeScript-Backend verbinden?

Ja – über den Storage Manager, indem du einen Speicher mit Lade- und Speicherfunktionen registrierst, die deinen API aufrufen. Halte die beiden Typen getrennt: ProjectData ist das JSON des Editors und sollte undurchsichtig gespeichert werden, während dein eigener Datensatz die ID, den Besitzer, den Namen, die Version und die Zeitstempel enthält, auf denen du tatsächlich abfragst.

Wo finde ich TypeScript-kompatible GrapesJS-Plugins?

Der GJS.Market-Katalog listet 100+ GrapesJS-Plugins auf. Beachten Sie, dass einzelne Einträge derzeit keine gebündelten Typdeklarationen bewerben, also überprüfen Sie die README jedes Plugins – aber das Editor-Objekt, das ein Plugin erhält, wird in jedem Fall vom Kernpaket typisiert, sodass Ihr eigener Integrationscode darum herum weiterhin überprüft wird.
Beginnen Sie mit dem Bau

Baue deinen GrapesJS Editor mit TypeScript zusammen

Beginne mit dem typisierten GrapesJS API, baue eigene Komponenten und Plugins, verbinde deine Anwendungsinfrastruktur und erweitere den Editor, wenn dein Produkt mehr Funktionalität benötigt.

Lernen Sie

Starte das Tutorial

Installiere das Paket, typisiere den Editor und hab in wenigen Minuten ein funktionierendes, typisiertes Setup.

Starte das Tutorial
Erweitern

Entdecken Sie Plugins

Speicheradapter, Bauteiltypen und Entwickler-Tools wurden bereits für GrapesJS entwickelt.

Plugins entdecken

TypeScript macht GrapesJS nicht sicherer. Es macht einen individuell angepassten GrapesJS-Editor wartbar, sobald er eine Datei überwachsen ist.