Saltar a contenido

Contenido CMS

MageObsidian trata el contenido CMS como parte de primera clase del storefront: una página puede renderizarse desde una plantilla versionada, un autor puede colocar una isla Vue dentro, y las clases de Tailwind escritas en el admin funcionan — incluso después de haber construido el tema.


Una página CMS renderizada desde el tema

Una página cuyo texto pertenece al código —un aviso de privacidad, una política de envíos— debería viajar y traducirse con el tema, no vivir en una fila de base de datos que ninguna instalación reproduce.

Magento ya arma un handle de layout por página, así que el tema la marca:

<!-- <tema>/Magento_Cms/layout/cms_page_view_id_<identifier>.xml -->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="cms_page">
            <arguments>
                <argument name="obsidian_template" xsi:type="string">Magento_Cms::privacy/policy.twig</argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

La plantilla reemplaza el contenido guardado y nada más cambia: cms_page sigue en el layout, así que el título del documento, la meta description, las keywords, el breadcrumb y la clase cms-<identifier> del body siguen saliendo del registro CMS. El merchant recupera la página borrando el archivo de layout.

Tipografía del contenido del autor

El preflight de Tailwind quita los estilos por defecto de h2, ul y blockquote, lo cual está bien para un storefront diseñado y mal para HTML que alguien pegó en el admin. Por eso cada página y bloque CMS se envuelve en .cms-content, y el tema estiliza eso.

Esas reglas van en la capa base:

1
2
3
4
@layer base {
  .cms-content :is(h1, h2, h3) { font-family: var(--font-display); }
  .cms-content h2 { font-size: var(--text-h3); }
}

Tailwind declara el orden theme, base, components, utilities, así que un class="text-3xl" escrito en el CMS le gana a la regla de prosa por más específica que sea. Sin capa perdería: la clase estaría en la hoja de estilos y aun así no se aplicaría.


Islas Vue desde el contenido

Exponer una

La mayoría de los componentes no son candidatos: un formulario de producto necesita un producto en el registry, un contador de carrito pertenece al header. Así que cada módulo declara cuáles de sus propios componentes tienen sentido en contenido:

<type name="MageObsidian\ModernFrontend\Service\Cms\IslandRegistry">
    <arguments>
        <argument name="islands" xsi:type="array">
            <item name="product_carousel" xsi:type="array">
                <item name="component" xsi:type="string">Vendor_Module::catalog/ProductCarousel</item>
                <item name="label" xsi:type="string" translate="true">Carrusel de productos</item>
                <item name="description" xsi:type="string" translate="true">Una fila de productos de una categoría.</item>
                <item name="props" xsi:type="array">
                    <item name="limit" xsi:type="number">4</item>
                </item>
            </item>
        </argument>
    </arguments>
</type>

El array mergea entre módulos, así que cualquiera aporta el suyo sin tocar el engine. props son los defaults sobre los que se mergean los valores del autor.

Colocarla

Desde el admin, Insert Widget → Vue Island. El desplegable de componentes es el registro intersectado con el manifest de Vite del tema: permitido y realmente construido, así que es imposible elegir un componente cuyo chunk daría 404.

A mano, la directiva {{island}}:

1
2
3
{{island "Vendor_Module::catalog/ProductCarousel" strategy="eager"}}

{{island component="Vendor_Module::catalog/ProductCarousel"}}{"limit": 8}{{/island}}

Los props van en el cuerpo, no en un parámetro: el tokenizer de directivas lee pares key="value", así que las comillas del propio JSON cortarían el valor por la mitad.

Un componente no registrado no renderiza nada y deja el motivo en el log. strategy es visible (montar al hacer scroll) o eager; ver Islas Vue.

Las islas necesitan el runtime del storefront

La directiva está disponible dondequiera que corra el filtro CMS de Magento, emails incluidos. En un email no hay bootstrap de islas, así que el marcador quedaría inerte.


Tailwind escrito en el CMS

El scanner de Tailwind lee archivos. El contenido CMS vive en una base de datos, así que una clase escrita en el admin es invisible para el build. MageObsidian lo cierra en dos pasos.

En el build

bin/magento mage-obsidian:cms:export

Vuelca cada página y bloque a var/mage-obsidian/cms/, y el engine emite un @source para ese directorio. Corrélo antes de construir el tema y el build cubre exacto todas las clases que el contenido ya usa, sin ninguna lista que curar.

El export deja además la lista de clases que vio, que el build copia a su propia salida como cms-candidates.json. Ese es el baseline de lo que viene después.

Un tema se puede excluir en theme.config.js:

1
2
3
export default {
    scanCmsContent: false,
};

Después del build

Un autor que escribe una clase nueva al día siguiente no puede esperar un deploy. Así que el storefront compila la diferencia:

delta = clases(contenido CMS actual) − clases(baseline del build)

Es derivado, nunca acumulativo. No hay archivo que crezca ni reset que recordar: después de un build la diferencia da vacía sola y la hoja de estilos desaparece de la página.

El delta lo compila el binario standalone de Tailwind — un solo archivo, sin Node:

1
2
3
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
chmod +x tailwindcss-linux-x64
mv tailwindcss-linux-x64 bin/tailwindcss

Usá -musl en Alpine y -arm64 en ARM. Se puede configurar otra ruta en mage_obsidian/cms/tailwind_bin.

Como es el compilador de verdad, los valores arbitrarios funcionan: p-[13px] y text-[17px] compilan igual que p-4.

Corre cuando un autor guarda, y un cron cada quince minutos alcanza el contenido que llegó por otra vía —un import, la API REST, un data patch—. bin/magento mage-obsidian:cms:jit lo fuerza, y --show reporta el estado sin reconstruir.

Sin el binario

No se rompe nada: guardar funciona, el storefront sirve lo que produjo el build, y las clases escritas desde entonces simplemente no se aplican. mage-obsidian:frontend:doctor lo reporta y nombra las clases que no pudo generar.

Excluir contenido

El contenido pegado de otro lado —un embed de terceros— trae cientos de nombres de clase ajenos que ensuciarían tanto el build como el delta. Activá Exclude from the CSS scan en la página o el bloque y los dos lo saltean. El estilado que necesite tiene que venir con él.


Cómo se sirve

El delta es una hoja de estilos bajo pub/media, pero se sirve por un controlador en una URL fija:

/mage-obsidian/cms/css

La config estándar de nginx de Magento manda expires +1y para cualquier .css bajo /media/, así que una URL estable ahí no se podría invalidar en un año — y una versionada pondría un href cambiante en el head de todas las páginas, invalidando la FPC entera cada vez que un autor escribe una clase nueva.

Acá el href no cambia nunca y el trabajo lo hace el ETag: un cliente revalida dentro de los cinco minutos y recibe los bytes nuevos apenas cambia el contenido, mientras la caché de páginas queda intacta. El <link> se emite sólo mientras hay delta, así que la transición vacío↔no-vacío es lo único que cambia el HTML de una página.


Notas Clave

  • Las reglas generadas se envuelven en @layer utilities, uniéndose a la capa que la hoja construida ya declara: una clase compilada al vuelo se comporta exactamente como una que salió del build.
  • Un delta por tema. La misma clase compila a CSS distinto contra tokens distintos.
  • Sólo se ven las clases escritas en un atributo class. Una armada por JavaScript en runtime es invisible para cualquier scanner, acá y en el build.
  • El contenido del merchant no se pasa por Twig — sería ejecución arbitraria desde el admin. Las plantillas entran por {{block ... template="....twig"}} o por el argumento de layout de arriba.

Próximos Pasos

  • Islas Vue — cómo se montan los componentes que se colocan acá.
  • Build de Vite — dónde encaja el @source del contenido exportado en el pipeline.