Freddy Montes

Soy un Frontend Developer y diseñador. Aprendo y enseño todo sobre Tecnologías y Diseño Web.

Costa Rica (GMT-6)

🇺🇸 Translate
InstagramTwitterGithubLinkedInStackOverflow

La construcción de un sitio web con Gatsby y un CMS sin cabeza - Parte 2

Freddy Montes|

Esta es una serie de dos partes del blog, aquí está la primera parte.

Escribiendo el plugin de Gatsby

Abre el archivo /gatsby-source-dotcms/gatsby-node.js y reemplaza el código con lo siguiente

const dotCMSApi = require('./dotcms-api');

exports.sourceNodes = ({ actions, createNodeId }, configOptions) => {
    const { createNode } = actions;

    // Gatsby añade una configOption que no es necesaria para este plugin, elimínala
    borrar configOptions.plugins;

    // Función de ayuda que procesa un contentlet para que coincida con la estructura de nodos de Gatsby
    const processContentlet = (contentlet) => {
        const nodeId = createNodeId('dotcms-${contentlet.contentType}-${contentlet.inode}');
        const nodeContent = JSON.stringify(contentlet);
        const nodeData = {
            ...contentlet,
            id: nodeId,
            parent: null,
            children: [],
            internal: {
                type: 'DotCMS${contentlet.contentType}',
                content: nodeContent,
                contentDigest: JSON.stringify(contentlet)
            }
        };
        return nodeData;
    };

    // Gatsby espera que sourceNodes devuelva una promesa
    return dotCMSApi
        .getContentlets(configOptions)
        .then((contentlets) => {
            // Procesa los datos de la respuesta en un nodo
            contentlets.forEach((contentlet) => {
                // Procesa los datos de cada contentlet para que coincidan con la estructura de un nodo Gatsby
                const nodeData = processContentlet(contentlet);
                // Utiliza el ayudante de Gatsby createNode para crear un nodo a partir de los datos del nodo
                createNode(nodeData);
            });
        });
};

Repasemos el nuevo código.

const dotCMSApi = require('./dotcms-api');

En primer lugar, has importado la API de dotCMS que has creado.

const processContentlet = (contentlet) => {
    const nodeId = createNodeId('dotcms-${contentlet.contentType}-${contentlet.inode}');
    const nodeContent = JSON.stringify(contentlet);
    const nodeData = {
        ...contentlet,
        id: nodeId,
        parent: null,
        hijos: [],
        interno: {
            tipo: `DotCMS${contentlet.contentType}`,
            content: nodeContent,
            contentDigest: JSON.stringify(contentlet)
        }
    };
    return nodeData;
};

El trabajo de esta función es recibir un contentlet dotCMS y devolver un Nodo Gatsby extendiéndolo con un operador de propagación. La estructura básica del nodo es la siguiente

interface BasicNode {
    id: String;
    children: Array[String];
    parent: String;
    // Reservado para plugins que deseen extender otros nodos.
    fields: Object;
    internal: {
        contentDigest: Cadena;
        // Tipo de medio opcional (https://en.wikipedia.org/wiki/Media_type) para indicar
        // a los plugins transformadores que este nodo tiene datos que pueden procesar posteriormente.
        mediaType: String;
        // Un tipo de nodo globalmente único elegido por el propietario del plugin.
        type: String;
        // El plugin que creó este nodo.
        owner: String;
        // Almacena qué plugins crearon qué campos.
        fieldOwners: Objeto;
        // Campo opcional que expone el contenido bruto de este nodo
        // que los plugins transformadores pueden tomar y procesar.
        content: Cadena;
        //...otros campos específicos para este tipo de nodo
    };
}

Gatsby espera que sourceNodes devuelva una promesa

return dotCMSApi
    .getContentlets(configOptions)
    .then((contentlets) => {
        // Procesa los datos de la respuesta en un nodo
        contentlets.forEach((contentlet) => {
            // Procesa los datos de cada contentlet para que coincidan con la estructura de un nodo Gatsby
            const nodeData = processContentlet(contentlet);
            // Utiliza el ayudante de Gatsby createNode para crear un nodo a partir de los datos del nodo
            createNode(nodeData);
        });
    });

La última pieza del puzzle es devolver una Promesa de nodos Gatsby procesados. Has utilizado la librería dotCMSApi para getContentlets y convertir cada uno de los contentlets que has obtenido en un nodo.

Probando nuestro nuevo plugin

Edita tu gatsby-config.js y actualiza la entrada para gatsby-source-dotcms:

{
    resolver: 'gatsby-source-dotcms',
    opciones: {
        host: {
            protocolo: 'http',
            url: 'localhost: 8080',
            identifier: '48190c8c-42c4-46af-8d1a-0cd5db894797'
        },
        credenciales: {
            correo electrónico: 'admin@dotcms.com',
            contraseña: 'admin'
        }
    }
};

NOTA: Esta configuración se establece para una instancia de dotCMS que se ejecuta en localhost:8080, asegúrese de apuntar estos valores a una instancia real de dotCMS en ejecución.

Ahora, desde la raíz de su proyecto, ejecute lo siguiente en su terminal:

$ gatsby develop

Si todo ha ido bien deberías ver

Gatsby Done Alert

Consulta de datos de DotCMS con GraphQL

Cada vez que ejecutas $ gatsby develop obtienes esto:

Si vas a http://localhost:8000/__graphql en tu navegador, obtendrás una aplicación web que te permite ejecutar consultas GraphQL y asegurarte de que todo el contenido de DotCMS está ahí. Tendrá el siguiente aspecto:

GraphiQL

Cómo generar páginas

Nota: Como he mencionado antes, Gatsby utiliza React.js para construir páginas y componentes. Es este tutorial, estoy asumiendo que usted está familiarizado con esta biblioteca.

Así que tienes todo nuestro contenido dotCMS listo para usar. Vamos a crear algunas páginas.

Puedes crear páginas en Gatsby explícitamente definiendo componentes React en src/pages/, o programáticamente usando la API createPages.

Crear una página de listado de noticias

Por defecto, Gatsby crea un archivo src/pages/page-2.js. Cambiemos el nombre de ese archivo a src/pages/news.js y sustituyamos el código por

import React from 'react';
import { Link } from 'gatsby';
import Layout from '../components/layout';

const NewsPage = () => (
    <Layout>
        <h1>Lista de noticias</h1>
        <p>Aquí mostraremos una lista de noticias</p>
        <Link to="/">Volver a la página principal</Link>
    </Layout>
);

exportar NewsPage por defecto;

Este es un código React.js muy sencillo con el que acabas de crear un componente que imprimirá HTML plano.

Para ver esta página, ve a tu terminal y ejecuta

$ gatsby develop

Y luego abre tu navegador a http://localhost:8000/news y deberías ver:

Gatsby Default Starter

Bring the dotCMS Contentlets

Bien, ahora es un buen momento para usar el plugin fuente que construiste antes. Vas a consultar todos los contentlets del tipo de contenido "Noticias" y ponerlos en la página.

Una vez más, edita src/pages/news.js y añade el siguiente código:

import React from 'react';
import { graphql } from 'gatsby';
import Layout from '../components/layout';

const NewsPage = ({ data }) => (
    <Layout>
        <h1>News list</h1>
        <ul class="news">
            {data.allDotCmsNews.edges.map(({ node }, index) => (
                <li key={index}>
                    <h4>{node.title}</h4>

                    <p>{node.lead}</p>
                </li>
            ))}
        </ul>
    </Layout>
);

export const query = graphql`
    query {
        allDotCmsNews {
            edges {
                node {
                    lead
                    title
                    urlTitle
                }
            }
        }
    }
`;

export default NewsPage;

Repasemos este nuevo código.

import { graphql } from 'gatsby'

Necesitas importar GraphQL para poder utilizarlo para consultar los contentlets. La [etiqueta] GraphQL de Gatsby (https://www.gatsbyjs.org/docs/page-query#how-does-the-graphql-tag-work) permite a los componentes de la página recuperar datos a través de una consulta GraphQL.

const NewsPage = ({ data }) => (
    <Layout>
        <h1>News list</h1>

        <ul class="news">
            {data.allDotCmsNews.edges.map(({ node }, index) => (
                <li key={index}>
                    <h4>{node.title}</h4>

                    <p>{node.lead}</p>
                </li>
            ))}
        </ul>
    </Layout>
);

Finalmente, sólo tienes que imprimir los datos que obtienes de la consulta GraphQL.

export const query = graphql`
    query {
        allDotCmsNews {
            edges {
                node {
                    lead
                    title
                    urlTitle
                }
            }
        }
    }
`;

En este caso, la "magia" para conseguir los datos es una simple consulta GraphQL para obtener todos los elementos de dotCMS News. En este caso, has pedido el campo: lead, title y urlTitle. Si todo ha ido bien, deberías ver algo como:

Gatsby Default Starter

Creación de páginas de detalles de noticias

Tienes una lista de noticias, ahora vamos a crear una página de detalle para cada noticia en dotCMS. Sé que suena como una tonelada de trabajo, pero con Gatsby puedes crear páginas programáticamente a partir de los datos: básicamente, puedes decirle a Gatsby, aquí hay una colección de noticias, crea una página para cada artículo usando esta plantilla (otro componente de React).

Edita tu archivo gatsby-node.js y añade el siguiente código:

const path = require('path');

exports.createPages = ({ graphql, actions }) => {
    const { createPage } = actions;

    return new Promise((resolve, reject) => {
        graphql(`
            {
                allDotCmsNews {
                    edges {
                        node {
                            inode

                            lead

                            sysPublishDate

                            title

                            urlTitle
                        }
                    }
                }
            }
        `).then((result) => {
            result.data.allDotCmsNews.edges.forEach(({ node }) => {
                createPage({
                    path: 'news/${node.urlTitle}',
                    component: path.resolve('./src/templates/news-item.js'),
                    context: {
                        // Data passed to context is available
                        // in page queries as GraphQL variables.
                        slug: node.urlTitle
                    }
                });
            });

            resolve();
        });
    });
};

¿Qué hace este código?

En primer lugar, este código se ejecutará en tiempo de construcción, todas estas páginas se generarán una vez cuando construyas Gatsby, por lo que si se añade un nuevo contentlet a dotCMS, necesitas construir y desplegar tu sitio Gatsby con el fin de obtener ese contenido.

const path = require(`path`)

Esta es la implementación de la API createPages que Gatsby llama para que los plugins puedan añadir páginas. Al exportar createPages desde un archivo gatsby-node.js, estás diciendo, "en este punto de la secuencia de arranque, ejecuta este código.

graphql(`
    {
        allDotCmsNews {
            edges {
                node {
                    inode
                    lead
                    sysPublishDate
                    title
                    urlTitle
                }
            }
        }
    }
`);

Igual que hiciste con la página del listado, aquí tienes otra consulta GraphQL para obtener todos los contentlets de noticias de dotCMS, la única diferencia es que estás pidiendo más campos.

.then((result) => {
    result.data.allDotCmsNews.edges.forEach(({ node }) => {
        createPage({
            path: `news/${node.urlTitle}`,
            component: path.resolve('./src/templates/news-item.js'),
            context: {
                // Data passed to context is available
                // in page queries as GraphQL variables.
                slug: node.urlTitle
            }
        });
    });
    resolve();
});

Una vez que la consulta GraphQL se resuelve, tienes acceso a todas las noticias, y puedes iterar y crear una página para cada artículo; pero necesitas pasar un objeto a la función createPage:

  1. path: la URL donde se creará la página generada. En este caso, será news/page-url-title. Se utiliza el campo urlTitle del contentlet para crear la ruta, pero, sea cual sea la que utilices, tiene que ser única, porque será el nombre del archivo HTML generado y, por tanto, la URL de la página.
  2. component: aquí es donde le dices a Gatsby qué plantilla vas a utilizar para crear cada página (lo crearás en el siguiente paso).
  3. context: es un objeto que se pasa a la consulta GraphQL que estará en el componente de la plantilla.

Finalmente, se llama a resolve() para la Promise que retorna en la implementación de la función createPages.

Creación de la plantilla

Una plantilla es un componente normal de React.js, así que vamos a crear un archivo en src/templates/news-item.js (como el que has establecido en la propiedad path del parámetro createPage) y añadir el siguiente código:

import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/layout'

export default ({ data }) => {
    const post = data.allDotCmsNews.edges[0].node
    return (
        <Layout>
            <h1>{post.title}</h1>
            <div dangerouslySetInnerHTML={{ __html: post.story }} />
        </Layout>
    )
}

export const query = graphql`
    query($slug: String!) {
        allDotCmsNews(filter: { urlTitle: { eq: $slug } }) {
            edges {
                node {
                    title,
                    story,
                }
            }
        }
    }
`

Repasemos este código

export default ({ data }) => {

Antes de obtener los datos para usarlos en la página, los obtienes como un prop en el componente.

<Layout>
 <h1>{post.title}</h1>
 <div dangerouslySetInnerHTML={{ __html: post.story }} />
</Layout>

Imprime el contenido de la noticia en la página.

export const query = graphql`
    query($slug: String!) {
        allDotCmsNews(filter: { urlTitle: { eq: $slug } }) {
            edges {
                node {
                    title,
                    story,
                }
            }
        }
    }
`

Aquí está en la consulta GraphQL. Todo es casi lo mismo que las otras consultas con la diferencia de que estás coincidiendo con una noticia por urlTitle y el parámetro $slug, que es lo que pasas en el objeto context en los params de la función createPage en el gatsby-node.js

Ahora podemos construir nuestras páginas, sólo necesitamos ejecutarlas:

$ gatsby develop

Ahora mismo no tienes enlaces a los enlaces creados recientemente, pero puedes usar el campo urlTitle del contentlet. Vaya a: http://localhost:8080/news/the-gas-price-rollercoaster y debería ver:

dotCMS Gatsby News Detail

Toques finales: Añadiendo enlaces a nuestra página de anuncios

Ve y edita src/pages/news.js y:

Importe el componente Link en la parte superior del archivo:

import { Link } from 'gatsby'

Sustituye esta línea:

<h4>{node.title}</h4>

Con:

<h4><Link to={'news/' + node.urlTitle}>{node.title}</Link></h4>

En el navegador ve a http://localhost:8080/news y deberías ver:

dotCMS Gatsby News Results

Ahora los elementos de la lista de noticias están vinculados a la página de detalles que has creado dinámicamente con Gatsby.

Generador de sitios estáticos + Headless CMS = La combinación perfecta

¡Y ahí lo tienes! Ahora ha utilizado un generador de sitios estáticos y un CMS sin cabeza para construir un sitio web estático. Este es sólo un ejemplo de cómo estas dos tecnologías pueden combinarse.

Para recapitular, construimos un plugin de fuente de Gatsby desde cero, creamos una página de listado, y creamos un montón de páginas HTML estáticas consultando datos de dotCMS con GraphQL.

Te recomiendo que eches un vistazo a Gatsby Documentation, esto es lo más básico pero puedes hacer mucho más, componentes con estilo, diseños, paginación, etc.


Gracias por leer esta entrada del blog. Cualquier comentario o pregunta me puedes preguntarme en mi Twitter: @fmontes