Cómo crear un blog con Sapper (Svelte) y Markdown

¿Qué es Sapper?

Sapper es un framework hecho con Svelte ya preparado con un enrutamiento flexible basado en sistemas de archivos y SSR. Tal vez conozcas NuxtJS con Vue… pues lo mismo, pero para Svelte 😜

¿Por qué hacer un blog con markdown?

Bueno, esta decisión fue más por aprender que por otra cosa. Puede ser una buena opción para toda persona que no tenga un hosting, o no quiera tenerlo por el motivo que sea. También me gusto el hecho de usar Markdown, texto plano de siempre con un marcado claro y sencillo.

Vamos a empezar

No te llevará más de 10 minutos. Debo decir que no me voy a detener en maquetaciones ni nada de los posts, ni del listado de posts. Sólo me voy a centrar en la lógica para obtener el listado de posts (*.md), mostrarlos y ver el detalle.

Instalar Sapper y arrancar el proyecto

npx degit "sveltejs/sapper-template#rollup" NOMBRE_DE_TU_APP

El proceso no debe llevarte mucho tiempo, apenas unos segundos. Ya que no instala las dependencias. Una vez termine, deberás acceder a la carpeta del proyecto e instalar las depenciencias:

cd NOMBRE_DE_TU_APP
npm install
npm run dev & open http://localhost:3000

Con todo esto debería abrirse el navegador y ver la plantilla de ejemplo de Sapper. Verás que ya viene un menú con 3 enlaces en la cabecera totalmente funcionales.

  • http://localhost:3000/
  • http://localhost:3000/about
  • http://localhost:3000/blog

¡Perfecto! Seguimos.

Contenido

La carpeta donde irá nuestro contenido será dentro de src/contents. Y pondremos un archivo de ejemplo con la siguiente estructura:

---
date: 2021-01-18
summary: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ultricies congue erat, ut tempor felis euismod imperdiet.
title: ¡Hola Alex Tomás!
---

Este es mi primer documento con formato Markdown. Lo guardamos con el nombre que quieras, por ejemplo `primera-prueba.md`

Plugins

Podríamos cargar los archivos .md usando algo como fs.readFile() en tiempo de compilación, pero hay una forma más fácil usando import.

Para ello vamos a utilizar el plugin @jackfranklin/rollup-plugin-markdown. ¡Es magia pura!

El plugin hace posible lo siguiente:

import post1 from 'posts/example1.md'
import post2 from 'posts/example2.md'
// ...

Si tuvieramos que hacer esto uno por uno… llegaría un momento que nos volveríamos locos. 😅 Es más fácil que "algo" nos importe todos los archivos *.md de nuestra carpeta. Eso exactamente hace el plugin rollup-plugin-glob 🚀

Entonces, vamos a añadir los siguientes paquetes:

npm i -D @jackfranklin/rollup-plugin-markdown rollup-plugin-glob

Y tenemos que hacer una pequeña modificación en nuestro rollup.config.js:

// Importamos los paquetes
import markdown from '@jackfranklin/rollup-plugin-markdown'
import glob from 'rollup-plugin-glob'

// Recuerda que rollup crea multiples builds, así que fíjate de
// añadir los dos plugins tanto en `server` como en `client`
export {
  client: {
    plugins: [
      markdown(),
      glob(),
      ...
    ],
    ...
  },

  server: {
    plugins: [
      markdown(),
      glob(),
      ...
    ],
    ...
  }
}

Centralizamos los posts

Ahora que podemos importar .md, vamos a centralizar la lógica para acceder a las publicaciones, crearemos el archivo src/posts.js:

import all from './contents/*.md';

export const posts = all;

Si pusieramos un console.log(posts), varíamos algo como:

[
    {
        "metadata": {
            "title": "¡Hola Alex Tomás!",
            "summary": "Lorem ipsum dolor sit amet...",
            "date": "2021-01-18"
        },
        "html": "Este es mi primer documento con formato Markdown",
        "filename": "primera-prueba.md"
    }
]

¡Genial! Ahora sólo nos queda darle un poco de forma y crearnos un par de funciones. Vamos a realizar estas mejoras:

  • Ponga la metadata( title, summary, date) en el nivel superior.
  • Agrega un permalinkcampo. Se basará en elfilename
  • Ordene la lista de publicaciones dateen orden descendente (las publicaciones más recientes primero)

Dejaremos el archivo src/posts.js de la siguiente forma:

import _ from 'lodash';
import all from '../posts/*.md';

export const posts = _.chain(all)
    .map(transform)
    .orderBy('date', 'desc') // ordenamos los posts por fecha
    .value();

// función para formatear cada post
function transform({ filename, html, metadata }) {
    // el permalink es el nombre del archivo '.md'
    const permalink = filename.replace(/\.md$/, '');

    // convertimos la fecha en un Date
    const date = new Date(metadata.date);

    // devolvemos el post con el nuevo formato
    return { ...metadata, filename, html, permalink, date };
}

// función para buscar un post
export function findPost(permalink) {
    // usamos lodash para encontrar un post por su permalink (nombre de fichero):
    return _.find(posts, { permalink });
}

Nota: vamos a necesitar instalar el paquete de lodash:

npm i lodash

Listado de posts

¡Es hora de mostrar los posts que tenemos! Abrimos el archivo src/routes/index.svelte que ya nos había generado Sapper por nosotros.

<script>
    // Importamos los posts desde el fichero src/posts.js
    import { posts } from '../../posts';
</script>

<svelte:head>
    <title>Blog</title>
</svelte:head>

<h1>Recent posts</h1>

{#each posts as post}
    <article>
        <a href={`blog/${post.permalink}`}>
            <h2>{post.title}</h2>
            <p>{post.summary}</p>
        </a>
    </article>
{/each}

Básicamente lo que hacemos es:

  • Importamos los posts.
  • Con svelte:head ponemos el título en la página.
  • Hacemos un bucle para imprimir un article para cada post.
  • Añadimos un enlace con el link al post. Recuerda que el permalink es el nombre del archivo .md

Detalle de un post

Ya tenemos nuestro listado de posts en src/routes/blog/index.svelte. Ahora vamos a editar src/routes/blog/[slug].svelte, que es otra ruta que Sapper no había dejado preparado por nosotros.

¿Te has fijado en los corchetes alrededor de [slug]? Eso le dice a Sapper que slug es un parámetro dinámico. Sapper proporcionará todos los parámetros a nuestra función preload (). Ahora lo vemos.

De momento te dejo como debería quedar el archivo final:

<script context="module">
    import { findPost } from '../../posts';

    export async function preload(page, session) {
        const { slug } = page.params;
        const post = findPost(slug);
        return { post };
    }
</script>

<script>
    export let post;
</script>

<svelte:head>
    <title>{post.title}</title>
</svelte:head>

<h1>{post.title}</h1>

<div class="content">{@html post.html}</div>

Nota: La definición de context="module" es necesaria para poder utilizar la función preloadantes de que el componente sea creado. De este modo podremos obtener los parámetros pasados como props.

Así pues, podemos obtener el slugy pasarselo a la función findPost, que se encargará de encontrar el post a través de su nombre (slug). Así de fácil. Y nos lo guardará en la constante post.

Finalmente sólo nos queda pintar la información.

¡Ya está! Ya tienes un blog con una funcionalidad básica, pero que trabaja con archivos markdown.

Conclusiones

Muchas veces me ha tocado trabajar con equipos de redacción y he peleado mucho con Wordpress u otros sistemas. Es cierto que Wordpress, por ejemplo, es muy conocido y usado por redactores, pero luego vienen problemas como código copiado/pegado y estilos heredados, o bien el que no es amigo/a de las tecnologías y quiere algo básico.

Markdown es texto plano, tal cual. Su sintaxis es realmente básica para poder usarla por cualquier persona. Por tanto, te pueden mandar el archivo .md, lo copias en el directorio contents y eso es todo. Sapper hará el resto por tí. Lo mostrará en el listado de archivos y enlazará a la página de detalle.

¡No necesitarás base de datos! No toda la gente quiere o puede invertir en un hosting con base de datos, y un hosting gratuito les hace el papel. Ésta es una muy buena solución.

Y esto es todo. Espero que te pueda servir 🙃