Plugins sind der primäre Erweiterungsmechanismus für docmd. Sie ermöglichen das Einfügen von benutzerdefiniertem HTML, das Ändern der Markdown-Parslogik, das Einschleusen von Build-Zeit-Daten vor dem Template-Rendering und die Automatisierung von Post-Build-Aufgaben. Dieser Leitfaden beschreibt die Plugin-API und Best Practices für die Erstellung wiederverwendbarer Komponenten.
Plugin-Deskriptor
Jedes Plugin sollte einen plugin-Deskriptor exportieren, der seine Identität und Fähigkeiten deklariert. Dies ermöglicht der Engine, Fähigkeitsgrenzen beim Laden zu validieren, zu isolieren und durchzusetzen.
export default {
plugin: {
name: 'my-analytics',
version: '1.0.0',
capabilities: ['head', 'body', 'post-build']
},
generateScripts: (config, opts) => { ... },
onPostBuild: async (ctx) => { ... }
};
Hinweis: Der Deskriptor ist derzeit optional (Soft-Deprecation-Warnung). Er wird ab 0.8.0 erforderlich sein.
Kern-Fähigkeiten
Das capabilities-Array bestimmt, welche Hooks Ihr Plugin verwenden darf.
| Fähigkeit | Erlaubte Hooks | Phase |
|---|---|---|
init |
onConfigResolved |
Init |
markdown |
markdownSetup |
Setup |
head |
generateMetaTags, generateScripts (head) |
Rendering |
body |
generateScripts (body) |
Rendering |
build |
onBeforeParse, onAfterParse, onBeforeRender, onPageReady |
Build |
post-build |
onPostBuild |
Post-Build |
dev |
onDevServerReady |
Dev-Server |
assets |
getAssets |
Ausgabe |
actions |
actions |
Interaktiv |
events |
events |
Interaktiv |
translations |
translations |
i18n |
Legacy-Plugins ohne Deskriptor erhalten vollen Zugriff auf alle Hooks, sodass während der Übergangsphase nichts abbricht.
Plugin-API-Referenz
Ein docmd-Plugin ist ein Standard-JavaScript-Objekt (oder ein Modul, das eines als Standard exportiert), das einen oder mehrere der folgenden Hooks implementiert.
| Hook | Beschreibung |
|---|---|
markdownSetup(md, opts) |
Erweitert die markdown-it-Instanz. Synchron. |
generateMetaTags(config, page, root) |
Fügt <meta>- oder <link>-Tags in den <head> ein. |
generateScripts(config, opts) |
Gibt ein Objekt mit headScriptsHtml und bodyScriptsHtml zurück. |
getAssets(opts) |
Definiert externe Dateien oder CDN-Skripte zum Einbinden. |
onPostBuild(ctx) |
Führt Logik nach der Generierung aller HTML-Dateien aus. |
translations(localeId) |
Gibt ein Objekt mit übersetzten Strings für das angegebene Gebietsschema zurück. |
actions |
Objekt mit benannten Aktions-Handlern für WebSocket-RPC-Aufrufe vom Browser. |
events |
Objekt mit benannten Event-Handlern für Fire-and-Forget-Nachrichten vom Browser. |
Ein lokales Plugin erstellen
Ein Plugin zu erstellen ist so einfach wie das Definieren einer JavaScript-Datei. Zum Beispiel my-plugin.js:
// my-plugin.js
import path from 'path';
export default {
plugin: {
name: 'my-plugin',
version: '1.0.0',
capabilities: ['head', 'post-build']
},
markdownSetup: (md, options) => {
// Beispiel: Regel hinzufügen oder markdown-it-Plugin verwenden
},
generateMetaTags: async (config, page, relativePathToRoot) => {
return `<meta name="x-build-id" content="${config._buildHash}">`;
},
onPostBuild: async ({ config, pages, outputDir, log, options }) => {
log(`Benutzerdefiniertes Plugin: ${pages.length} Seiten überprüft.`);
}
};
Verweisen Sie in Ihrer docmd.config.js über den vollständigen Paketnamen auf Ihr Plugin:
import { defineConfig } from '@docmd/core';
export default defineConfig({
plugins: {
'my-awesome-plugin': {
// Ihre benutzerdefinierten Optionen hier
}
}
});
Hinweis: Kurzformen (z.B.
math,search) sind ausschließlich für offizielle@docmd/plugin-*-Pakete reserviert. Drittanbieter-Plugins müssen immer mit ihrem vollständigen npm-Paketnamen referenziert werden.
Plugin-Isolierung
Jeder Hook-Aufruf ist in eine try/catch-Grenze eingebettet. Ein defektes Plugin kann den Build nicht zum Absturz bringen oder andere Plugins stören. Fehler werden protokolliert und am Ende des Builds in einer Zusammenfassung gesammelt.
Lebenszyklus-Hooks
docmd bietet tiefe Integrations-Hooks, die es Plugins ermöglichen, Konfiguration, Rohquellen und Seitendaten während der gesamten Build-Pipeline zu manipulieren.
| Hook | Beschreibung | Erwarteter Rückgabewert |
|---|---|---|
onConfigResolved(config) |
Liest oder modifiziert die aktive normalisierte Konfiguration direkt nach der Initialisierung. | void oder Promise<void> |
onDevServerReady(server, wss) |
Gibt den rohen Node.js http.Server und WebSocketServer im Entwicklungsmodus frei. |
void oder Promise<void> |
onBeforeParse(src, frontmatter) |
Vorverarbeitet rohe Markdown-Zeichenkettendaten unmittelbar bevor sie zur Analyse übergeben werden. | string oder Promise<string> |
onAfterParse(html, frontmatter) |
Nachverarbeitet den generierten HTML-Code, der das Markdown-Body-Segment darstellt. | string oder Promise<string> |
onBeforeRender(page) |
Wird vor dem Template-Rendering aufgerufen. Empfängt den vollständigen PageContext. Änderungen an frontmatter und html werden in der gerenderten Ausgabe widergespiegelt. |
void oder Promise<void> |
onPageReady(page) |
Greift auf die vollständig zusammengestellten Seitenmetadaten zu, kurz bevor die Seite in die Zieldatei geschrieben wird. | void oder Promise<void> |
onBeforeRender und PageContext
Der onBeforeRender-Hook ist der richtige Ort für Plugins, die Build-Zeit-Daten aus der Quelldatei einschleusen müssen — Datei-Metadaten lesen, benutzerdefinierte Frontmatter-Felder berechnen oder Daten aus externen Quellen laden.
interface PageContext {
sourcePath: string; // Absoluter Pfad zur .md-Quelldatei. Immer gesetzt.
frontmatter: Record<string, any>; // Veränderbar — Änderungen in der Template-Ausgabe widergespiegelt
html: string; // Veränderbar — gerenderter Markdown-Body
localeId?: string;
versionId?: string;
relativePathToRoot?: string;
}
export default {
plugin: {
name: 'my-metadata-plugin',
version: '1.0.0',
capabilities: ['build']
},
onBeforeRender: async (page) => {
// sourcePath ist immer verfügbar — kein Raten oder Pfadkonstruktion nötig
const stats = fs.statSync(page.sourcePath);
page.frontmatter.wordCount = page.html.split(/\s+/).length;
page.frontmatter.fileSize = stats.size;
}
};
Vertiefung: Asset-Einbindung
Der getAssets()-Hook ermöglicht es Ihrem Plugin, clientseitige Logik sicher zu bündeln.
getAssets: (options) => {
return [
{
url: 'https://cdn.example.com/lib.js',
type: 'js',
location: 'head'
},
{
src: path.join(__dirname, 'plugin-init.js'),
dest: 'assets/js/plugin-init.js',
type: 'js',
location: 'body'
}
];
}
Plugins übersetzen (i18n)
Plugins, die clientseitige UI rendern, sollten übersetzbare Strings über den translations(localeId)-Hook bereitstellen.
export default {
plugin: {
name: 'my-plugin',
version: '1.0.0',
capabilities: ['translations', 'body']
},
translations: (localeId) => {
try {
const p = path.join(__dirname, 'i18n', `${localeId}.json`);
return JSON.parse(fs.readFileSync(p, 'utf8'));
} catch { }
try {
const p = path.join(__dirname, 'i18n', 'en.json');
return JSON.parse(fs.readFileSync(p, 'utf8'));
} catch { }
return {};
}
}
WebSocket-RPC-Aktionen
Plugins können Aktions-Handler und Event-Handler registrieren, die auf dem Dev-Server laufen und über die window.docmd-API vom Browser aufrufbar sind.
export default {
plugin: {
name: 'my-live-plugin',
version: '1.0.0',
capabilities: ['actions', 'events']
},
actions: {
'my-plugin:save-note': async (payload, ctx) => {
const content = await ctx.readFile(payload.file);
const updated = content + '\n\n> ' + payload.note;
await ctx.writeFile(payload.file, updated);
return { saved: true };
}
},
events: {
'my-plugin:page-viewed': (data, ctx) => {
console.log(`Seite aufgerufen: ${data.path}`);
}
}
};
Das ctx-Objekt (ActionContext) bietet:
| Methode | Beschreibung |
|---|---|
ctx.readFile(path) |
Liest eine Datei relativ zum Projektstamm. |
ctx.writeFile(path, content) |
Schreibt eine Datei (löst Rebuild + Reload aus). |
ctx.readFileLines(path) |
Liest eine Datei als Zeilenarray. |
ctx.broadcast(event, data) |
Sendet ein Event an alle verbundenen Browser. |
ctx.source |
Quellbearbeitungswerkzeuge für blockbasierte Markdown-Manipulation. |
ctx.projectRoot |
Absoluter Pfad zum Projektstamm. |
ctx.config |
Aktuelle docmd-Seitenkonfiguration. |
Das WebSocket-RPC-System ist nur während docmd dev aktiv. Produktions-Builds enthalten weder den API-Client noch serverseitige Aktionsverarbeitung.
Best Practices
- Fähigkeiten deklarieren: Exportieren Sie immer einen
plugin-Deskriptor mit Ihren deklarierten Fähigkeiten. Ab0.8.0wird dies erforderlich sein. onBeforeRenderfür Dateneinfügung verwenden: Wenn Ihr Plugin die Quelldatei liest oder Frontmatter-Felder berechnet, verwenden SieonBeforeRender— nichtgenerateMetaTags.sourcePathist imPageContextimmer verfügbar.- Async/Await: Verwenden Sie immer
async-Funktionen füronPostBuild,onBeforeRenderund Aktions-Handler. - Zustandslosigkeit: Vermeiden Sie die Beibehaltung von Zustand im Plugin-Objekt, da
docmdPlugins während Entwicklungs-Rebuilds neu initialisieren kann. - Namenskonvention: Für Community-Plugins, stellen Sie
docmd-plugin-voran (z.B.docmd-plugin-analytics). - Aktions-Namensräume: Stellen Sie Ihren Plugin-Namen voran (z.B.
my-plugin:save-note) um Kollisionen zu vermeiden. - Aktionsvalidierung: Definieren Sie immer ein explizites Payload-Schema in Ihren Aktionen.
- Logging: Verwenden Sie den bereitgestellten
log()-Helfer inonPostBuild.
Die docmd-Plugin-API ist LLM-optimal gestaltet. Da die Hooks Standard-JavaScript-Objekte und -Typen ohne versteckte komplexe Klassenhierarchien verwenden, können KI-Agenten fehlerfreie benutzerdefinierte Plugins mit minimaler Anleitung generieren.