Portar un tema de WordPress

En esta página

Porta un tema de WordPress separando su diseño, sus plantillas de ruta y las funciones gestionadas por la base de datos. El resultado es un proyecto Astro completo con consultas de EmDash y un archivo seed, no un tema PHP cargado por un runtime de temas.

Empieza por el contenido y las URL

Importa contenido representativo antes de convertir las plantillas. Registra los tipos de contenido de WordPress, las taxonomías, las reglas de enlaces permanentes, los menús, las áreas de widgets, los valores del Customizer, los shortcodes y el marcado generado por plugins que usa el sitio.

Elige la nueva URL para cada tipo de contenido. Las rutas de Astro son archivos explícitos, así que no existe un equivalente automático de la jerarquía de plantillas de WordPress.

La plantilla de blog actual usa esta correspondencia:

WordPressRuta de plantilla de EmDash
front-page.php o home.phpsrc/pages/index.astro
single.phpsrc/pages/posts/[slug].astro
archive.phpsrc/pages/posts/index.astro
page.phpsrc/pages/pages/[slug].astro
category.phpsrc/pages/category/[slug].astro
tag.phpsrc/pages/tag/[slug].astro
search.phpsrc/pages/search.astro
404.phpsrc/pages/404.astro
header.php y footer.phpsrc/layouts/Base.astro
template-parts/content.phpsrc/components/PostCard.astro u otro componente

La plantilla inicial, en cambio, usa src/pages/[slug].astro para las páginas. Elige un modelo de rutas y haz que el urlPattern del seed y las redirecciones coincidan con él.

Extrae el diseño

Reúne los archivos fuente que determinan el diseño renderizado:

  • style.css y las hojas de estilo encoladas.
  • theme.json en el caso de un tema de bloques.
  • El marcado de las plantillas y las partes de plantilla.
  • Fuentes, iconos e imágenes con sus licencias.
  • Puntos de ruptura responsivos y comportamiento interactivo.

Copia los tokens de diseño al CSS del proyecto Astro y construye la envoltura de documento compartida en src/layouts/Base.astro. Convierte en componentes las partes de plantilla repetidas antes de desarrollar cada ruta.

No copies los nombres de clase ni el JavaScript generados por WordPress cuando el nuevo componente no necesite su comportamiento. Conserva el diseño y la interacción observables, no los residuos de la implementación.

Convierte las consultas de archivo

WP_Query se convierte en getEmDashCollection(). Filtra y ordena en la consulta para que la base de datos haga el trabajo.

Los siguientes ejemplos renderizan un archivo de las últimas publicaciones:

WordPress

<?php
$posts = new WP_Query([
  'post_type' => 'post',
  'post_status' => 'publish',
  'posts_per_page' => 12,
  'orderby' => 'date',
  'order' => 'DESC',
]);

while ($posts->have_posts()) :
  $posts->the_post();
?>
  <article>
    <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
  </article>
<?php endwhile; wp_reset_postdata(); ?>

EmDash

---
import { getEmDashCollection } from "emdash";

const { entries: posts, error, cacheHint } = await getEmDashCollection("posts", {
  orderBy: { published_at: "desc" },
  limit: 12,
});

if (error) return new Response("Could not load posts", { status: 500 });
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

{posts.map((post) => (
  <article>
    <h2><a href={`/posts/${post.id}`}>{post.data.title}</a></h2>
  </article>
))}

Usa orderBy, no sort ni sortBy. Las claves son nombres de campo almacenados, como published_at, created_at o un campo indexado de la colección.

Convierte las plantillas de una sola entrada

Usa el slug de Astro.params para llamar a getEmDashEntry(). Renderiza el texto enriquecido con PortableText.

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
import Base from "../../layouts/Base.astro";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.rewrite("/404");

const { entry: post, error, cacheHint } = await getEmDashEntry("posts", slug);
if (error) return new Response("Could not load the post", { status: 500 });
if (!post) return Astro.rewrite("/404");
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

<Base title={post.data.title} content={{ collection: "posts", id: post.data.id, slug }}>
  <article>
    <h1 {...post.edit.title}>{post.data.title}</h1>
    <PortableText value={post.data.content} />
  </article>
</Base>

post.id es el identificador de ruta de Astro y normalmente es el slug. post.data.id es el ID de contenido de EmDash almacenado. Usa data.id para los comentarios, los helpers de taxonomías y el contexto de edición visual.

Convierte los archivos de taxonomías

Resuelve un término con el nombre de su taxonomía y usa después ese nombre en el filtro where de la colección. La plantilla de blog actual usa nombres de taxonomía y directorios de ruta en singular:

---
import { decodeSlug, getEmDashCollection, getTerm } from "emdash";

const slug = decodeSlug(Astro.params.slug);
const term = slug ? await getTerm("category", slug, { includeCounts: false }) : null;
if (!term) return Astro.rewrite("/404");

const { entries: posts } = await getEmDashCollection("posts", {
  where: { category: term.slug },
  orderBy: { published_at: "desc" },
});
---

<h1>{term.label}</h1>
{posts.map((post) => <a href={`/posts/${post.id}`}>{post.data.title}</a>)}

Usa getTermsForEntries() con entry.data.id cuando un archivo necesite los términos de muchas entradas. Así se agrupa la consulta de relaciones en lugar de llamar a un helper por entrada dentro de un bucle.

Convierte las funciones dinámicas del tema

Menús

Crea las definiciones de menú en seed/seed.json y consúltalas con getMenu("primary"). Los elementos del menú se pueden reordenar y editar después de la configuración, así que no dupliques la misma navegación como enlaces escritos a mano.

El siguiente componente conserva los elementos de menú anidados y marca la página actual:

---
import { getMenu } from "emdash";

const menu = await getMenu("primary");
---

{menu && (
  <nav aria-label="Primary navigation">
    <ul>
      {menu.items.map((item) => (
        <li>
          <a href={item.url} aria-current={Astro.url.pathname === item.url ? "page" : undefined}>
            {item.label}
          </a>
          {item.children.length > 0 && (
            <ul>
              {item.children.map((child) => (
                <li><a href={child.url}>{child.label}</a></li>
              ))}
            </ul>
          )}
        </li>
      ))}
    </ul>
  </nav>
)}

Áreas de widgets

Define las áreas de widgets en el seed y renderízalas con <WidgetArea name="sidebar" /> de emdash/ui. Usa widgets de componente solo cuando la plantilla registre el ID de componente correspondiente.

---
import { WidgetArea } from "emdash/ui";
---

<main><slot /></main>
<aside aria-label="Related content">
  <WidgetArea name="sidebar" />
</aside>

Identidad del sitio

Lee el título del sitio, el eslogan, el logotipo y el favicon con getSiteSettings(). El texto legal o de diseño estático puede permanecer en la plantilla, pero la identidad gestionada por el administrador debe provenir de los ajustes.

Valor de WordPressAjuste de EmDash
Título del sitiotitle
Eslogantagline
Logotipo personalizadologo
Icono del sitiofavicon
Entradas por páginapostsPerPage

Plantillas de página

Si los editores deben elegir entre varios diseños de página, añade un campo de selección a la colección de páginas y asigna cada valor almacenado a un componente conocido. No conviertas una cadena almacenada en una ruta de importación arbitraria.

Shortcodes y bloques

Asigna los shortcodes que contienen contenido y los bloques de WordPress a estructuras de Portable Text. Usa el renderizador PortableText ordinario para los bloques integrados y un mapa de componentes para los valores _type personalizados.

Un seed puede aportar datos de bloques personalizados, pero no registra un editor personalizado. Usa un plugin nativo cuando el port requiera renderizadores de Astro empaquetados o una interfaz de edición React personalizada.

Por ejemplo, asigna un shortcode de galería de WordPress a un _type con espacio de nombres, como publication.gallery, crea un renderizador Gallery.astro y pásalo a través del mapa de componentes de PortableText:

---
import type { PortableTextBlock } from "emdash";
import { PortableText } from "emdash/ui";
import Gallery from "./blocks/Gallery.astro";

interface Props {
  value: PortableTextBlock[];
}

const { value } = Astro.props;
const customTypes = { "publication.gallery": Gallery };
---

<PortableText value={value} components={{ type: customTypes }} />

Construye el seed

En las plantillas actuales, el seed está en seed/seed.json, y package.json#emdash.seed apunta a él. Declara cada campo de colección, taxonomía, menú, área de widgets, sección y entrada de ejemplo que la plantilla da por supuestos.

El siguiente fragmento define los campos de las publicaciones que intervienen en las rutas:

{
  "$schema": "https://emdashcms.com/seed.schema.json",
  "version": "1",
  "collections": [
    {
      "slug": "posts",
      "label": "Posts",
      "labelSingular": "Post",
      "supports": ["drafts", "revisions", "search", "seo"],
      "urlPattern": "/posts/{slug}",
      "fields": [
        { "slug": "title", "label": "Title", "type": "string", "required": true },
        { "slug": "content", "label": "Content", "type": "portableText" },
        { "slug": "excerpt", "label": "Excerpt", "type": "text" },
        { "slug": "featured_image", "label": "Featured image", "type": "image" }
      ]
    }
  ]
}

Lee Archivos seed para ver los ID de contenido, las referencias, la localización, los medios, la validación y las opciones de aplicación.

Porta en etapas verificables

  1. Importa una exportación representativa de WordPress y registra los bloques o campos no compatibles.

  2. Parte de la plantilla actual de EmDash para el destino de despliegue.

  3. Implementa el layout compartido, los tokens, la tipografía y la envoltura responsiva.

  4. Implementa las rutas de archivo y de una sola entrada usando los slugs y campos de las colecciones importadas.

  5. Añade las rutas de taxonomías, la búsqueda, los menús, las áreas de widgets y los ajustes del sitio que el tema de origen realmente usa.

  6. Añade definiciones de seed para cada dependencia de ruta y prueba la configuración contra una base de datos vacía.

  7. Compara URL públicas representativas en anchos móvil y de escritorio. Prueba campos vacíos, títulos largos, imágenes ausentes, borradores y rutas 404.

  8. Prepara las redirecciones de cada enlace permanente modificado antes del traspaso.

Gestiona los temas de WordPress difíciles

  • Temas hijos: combina la salida resuelta del tema padre y del hijo. Porta las plantillas y los estilos efectivos, no solo el directorio del hijo.
  • Temas de bloques: usa theme.json como fuente de los tokens de diseño y templates/*.html como estructura del contenido, y expresa después el resultado como componentes de Astro.
  • Constructores de páginas: importa primero páginas representativas. Los shortcodes y el JSON propietario de los constructores suelen requerir una conversión deliberada a Portable Text o una plantilla de página rediseñada.
  • Temas de WooCommerce: trata el comportamiento de comercio como una integración de aplicación independiente. Portar los archivos de presentación no sustituye la semántica de producto, carrito, pago, cobro o pedido.