Portare plugin WordPress

In questa pagina

Porta un plugin WordPress separando comportamento dei contenuti, dati memorizzati, route HTTP e interfaccia utente. Poi scegli il formato plugin EmDash che supporta tali requisiti.

Verificare se il plugin appartiene a EmDash

Buoni candidati gestiscono comportamento indipendente dal core WordPress, come validazione contenuti, chiamate API esterne, elaborazione in background, record personalizzati, impostazioni o uno strumento admin.

Non portare un plugin il cui unico scopo è implementare una funzione WordPress già sostituita da Astro o EmDash. Esempi: cache pagine PHP, regole rewrite WordPress, selezione template del tema o modifiche ai global del core.

Per un plugin che definisce solo un custom post type o campi con poco comportamento runtime, crea invece una collection EmDash e un file seed.

Scegliere sandboxed o native

Inizia da Scegliere un formato plugin. I due formati condividono nomi hook e API PluginContext, ma i pacchetti sorgente differiscono.

RequisitoSandboxedNative
Installazione dal registrySìNo
Runtime isolatoSì, con runner configuratoNo
Hook, route, KV, storage strutturatoSìSì
Pagine admin Block KitSìSì
Componenti React admin personalizzatiNoSì
Componenti Astro per il rendering pubblicoNoSì
Fragment di pagina grezziNoSì

Scegli native solo quando il port richiede una superficie build-time o UI esclusiva di native.

Formato pacchetto sandboxed

emdash-plugin init crea il formato sandboxed attuale:

my-plugin/
├── emdash-plugin.jsonc
├── src/
│   └── plugin.ts
├── tests/
│   └── plugin.test.ts
├── package.json
└── tsconfig.json

Il manifest contiene identità, publisher, capabilities, host consentiti e dichiarazioni di storage. La versione proviene normalmente da package.json.

Il manifest seguente dichiara una collection di storage indicizzata e la capability richiesta da content:afterSave:

{
  "$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
  "slug": "read-time",
  "publisher": "did:plc:abc123def456",
  "license": "MIT",
  "author": { "name": "Example Author" },
  "security": { "email": "security@example.com" },
  "capabilities": ["content:read"],
  "allowedHosts": [],
  "storage": {
    "calculations": { "indexes": ["contentId", "updatedAt"] }
  }
}

src/plugin.ts esporta per default un plain object tipizzato con SandboxedPlugin. Gli handler hook sandboxed usano { handler }; gli handler route sandboxed ricevono (routeCtx, ctx):

import type { SandboxedPlugin } from "emdash/plugin";

export default {
  hooks: {
    "content:afterSave": {
      handler: async (event, ctx) => {
        await ctx.storage.calculations.put(event.content.id, {
          contentId: event.content.id,
          updatedAt: new Date().toISOString(),
        });
      },
    },
  },
  routes: {
    recent: {
      handler: async (_routeCtx, ctx) => {
        const result = await ctx.storage.calculations.query({
          orderBy: { updatedAt: "desc" },
          limit: 10,
        });
        return { items: result.items };
      },
    },
  },
} satisfies SandboxedPlugin;

La route è disponibile su /_emdash/api/plugins/read-time/recent. Un campo storage deve essere dichiarato come indice prima che una query possa filtrare o ordinare su di esso.

Compila il pacchetto con emdash-plugin build; non aggiungere un descrittore src/index.ts scritto a mano a questo formato. Leggi Il tuo primo plugin sandboxed per il package.json generato, l’output di build e la registrazione sul sito.

Formato pacchetto native

Un pacchetto native esporta sia una factory di descrittore per astro.config.mjs sia una factory runtime costruita con definePlugin(). Gli entrypoint admin e Astro opzionali sono export di pacchetto separati.

my-native-plugin/
├── src/
│   ├── index.ts
│   ├── admin.tsx
│   └── astro/
│       └── index.ts
├── package.json
└── tsconfig.json

Il seguente entrypoint native ridotto mostra i due pezzi richiesti:

import { definePlugin } from "emdash";
import type { PluginDescriptor } from "emdash";

export interface ReadTimeOptions {
  wordsPerMinute?: number;
}

export function readTimePlugin(options: ReadTimeOptions = {}): PluginDescriptor {
  return {
    id: "read-time",
    version: "0.1.0",
    format: "native",
    entrypoint: "@example/plugin-read-time",
    capabilities: ["content:read"],
    options,
  };
}

export function createPlugin(options: ReadTimeOptions = {}) {
  return definePlugin({
    id: "read-time",
    version: "0.1.0",
    capabilities: ["content:read"],
    admin: {
      settingsSchema: {
        wordsPerMinute: {
          type: "number",
          label: "Words per minute",
          default: options.wordsPerMinute ?? 200,
          min: 1,
        },
      },
    },
    hooks: {
      "content:afterSave": async (event, ctx) => {
        ctx.log.info("Content saved", { id: event.content.id });
      },
    },
  });
}

export default createPlugin;

Gli handler hook native possono essere funzioni direttamente. Gli handler route native ricevono un unico argomento di contesto combinato. Mantieni allineate le copie descrittore e runtime di id, version, capabilities ed entrypoint.

Leggi Il tuo primo plugin native prima di aggiungere pagine admin React, renderer Portable Text o fragment di pagina.

Mappare il comportamento WordPress

Hook

Mappa l’intento di un’action o filter WordPress, non solo il nome:

WordPressEmDash
register_activation_hook()plugin:install alla prima installazione, o plugin:activate all’abilitazione
register_uninstall_hook()plugin:uninstall
wp_insert_post_datacontent:beforeSave
save_postcontent:afterSave
before_delete_postcontent:beforeDelete
deleted_postcontent:afterDelete
wp_handle_upload_prefiltermedia:beforeUpload
add_attachmentmedia:afterUpload

Gli eventi hook hanno forme tipizzate proprie. Consulta la reference hook prima di tradurre gli argomenti dei callback WordPress.

Gli hook contenuto che ricevono dati di entry richiedono content:read. Aggiungi capabilities in base alle API chiamate dal port:

CapabilityAPI resa disponibile
content:readLeggere contenuti e registrare hook contenuto che espongono dati entry
content:writeCreare, aggiornare, pubblicare o eliminare contenuti; implica anche lettura
media:readLeggere record media
media:writeCreare o aggiornare media; implica anche lettura
network:requestUsare ctx.http per gli host elencati in allowedHosts

Opzioni e tabelle personalizzate

Usa ctx.settings per la configurazione utente e ctx.kv per piccoli valori interni. Entrambi gli store sono isolati per plugin. Dichiara le credenziali come campi secret in admin.settingsSchema così EmDash le cifra.

Usa collection ctx.storage.<collection> dichiarate per record plugin interrogabili. La dichiarazione storage appartiene a emdash-plugin.jsonc per sandboxed e a definePlugin() per native. Non aprire il database EmDash né interpolare SQL dal codice plugin.

Il confronto seguente porta un valore opzione senza esporre i global WordPress al nuovo plugin:

WordPress

$api_key = get_option('read_time_api_key', '');
update_option('read_time_api_key', $new_api_key);

EmDash

import type { PluginContext } from "emdash/plugin";

export async function saveApiKey(ctx: PluginContext, newApiKey: string) {
  await ctx.settings.set("apiKey", newApiKey);
}

export async function readApiKey(ctx: PluginContext) {
  return await ctx.settings.get<string>("apiKey") ?? "";
}

Per una tabella personalizzata WordPress, identifica i campi usati per filtrare e ordinare prima di dichiarare lo storage. Il seguente frammento manifest sandboxed indicizza entrambi i campi usati dalla query:

"storage": {
  "jobs": { "indexes": ["status", "createdAt"] }
}

Il runtime può quindi memorizzare e interrogare record job:

await ctx.storage.jobs.put("job-123", {
  status: "pending",
  createdAt: new Date().toISOString(),
});

const pending = await ctx.storage.jobs.query({
  where: { status: "pending" },
  orderBy: { createdAt: "asc" },
  limit: 50,
});

Dichiara sia status sia createdAt come indici nel manifest o nella definizione storage native prima di eseguire quella query.

Endpoint REST

Mappa una route REST WordPress a una route plugin. EmDash la monta su /_emdash/api/plugins/<plugin-id>/<route-name>. Definisci un inputSchema quando la route accetta input e restituisci dati serializzabili JSON.

Impostazioni e pagine admin

I plugin sandboxed descrivono pagine admin con Block Kit e leggono o scrivono valori tramite route e KV. Non spediscono React nell’applicazione admin.

I plugin native possono usare admin.settingsSchema per un form generato. Usa un export adminEntry del pacchetto per pagine React personalizzate, widget, widget di campo o colonne elenco.

File e media

Usa le API media per file caricati o generati. I plugin sandboxed non hanno accesso al filesystem. I plugin native condividono il processo host, ma scrivere file locali al deployment non è una strategia di storage portabile.

Portare il plugin

  1. Inventaria hook, opzioni, tabelle personalizzate, cron job, route REST, pagine admin, blocchi, shortcode e host esterni WordPress.

  2. Rimuovi comportamento che appartiene al routing Astro, al modello contenuti EmDash o alla piattaforma di deployment.

  3. Scegli il formato pacchetto sandboxed o native. Registra ogni capability e host consentito necessario al comportamento rimanente.

  4. Definisci chiavi KV e collection storage strutturato. Aggiungi indici per ogni campo usato in where o orderBy.

  5. Porta un comportamento osservabile alla volta. Testa hook o route con contenuti rappresentativi e casi di errore.

  6. Aggiungi Block Kit o UI admin native solo dopo che route e storage sottostanti funzionano.

  7. Testa installazione, upgrade, attivazione, disattivazione, disinstallazione con e senza cancellazione dati e cambi di capabilities.

Passi successivi