Porter des plugins WordPress vers EmDash
Cette skill mappe les concepts WordPress à leurs équivalents EmDash pour le portage de plugins. Pour les détails généraux de création de plugins (structure, definePlugin(), hooks, stockage, interface admin, etc.), consultez la skill creating-plugins.
Approche de migration
- Comprendre le plugin — Ce qu'il fait, pas comment
- Identifier les concepts — Types de contenu, pages admin, hooks, shortcodes
- Mapper vers EmDash — Utiliser les tableaux ci-dessous
- Implémenter en TypeScript — Partant de zéro, pas une portage ligne par ligne. Consultez la skill creating-plugins pour les détails d'implémentation.
- Tester le comportement — Même résultat, implémentation différente
Mappage des concepts
Contenu et données
| WordPress | EmDash | Notes |
|---|---|---|
register_post_type() |
SchemaRegistry.createCollection() |
Via Admin API ou fichier seed |
register_taxonomy() |
Table _emdash_taxonomy_defs |
Hiérarchique ou plate, attachée aux collections |
register_meta() / ACF |
Champs de collection via SchemaRegistry | Tous deviennent des champs de schéma typés |
get_post_meta() |
entry.data.fieldName |
Accès typé direct |
get_option() |
getSiteSetting() / ctx.kv |
Paramètres du site ou KV nommacé par plugin |
WP_Query |
getEmDashCollection() |
Requêtes d'exécution avec filtres |
get_post($id) |
getEmDashEntry(collection, slug) |
Retourne entry ou null |
wp_insert_post() |
POST /_emdash/api/content/{type} |
API REST |
wp_update_post() |
PUT /_emdash/api/content/{type}/{id} |
API REST |
wp_delete_post() |
DELETE /_emdash/api/content/{type}/{id} |
Suppression logicielle |
| Tables personnalisées | Collections de stockage de plugin | ctx.storage.collectionName.put/get/query |
Configuration du site
| WordPress | EmDash | Notes |
|---|---|---|
get_bloginfo('name') |
getSiteSetting('title') |
Depuis la table options avec préfixe site: |
get_option('blogdesc') |
getSiteSetting('tagline') |
API des paramètres du site |
| Personnalisateur de thème | Page admin Paramètres du site | /_emdash/admin/settings |
site_icon |
getSiteSetting('favicon') |
Référence média |
custom_logo |
getSiteSetting('logo') |
Référence média |
Menus de navigation
| WordPress | EmDash | Notes |
|---|---|---|
register_nav_menu() |
Créer un menu via admin ou seed | Table _emdash_menus |
wp_nav_menu() |
getMenu(name) |
Retourne { items: MenuItem[] } |
wp_nav_menu_item |
Table _emdash_menu_items |
Type : custom, page, post, taxonomy |
_menu_item_object_id |
reference_id + reference_collection |
Liens vers entries de contenu |
| Emplacements de menu | Requête par nom dans les templates | Aucun concept d'emplacements — requête directe |
Taxonomies
| WordPress | EmDash | Notes |
|---|---|---|
register_taxonomy() |
Table _emdash_taxonomy_defs |
Définir via admin, seed, ou API |
get_terms() |
getTaxonomyTerms(name) |
Retourne l'arborescence pour hiérarchique |
get_the_terms() |
getEntryTerms(collection, id, name) |
Termes pour entry spécifique |
wp_set_post_terms() |
TaxonomyRepository.setTermsForEntry() |
Remplacer termes pour entry |
| Taxonomie hiérarchique | hierarchical: true dans définition |
Style catégories |
| Taxonomie plate | hierarchical: false |
Style tags |
Widgets et barres latérales
| WordPress | EmDash | Notes |
|---|---|---|
register_sidebar() |
Table _emdash_widget_areas |
Créer via admin ou seed |
dynamic_sidebar() |
getWidgetArea(name) |
Retourne { widgets: Widget[] } |
Classe WP_Widget |
Types de widget : content, menu, component | Simplifié — 3 types seulement |
| Widget texte | type: 'content' + Portable Text |
Widget de texte enrichi |
| Widget Menu Nav | type: 'menu' + menuName |
Référence un menu |
| Widgets personnalisés | type: 'component' + componentId |
Composants enregistrés par plugin |
Interface admin
| WordPress | EmDash | Notes |
|---|---|---|
add_menu_page() |
admin.pages dans definePlugin() |
Configuration du plugin |
add_submenu_page() |
Pages admin imbriquées | Le parent détermine la hiérarchie |
add_settings_section() |
admin.settingsSchema |
Page de paramètres auto-générée |
add_meta_box() |
Groupes de champs dans schéma collection | Configuration UI dans schéma |
wp_enqueue_script() |
Imports ESM dans composants admin | React (approuvé) ou Block Kit (sandboxé) |
| Avis admin | Notifications toast | Via framework interface admin |
Hooks
| WordPress | EmDash | Notes |
|---|---|---|
add_action('init') |
Hook plugin:install |
S'exécute une fois à la première installation |
add_action('save_post') |
Hook content:afterSave |
Filtrer par event.collection |
add_action('before_delete_post') |
Hook content:beforeDelete |
Retourner false pour empêcher |
add_action('wp_head') |
Hook page:metadata / page:fragments |
Les métadonnées sont sandbox-safe ; scripts approuvés seulement |
add_action('rest_api_init') |
definePlugin({ routes }) |
Approuvé uniquement |
add_filter('the_content') |
Composants Portable Text | Renderers de bloc personnalisés |
add_filter('the_title') |
Logique de template | Gérer dans composant Astro |
Sortie frontale
| WordPress | EmDash | Notes |
|---|---|---|
add_shortcode() |
Bloc personnalisé Portable Text | Contenu → bloc. Template → composant. Approuvé seulement. |
register_block_type() |
Bloc PT + componentsEntry |
Données bloc → props composant Astro. Approuvé seulement. |
| Tags de template | Expressions Astro | get_the_title() → {post.data.title} |
| Widgets | Zone widget + composants | Requête avec getWidgetArea() |
Stockage de plugin
| WordPress | EmDash | Notes |
|---|---|---|
get_option('plugin_*') |
ctx.kv.get(key) |
Automatiquement nommacé par plugin |
update_option() |
ctx.kv.set(key, value) |
Stockage KV scoped |
delete_option() |
ctx.kv.delete(key) |
Supprimer une clé simple |
| Tables personnalisées | ctx.storage.collection |
Collections de documents avec index |
| Transients | KV de plugin | Pas de TTL pour l'instant |
Modèles spécifiques au portage
Ces modèles couvrent des concepts spécifiques à WordPress qui n'ont pas de mappage 1:1 direct. Pour les modèles généraux de plugin (définir hooks, stockage, routes, interface admin), consultez la skill creating-plugins.
Shortcodes → Blocs Portable Text
Les shortcodes WordPress ([youtube id="xxx"]) deviennent des types de bloc personnalisé Portable Text. Les données de bloc remplacent les attributs du shortcode, et un composant Astro remplace la fonction de rendu du shortcode. C'est une fonction approuvée uniquement.
// WordPress
add_shortcode('youtube', function($atts) {
return '<iframe src="https://youtube.com/embed/' . $atts['id'] . '"></iframe>';
});
// EmDash — déclaration de type de bloc dans definePlugin()
admin: {
portableTextBlocks: [{
type: "youtube",
label: "YouTube Video",
icon: "video",
fields: [
{ type: "text_input", action_id: "id", label: "YouTube URL" },
{ type: "text_input", action_id: "title", label: "Title" },
],
}],
}
// EmDash — composant Astro pour rendu
// src/astro/YouTube.astro
const { id, title } = Astro.props.node;
const videoId = id?.match(/(?:v=|youtu\.be\/)([^&]+)/)?.[1] ?? id;
// <iframe src={`https://youtube-nocookie.com/embed/${videoId}`} ... />
Options API → KV de plugin
L'API get_option/update_option de WordPress mappe vers le stockage KV du plugin. La différence clé : les options WordPress sont globales, le KV d'EmDash est automatiquement scoped au plugin.
// WordPress
$count = get_option("myplugin_post_count", 0);
update_option("myplugin_post_count", $count + 1);
delete_option("myplugin_temp_data");
// EmDash — pas de préfixe nécessaire, automatiquement scoped
const count = (await ctx.kv.get<number>("post-count")) ?? 0;
await ctx.kv.set("post-count", count + 1);
await ctx.kv.delete("temp-data");
Tables de base de données personnalisées → Collections de stockage
Les plugins WordPress qui créent des tables personnalisées avec $wpdb->query("CREATE TABLE ...") doivent utiliser les collections de stockage EmDash à la place. Aucune migration nécessaire — déclarez le schéma dans definePlugin() et il est automatiquement provisionné.
// WordPress
$wpdb->insert($table, ['form_id' => $id, 'data' => json_encode($data), 'created_at' => current_time('mysql')]);
$results = $wpdb->get_results("SELECT * FROM $table WHERE form_id = '$id' ORDER BY created_at DESC LIMIT 50");
// EmDash — déclaré dans definePlugin()
storage: {
submissions: {
indexes: ["formId", "createdAt", ["formId", "createdAt"]],
},
},
// Dans un hook ou gestionnaire de route
await ctx.storage.submissions!.put(entryId, { formId, data, createdAt: new Date().toISOString() });
const result = await ctx.storage.submissions!.query({
where: { formId },
orderBy: { createdAt: "desc" },
limit: 50,
});
Ensemencement de données (remplace contenu de démarrage, configuration de thème)
Les plugins WordPress qui appellent wp_insert_term(), register_nav_menu(), ou insèrent du contenu par défaut à l'activation doivent utiliser un fichier seed :
{
"version": "1",
"settings": { "title": "My Site", "tagline": "Welcome" },
"taxonomies": [
{
"name": "category",
"label": "Categories",
"hierarchical": true,
"collections": ["posts"],
"terms": [
{ "slug": "news", "label": "News" },
{ "slug": "tutorials", "label": "Tutorials" }
]
}
],
"menus": [
{
"name": "primary",
"label": "Primary Navigation",
"items": [
{ "type": "custom", "label": "Home", "url": "/" },
{ "type": "page", "ref": "about", "collection": "pages" }
]
}
],
"redirects": [
{ "source": "/?p=123", "destination": "/about" },
{ "source": "/old-contact", "destination": "/contact", "type": 301 }
]
}
Enregistrez dans .emdash/seed.json (ou connectez via package.json#emdash.seed) ; le runtime l'applique au prochain démarrage initial quand la base de données est vide.
Utilisez redirects pour les URLs WordPress héritées qui reçoivent encore du trafic après migration.
Interrogation de contenu (remplace WP_Query)
// WordPress
$query = new WP_Query(['post_type' => 'post', 'category_name' => 'tech', 'posts_per_page' => 10]);
// EmDash — dans frontmatter de composant Astro
import { getEmDashCollection, getEntryTerms } from "emdash";
const { entries } = await getEmDashCollection("posts", {
where: { category: "technology" },
limit: 10,
});
Menus (remplace wp_nav_menu)
// WordPress
wp_nav_menu(['theme_location' => 'primary']);
// EmDash — dans composant Astro
import { getMenu } from "emdash";
const nav = await getMenu("primary");
// nav.items[].label, nav.items[].url, nav.items[].children
Zones de widgets (remplace dynamic_sidebar)
// WordPress
dynamic_sidebar("sidebar-1");
// EmDash — dans composant Astro
import { getWidgetArea } from "emdash";
const sidebar = await getWidgetArea("sidebar");
// sidebar.widgets[].type: "content" | "menu" | "component"
Signaux d'alerte (Nécessite décision humaine)
Signaler ces éléments pour révision — ils peuvent nécessiter des décisions architecturales :
- Intégration profonde WP — Hooks dans les fonctionnalités du noyau WP absentes d'EmDash
- Dépendances de thème — Suppose une structure de thème spécifique
- Fonctionnalités multisite — Non supportées
- WP_Query complexe — Les requêtes de métadonnées peuvent nécessiter une implémentation personnalisée
- SQL direct — Le schéma diffère, utiliser Kysely ou stockage de plugin
- Abus de session/transient — Nécessite une couche de cache appropriée
- Vérifications de capacité utilisateur — Réviser mappage de rôles (futur)
- Buffering ob_start() — Modèle PHP, repenser pour le streaming
- Tâches Cron —
wp_schedule_event()n'a pas d'équivalent direct ; nécessite cron de plateforme
Format de sortie
Lors du portage d'un plugin, fournir :
- Analyse — Ce que fait le plugin WP (concepts, pas code)
- Mappage de concepts — Quels concepts WP mappent vers quelles fonctionnalités EmDash
- Code de plugin —
src/descriptor.tsetsrc/index.ts(utiliser skill creating-plugins pour structure) - Données seed — Si le plugin a besoin de taxonomies/menus/widgets par défaut
- Composants Astro — Pour sortie frontale
- Signaux d'alerte — Tout ce qui nécessite une décision humaine