Contexto y variables en el generador de sitios estáticos de Hugo
Publicado: 2022-03-10En este artículo, veremos de cerca cómo funciona el contexto en el generador de sitios estáticos de Hugo. Examinaremos cómo fluyen los datos del contenido a las plantillas, cómo ciertas construcciones cambian qué datos están disponibles y cómo podemos pasar estos datos a parciales y plantillas base.
Este artículo no es una introducción a Hugo . Probablemente lo aproveche al máximo si tiene algo de experiencia con Hugo, ya que no repasaremos todos los conceptos desde cero, sino que nos centraremos en el tema principal del contexto y las variables. Sin embargo, si consulta la documentación de Hugo en todo momento, es posible que pueda seguirlo incluso sin experiencia previa.
Estudiaremos varios conceptos construyendo una página de ejemplo. No se cubrirán en detalle todos los archivos necesarios para el sitio de ejemplo, pero el proyecto completo está disponible en GitHub. Si quieres entender cómo encajan las piezas, ese es un buen punto de partida. Tenga en cuenta también que no cubriremos cómo configurar un sitio de Hugo o ejecutar el servidor de desarrollo; las instrucciones para ejecutar el ejemplo se encuentran en el repositorio.
¿Qué es un generador de sitio estático?
Si el concepto de generadores de sitios estáticos es nuevo para usted, ¡aquí tiene una introducción rápida! Los generadores de sitios estáticos quizás se describan mejor comparándolos con sitios dinámicos. Un sitio dinámico como un CMS generalmente ensambla una página desde cero para cada visita, tal vez obteniendo datos de una base de datos y combinando varias plantillas para hacerlo. En la práctica, el uso del almacenamiento en caché significa que la página no se regenera con tanta frecuencia, pero a efectos de esta comparación, podemos pensar en ello de esa manera. Un sitio dinámico se adapta bien al contenido dinámico : contenido que cambia con frecuencia, contenido que se presenta en muchas configuraciones diferentes según la entrada y contenido que el visitante del sitio puede manipular.
Por el contrario, muchos sitios rara vez cambian y aceptan pocos aportes de los visitantes. Una sección de "ayuda" para una aplicación, una lista de artículos o un libro electrónico podrían ser ejemplos de tales sitios. En este caso, tiene más sentido ensamblar las páginas finales una vez que cambia el contenido, y luego mostrar las mismas páginas a todos los visitantes hasta que el contenido cambie nuevamente.
Los sitios dinámicos tienen más flexibilidad, pero exigen más al servidor en el que se ejecutan. También pueden ser difíciles de distribuir geográficamente, especialmente si se trata de bases de datos. Los generadores de sitios estáticos se pueden alojar en cualquier servidor capaz de entregar archivos estáticos y son fáciles de distribuir.
Una solución común hoy en día, que combina estos enfoques, es JAMstack. "JAM" significa JavaScript, API y marcado y describe los componentes básicos de una aplicación JAMstack: un generador de sitios estáticos genera archivos estáticos para entregarlos al cliente, pero la pila tiene un componente dinámico en forma de JavaScript que se ejecuta en el cliente: este componente de cliente puede usar las API para proporcionar funcionalidad dinámica al usuario.
Hugo
Hugo es un popular generador de sitios estáticos. Está escrito en Go, y el hecho de que Go sea un lenguaje de programación compilado sugiere algunos de los beneficios y desventajas de Hugo. Por un lado, Hugo es muy rápido , lo que significa que genera sitios estáticos muy rápidamente. Por supuesto, esto no tiene relación con la rapidez o la lentitud de los sitios creados con Hugo para el usuario final, pero para el desarrollador, el hecho de que Hugo compile incluso sitios grandes en un abrir y cerrar de ojos es muy valioso.
Sin embargo, como Hugo está escrito en un lenguaje compilado, extenderlo es difícil . Algunos otros generadores de sitios le permiten insertar su propio código, en lenguajes como Ruby, Python o JavaScript, en el proceso de compilación. Para extender Hugo, necesitaría agregar su código a Hugo mismo y volver a compilarlo; de lo contrario, se quedaría con las funciones de plantilla que Hugo viene de fábrica.
Si bien proporciona una rica variedad de funciones, este hecho puede volverse limitante si la generación de sus páginas implica una lógica complicada. Como descubrimos, tener un sitio desarrollado originalmente que se ejecuta en una plataforma dinámica, los casos en los que ha dado por sentada la capacidad de colocar su código personalizado tienden a acumularse.
Nuestro equipo mantiene una variedad de sitios web relacionados con nuestro producto principal, el cliente Tower Git, y recientemente consideramos trasladar algunos de estos a un generador de sitios estáticos. Uno de nuestros sitios, el sitio "Learn", parecía particularmente adecuado para un proyecto piloto. Este sitio contiene una variedad de material de aprendizaje gratuito que incluye videos, libros electrónicos y preguntas frecuentes sobre Git, pero también otros temas tecnológicos.
Su contenido es en gran parte de naturaleza estática, y todas las características interactivas que existen (como las suscripciones a boletines) ya estaban impulsadas por JavaScript. A fines de 2020, convertimos este sitio de nuestro CMS anterior a Hugo , y hoy funciona como un sitio estático. Naturalmente, aprendimos mucho sobre Hugo durante este proceso. Este artículo es una forma de compartir algunas de las cosas que aprendimos.
Nuestro ejemplo
Como este artículo surgió de nuestro trabajo de convertir nuestras páginas a Hugo, parece natural armar una página de destino hipotética (¡muy!) simplificada como ejemplo. Nuestro enfoque principal será una llamada plantilla de "lista" reutilizable.
En resumen, Hugo utilizará una plantilla de lista para cualquier página que contenga subpáginas. Hay más en la jerarquía de plantillas de Hugo que eso, pero no tiene que implementar todas las plantillas posibles. Una sola plantilla de lista es muy útil. Se utilizará en cualquier situación que requiera una plantilla de lista donde no haya ninguna plantilla más especializada disponible.
Los posibles casos de uso incluyen una página de inicio, un índice de blog o una lista de preguntas frecuentes. Nuestra plantilla de lista reutilizable residirá en layouts/_default/list.html
en nuestro proyecto. Nuevamente, el resto de los archivos necesarios para compilar nuestro ejemplo están disponibles en GitHub, donde también puede ver mejor cómo encajan las piezas. El repositorio de GitHub también viene con una plantilla single.html
; como sugiere el nombre, esta plantilla se usa para páginas que no tienen subpáginas, pero que actúan como piezas únicas de contenido por derecho propio.
Ahora que hemos preparado el escenario y explicado qué es lo que haremos, ¡comencemos!
El contexto o “el punto”
Todo comienza con el punto. En una plantilla de Hugo, el objeto .
— “el punto” — se refiere al contexto actual. ¿Qué significa esto? Cada plantilla renderizada en Hugo tiene acceso a un conjunto de datos: su contexto . Inicialmente, se establece en un objeto que representa la página que se está representando actualmente, incluido su contenido y algunos metadatos. El contexto también incluye variables de todo el sitio, como opciones de configuración e información sobre el entorno actual. Accedería a un campo como el título de la página actual usando .Title
y la versión de Hugo que se usa a través .Hugo.Version
; en otras palabras, está accediendo a campos del .
estructura.
Es importante destacar que este contexto puede cambiar, haciendo que una referencia como '.Title' arriba apunte a otra cosa o incluso anulándola. Esto sucede, por ejemplo, cuando recorre una colección de algún tipo usando range
, o cuando divide plantillas en parciales y plantillas base . ¡Vamos a ver esto en detalle más adelante!
Hugo usa el paquete de "plantillas" de Go, por lo que cuando nos referimos a las plantillas de Hugo en este artículo, en realidad estamos hablando de plantillas de Go. Hugo agrega muchas funciones de plantilla que no están disponibles en las plantillas estándar de Go.
En mi opinión, el contexto y la posibilidad de volver a unirlo es una de las mejores características de Hugo. Para mí, tiene mucho sentido tener siempre "el punto" que represente cualquier objeto que sea el foco principal de mi plantilla en un punto determinado, volviendo a vincularlo según sea necesario a medida que avanzo. Por supuesto, también es posible meterse en un lío enredado, pero hasta ahora he estado contento con eso, en la medida en que rápidamente comencé a extrañarlo en cualquier otro generador de sitios estáticos que miré.
Con esto, estamos listos para ver el humilde punto de partida de nuestro ejemplo: la plantilla a continuación, que reside en la ubicación layouts/_default/list.html
en nuestro proyecto:
<html> <head> <title>{{ .Title }} | {{ .Site.Title }}</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> </ul> </nav> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> </body> </html>
La mayor parte de la plantilla consta de una estructura HTML básica, con un enlace a la hoja de estilo, un menú de navegación y algunos elementos y clases adicionales que se utilizan para diseñar. Lo interesante está entre las llaves , que le indican a Hugo que intervenga y haga su magia, reemplazando lo que sea que esté entre las llaves con el resultado de evaluar alguna expresión y potencialmente manipular el contexto también.
Como puede adivinar, {{ .Title }}
en la etiqueta de título se refiere al título de la página actual, mientras que {{ .Site.Title }}
se refiere al título de todo el sitio, establecido en la configuración de Hugo . Una etiqueta como {{ .Title }}
simplemente le dice a Hugo que reemplace esa etiqueta con el contenido del campo Title
en el contexto actual.
Entonces, hemos accedido a algunos datos pertenecientes a la página en una plantilla. ¿De dónde vienen estos datos? Ese es el tema de la siguiente sección.
Contenido y material preliminar
Algunas de las variables disponibles en el contexto son proporcionadas automáticamente por Hugo. Otros son definidos por nosotros, principalmente en archivos de contenido . También hay otras fuentes de datos como archivos de configuración, variables de entorno, archivos de datos e incluso API. En este artículo, nuestro enfoque estará en los archivos de contenido como fuente de datos.
En general, un solo archivo de contenido representa una sola página. Un archivo de contenido típico incluye el contenido principal de esa página, pero también metadatos sobre la página, como su título o la fecha en que se creó. Hugo admite varios formatos tanto para el contenido principal como para los metadatos. En este artículo elegiremos quizás la combinación más común: el contenido se proporciona como Markdown en un archivo que contiene los metadatos como materia prima YAML.
En la práctica, eso significa que el archivo de contenido comienza con una sección delimitada por una línea que contiene tres guiones en cada extremo. Esta sección constituye el tema principal , y aquí los metadatos se definen usando una key: value
(como veremos pronto, YAML también admite estructuras de datos más elaboradas). El tema principal es seguido por el contenido real, especificado mediante el lenguaje de marcado Markdown.
Hagamos las cosas más concretas mirando un ejemplo. Aquí hay un archivo de contenido muy simple con un campo principal y un párrafo de contenido:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(Este archivo reside en content/_index.md
en nuestro proyecto, donde _index.md
indica el archivo de contenido de una página que tiene subpáginas. Una vez más, el repositorio de GitHub aclara dónde se supone que debe ir el archivo).
Renderizado usando la plantilla anterior, junto con algunos estilos y archivos periféricos (todos encontrados en GitHub), el resultado se ve así:
Quizás se pregunte si los nombres de los campos en el frente de nuestro archivo de contenido están predeterminados o si podemos agregar cualquier campo que deseemos. La respuesta es "ambos". Hay una lista de campos predefinidos, pero también podemos agregar cualquier otro campo que se nos ocurra. Sin embargo, se accede a estos campos de forma un poco diferente en la plantilla. Mientras que a un campo predefinido como el title
se accede simplemente como .Title
, a un campo personalizado como el author
se accede mediante .Params.author
.
(Para obtener una referencia rápida sobre los campos predefinidos, junto con cosas como funciones, parámetros de función y variables de página, consulte nuestra propia hoja de trucos de Hugo).
La variable .Content
, utilizada para acceder al contenido principal del archivo de contenido de su plantilla, es especial. Hugo tiene una función de "código abreviado" que le permite usar algunas etiquetas adicionales en su contenido de Markdown. También puede definir el suyo propio. Desafortunadamente, estos códigos abreviados solo funcionarán a través de la variable .Content
; aunque puede ejecutar cualquier otro dato a través de un filtro Markdown, esto no manejará los códigos abreviados en el contenido.
Una nota aquí sobre las variables no definidas: acceder a un campo predefinido como .Date
siempre funciona, aunque no lo haya configurado; en este caso, se devolverá un valor vacío. Acceder a un campo personalizado indefinido, como .Params.thisHasNotBeenSet
, también funciona y devuelve un valor vacío. Sin embargo, acceder a un campo de nivel superior no predefinido como .thisDoesNotExist
evitará que el sitio se compile.
Como se indica en .Params.author
, así como en .Hugo.version
y .Site.title
anteriormente, las invocaciones encadenadas se pueden usar para acceder a un campo anidado en alguna otra estructura de datos. Podemos definir tales estructuras en nuestro frente. Veamos un ejemplo, donde definimos un mapa , o diccionario, especificando algunas propiedades para un banner en la página de nuestro archivo de contenido. Aquí está el content/_index.md
actualizado:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
Ahora, agreguemos un banner a nuestra plantilla, haciendo referencia a los datos del banner usando .Params
de la manera descrita anteriormente:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
Así es como se ve nuestro sitio ahora:
¡Bien! Por el momento, estamos accediendo a los campos del contexto predeterminado sin ningún problema. Sin embargo, como se mencionó anteriormente, este contexto no es fijo, sino que puede cambiar.
Echemos un vistazo a cómo podría suceder eso.
Control de flujo
Las declaraciones de control de flujo son una parte importante de un lenguaje de plantillas, lo que le permite hacer diferentes cosas según el valor de las variables, recorrer datos y más. Las plantillas de Hugo proporcionan el conjunto esperado de construcciones, incluidos if/else
para lógica condicional y range
para bucles. Aquí, no cubriremos el control de flujo en Hugo en general (para obtener más información sobre esto, consulte la documentación), sino que nos centraremos en cómo estas declaraciones afectan el contexto. En este caso, las declaraciones más interesantes son with
y range
.
Comencemos con with
. Esta declaración verifica si alguna expresión tiene un valor "no vacío" y, si lo tiene, vuelve a vincular el contexto para hacer referencia al valor de esa expresión . Una etiqueta end
indica el punto donde se detiene la influencia de la declaración with
, y el contexto vuelve a ser lo que era antes. La documentación de Hugo define un valor no vacío como falso, 0 y cualquier matriz, sector, mapa o cadena de longitud cero.
Actualmente, nuestra plantilla de lista no está haciendo muchas listas. Podría tener sentido que una plantilla de lista presente algunas de sus subpáginas de alguna manera. Esto nos da una oportunidad perfecta para ejemplos de nuestras declaraciones de control de flujo.
Tal vez queramos mostrar algún contenido destacado en la parte superior de nuestra página. Puede ser cualquier contenido: una publicación de blog, un artículo de ayuda o una receta, por ejemplo. En este momento, digamos que nuestro sitio de ejemplo de Tower tiene algunas páginas que destacan sus características, casos de uso, una página de ayuda, una página de blog y una página de "plataforma de aprendizaje". Todos ellos están ubicados en el directorio content/
. Configuramos qué parte del contenido mostrar agregando un campo en el archivo de contenido para nuestra página de inicio, content/_index.md
. Se hace referencia a la página por su ruta, asumiendo que el directorio de contenido es raíz, así:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
A continuación, nuestra plantilla de lista debe modificarse para mostrar este contenido. Hugo tiene una función de plantilla, .GetPage
, que nos permitirá referirnos a objetos de página distintos al que estamos representando actualmente. Recuerde cómo el contexto, .
, estaba inicialmente vinculado a un objeto que representaba la página que se representaba? Usando .GetPage
y with
, podemos vincular temporalmente el contexto a otra página, haciendo referencia a los campos de esa página al mostrar nuestro contenido destacado:
<nav> ... </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} </div> </section>
Aquí, {{ .Title }}
, {{ .Summary }}
y {{ .Permalink }}
entre las etiquetas with
y end
se refieren a esos campos en la página destacada , y no al principal que se muestra.
Además de tener una pieza de contenido destacada, enumeremos algunas piezas de contenido más abajo. Al igual que el contenido destacado, las piezas de contenido enumeradas se definirán en content/_index.md
, el archivo de contenido de nuestra página de inicio. Agregaremos una lista de rutas de contenido a nuestro frente como esta (en este caso, también especificando el título de la sección):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
La razón por la que la página del blog tiene su propio directorio y un archivo _index.md
es que el blog tendrá sus propias subpáginas: publicaciones de blog.
Para mostrar esta lista en nuestra plantilla, usaremos range
. Como era de esperar, esta declaración se repetirá en una lista, pero también volverá a vincular el contexto a cada elemento de la lista a su vez. Esto es muy conveniente para nuestra lista de contenido.
Tenga en cuenta que, desde la perspectiva de Hugo, "listar" solo contiene algunas cadenas. Para cada iteración del ciclo de "rango", el contexto se vinculará a una de esas cadenas . Para obtener acceso al objeto de la página real, proporcionamos su cadena de ruta (ahora el valor de .
) como argumento para .GetPage
. Luego, usaremos la declaración with
nuevamente para volver a vincular el contexto al objeto de la página enumerada en lugar de su cadena de ruta. Ahora, es fácil mostrar el contenido de cada página enumerada a su vez:
<aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
Así es como se ve el sitio en este momento:
Pero espera, hay algo extraño en la plantilla anterior: en lugar de llamar a .GetPage
, estamos llamando a $.GetPage
. ¿Puedes adivinar por qué .GetPage
no funcionaría?
La notación .GetPage
indica que la función GetPage
es un método del contexto actual. De hecho, en el contexto predeterminado, existe un método de este tipo, ¡pero seguimos adelante y cambiamos el contexto ! Cuando llamamos a .GetPage
, el contexto está vinculado a una cadena, que no tiene ese método. La forma en que solucionamos esto es el tema de la siguiente sección.
El contexto mundial
Como se vio anteriormente, hay situaciones en las que se ha cambiado el contexto, pero aún nos gustaría acceder al contexto original. Aquí, es porque queremos llamar a un método existente en el contexto original; otra situación común es cuando queremos acceder a alguna propiedad de la página principal que se está representando. No hay problema, hay una manera fácil de hacer esto.
En una plantilla de Hugo, $
, conocido como contexto global , hace referencia al valor original del contexto: el contexto tal como era cuando se inició el procesamiento de la plantilla. En la sección anterior, se usó para llamar al método .GetPage
aunque habíamos reenlazado el contexto a una cadena. Ahora, también lo usaremos para acceder a un campo de la página que se está representando.
Al comienzo de este artículo, mencioné que nuestra plantilla de lista es reutilizable. Hasta ahora, solo lo hemos usado para la página de inicio, representando un archivo de contenido ubicado en content/_index.md
. En el repositorio de ejemplo, hay otro archivo de contenido que se representará con esta plantilla: content/blog/_index.md
. Esta es una página de índice para el blog y, al igual que la página de inicio, muestra un contenido destacado y enumera algunas publicaciones más, en este caso.
Ahora, digamos que queremos mostrar el contenido de la lista de forma ligeramente diferente en la página de inicio , no lo suficiente como para garantizar una plantilla separada, pero algo que podemos hacer con una declaración condicional en la plantilla misma. Como ejemplo, mostraremos el contenido de la lista en una cuadrícula de dos columnas, en lugar de una lista de una sola columna, si detectamos que estamos representando la página de inicio.
Hugo viene con un método de página, .IsHome
, que proporciona exactamente la funcionalidad que necesitamos. Manejaremos el cambio real en la presentación agregando una clase a las piezas individuales de contenido cuando nos encontremos en la página de inicio, permitiendo que nuestro archivo CSS haga el resto.
Por supuesto, podríamos agregar la clase al elemento del cuerpo o algún elemento contenedor en su lugar, pero eso no permitiría una buena demostración del contexto global. En el momento en que escribimos el HTML para el contenido de la lista, .
hace referencia a la página de la lista , pero es necesario llamar a IsHome
en la página principal que se está representando. El contexto global viene a nuestro rescate:
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article{{ if $.IsHome }} class="home"{{ end }}> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
El índice del blog se parece a nuestra página de inicio, aunque con contenido diferente:
…pero nuestra página de inicio ahora muestra su contenido destacado en una cuadrícula:
Plantillas parciales
Al crear un sitio web real, rápidamente se vuelve útil dividir sus plantillas en partes. Tal vez quiera reutilizar alguna parte particular de una plantilla, o tal vez solo quiera dividir una plantilla enorme y difícil de manejar en partes coherentes. Para este propósito, las plantillas parciales de Hugo son el camino a seguir.
Desde la perspectiva del contexto, lo importante aquí es que cuando incluimos una plantilla parcial, le pasamos explícitamente el contexto que queremos que esté disponible. Una práctica común es pasar el contexto tal como está cuando se incluye el parcial, así: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. Si el punto aquí se refiere a la página que se representa, eso es lo que se pasará al parcial; si el contexto se ha vuelto a vincular a otra cosa, eso es lo que se transmite.
Por supuesto, puede volver a vincular el contexto en plantillas parciales como en las normales. En este caso, el contexto global, $
, se refiere al contexto original pasado al parcial, no a la página principal que se representa (a menos que eso sea lo que se pasó).
Si queremos que una plantilla parcial tenga acceso a algún dato en particular, podríamos tener problemas si le pasamos solo esto al parcial. ¿Recuerda nuestro problema anterior con el acceso a métodos de página después de volver a vincular el contexto? Lo mismo ocurre con los parciales , pero en este caso el contexto global no puede ayudarnos; si hemos pasado, digamos, una cadena a una plantilla parcial, el contexto global en el parcial se referirá a esa cadena, y ganamos No podrá llamar a los métodos definidos en el contexto de la página.
La solución a este problema pasa por pasar más de un dato al incluir el parcial. Sin embargo, solo podemos proporcionar un argumento a la llamada parcial. Sin embargo, podemos convertir este argumento en un tipo de datos compuesto, comúnmente un mapa (conocido como diccionario o hash en otros lenguajes de programación).
En este mapa, podemos, por ejemplo, tener una clave de Page
configurada para el objeto de página actual, junto con otras claves para que pase cualquier dato personalizado. El objeto de página estará disponible como .Page
en el parcial, y el otro Se accede a los valores del mapa de manera similar. Un mapa se crea utilizando la función de plantilla de dict
, que toma un número par de argumentos, interpretados alternativamente como una clave, su valor, una clave, su valor, etc.
En nuestra plantilla de ejemplo, movamos el código de nuestro contenido destacado y listado a parciales. Para el contenido destacado, basta con pasar el objeto de la página destacada. Sin embargo, el contenido de la lista necesita acceso al método .IsHome
además del contenido de la lista en particular que se representa. Como se mencionó anteriormente, aunque .IsHome
está disponible en el objeto de la página de la lista, eso no nos dará la respuesta correcta: queremos saber si la página principal que se representa es la página de inicio.
En su lugar, podríamos pasar un conjunto booleano al resultado de llamar a .IsHome
, pero tal vez el parcial necesite acceso a otros métodos de página en el futuro; pasemos al objeto de la página principal , así como al objeto de la página enumerada. En nuestro ejemplo, la página principal se encuentra en $
y la página listada en .
. Entonces, en el mapa pasado al parcial listed
, la clave Page
obtiene el valor $
mientras que la clave "Listado" obtiene el valor .
. Esta es la plantilla principal actualizada:
<body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ partial "partials/listed.html" (dict "Page" $ "Listed" .) }} {{ end }} {{ end }} </div> </div> </section> </body>
El contenido de nuestro parcial "destacado" no cambia en comparación con cuando formaba parte de la plantilla de lista:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
Sin embargo, nuestro contenido parcial de la lista refleja el hecho de que el objeto de la página original ahora se encuentra en .Page
mientras que el contenido de la lista se encuentra en .Listed
:
<article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>
Hugo también proporciona una funcionalidad de plantilla base que le permite ampliar una plantilla base común , en lugar de incluir subplantillas. En este caso, el contexto funciona de manera similar: al extender una plantilla base, proporciona los datos que constituirán el contexto original en esa plantilla.
Variables personalizadas
También es posible asignar y reasignar sus propias variables personalizadas en una plantilla de Hugo. Estos estarán disponibles en la plantilla en la que se declaran, pero no se incluirán en ninguna plantilla base o parcial a menos que los transmitamos explícitamente. Una variable personalizada declarada dentro de un "bloque" como la especificada por una declaración if
solo estará disponible dentro de ese bloque; si queremos referirnos a ella fuera del bloque, debemos declararla fuera del bloque y luego modificarla dentro del bloque. bloque según sea necesario.
Las variables personalizadas tienen nombres precedidos por un signo de dólar ( $
). Para declarar una variable y darle un valor al mismo tiempo, use el operador :=
. Las asignaciones posteriores a la variable utilizan el operador =
(sin dos puntos). No se puede asignar una variable antes de declararla, y no se puede declarar sin darle un valor.
Un caso de uso para las variables personalizadas es simplificar las llamadas a funciones largas mediante la asignación de algún resultado intermedio a una variable con el nombre apropiado. Por ejemplo, podríamos asignar el objeto de la página destacada a una variable llamada $featured
y luego proporcionar esta variable a la declaración with
. También podríamos poner los datos para suministrar al parcial "listado" en una variable y dárselos a la llamada parcial.
Así es como se vería nuestra plantilla con esos cambios:
<section class="featured"> <div class="container"> {{ $featured := .GetPage .Params.featured }} {{ with $featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> ... </section> <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $context := (dict "Page" $ "Listed" .) }} {{ partial "partials/listed.html" $context }} {{ end }} {{ end }} </div> </div> </section>
Según mi experiencia con Hugo, recomendaría usar variables personalizadas generosamente tan pronto como intente implementar una lógica más complicada en una plantilla. Si bien es natural tratar de mantener su código conciso, esto puede hacer que las cosas sean menos claras de lo que podrían ser, confundiéndolo a usted y a los demás.
En su lugar, utilice variables con nombres descriptivos para cada paso y no se preocupe por utilizar dos líneas (o tres, o cuatro, etc.) donde bastaría con una.
.Rasguño
Finalmente, cubramos el mecanismo .Scratch
. En versiones anteriores de Hugo, las variables personalizadas solo se podían asignar una vez; no fue posible redefinir una variable personalizada. Hoy en día, las variables personalizadas se pueden redefinir, lo que hace que .Scratch
sea menos importante, aunque todavía tiene sus usos.
En resumen, .Scratch
es un área de borrador que le permite establecer y modificar sus propias variables , como variables personalizadas. A diferencia de las variables personalizadas, .Scratch
pertenece al contexto de la página, por lo que pasar ese contexto a un parcial, por ejemplo, traerá consigo las variables temporales automáticamente.
Puede establecer y recuperar variables en .Scratch
llamando a sus métodos Set
y Get
. Hay más métodos que estos, por ejemplo, para configurar y actualizar tipos de datos compuestos, pero estos dos serán suficientes para nuestras necesidades aquí. Set
toma dos parámetros : la clave y el valor de los datos que desea establecer. Get
solo requiere una: la clave de los datos que desea recuperar.
Anteriormente, usamos dict
para crear una estructura de datos de mapa para pasar múltiples datos a un parcial. Esto se hizo para que el parcial de una página enumerada tuviera acceso tanto al contexto de la página original como al objeto de la página enumerada en particular. Usar .Scratch
no es necesariamente una forma mejor o peor de hacer esto; lo que sea preferible puede depender de la situación.
Veamos cómo se vería nuestra plantilla de lista usando .Scratch
en lugar de dict
para pasar datos al parcial. Llamamos a $.Scratch.Get
(nuevamente usando el contexto global) para establecer la variable temporal "listada" en .
— en este caso, el objeto de página listado. Luego pasamos solo el objeto de la página, $
, al parcial. Las variables temporales seguirán automáticamente.
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $.Scratch.Set "listed" . }} {{ partial "partials/listed.html" $ }} {{ end }} {{ end }} </div> </div> </section>
Esto también requeriría alguna modificación en el archivo parcial listed.html
: el contexto de la página original ahora está disponible como "el punto", mientras que la página de la lista se recupera del objeto .Scratch
. Usaremos una variable personalizada para simplificar el acceso a la página listada:
<article{{ if .IsHome }} class="home"{{ end }}> {{ $listed := .Scratch.Get "listed" }} <h2>{{ $listed.Title }}</h2> {{ $listed.Summary }} <p><a href="{{ $listed.Permalink }}">Read more →</a></p> </article>
Un argumento para hacer las cosas de esta manera es la consistencia. Usando .Scratch
, puede acostumbrarse a pasar siempre el objeto de la página actual a cualquier parcial, agregando cualquier dato adicional como variables temporales. Entonces, siempre que escriba o edite sus parciales, sabrá que .
es un objeto de página. Por supuesto, también puede establecer una convención para usted utilizando un mapa pasado: enviar siempre el objeto de la página como .Page
, por ejemplo.
Conclusión
Cuando se trata de contexto y datos, un generador de sitios estáticos trae beneficios y limitaciones. Por un lado, una operación que es demasiado ineficiente cuando se ejecuta para cada visita a la página puede ser perfectamente buena cuando se ejecuta solo una vez mientras se compila la página. Por otro lado, puede que le sorprenda la frecuencia con la que sería útil tener acceso a alguna parte de la solicitud de red, incluso en un sitio predominantemente estático.
Para manejar parámetros de cadena de consulta , por ejemplo, en un sitio estático, tendría que recurrir a JavaScript o alguna solución propietaria como las redirecciones de Netlify. El punto aquí es que si bien el salto de un sitio dinámico a uno estático es simple en teoría, requiere un cambio de mentalidad. Al principio, es fácil volver a los viejos hábitos, pero la práctica hará la perfección.
Con eso, concluimos nuestra mirada a la gestión de datos en el generador de sitios estáticos de Hugo. Even though we focused only on a narrow sector of its functionality, there are certainly things we didn't cover that could have been included. Nevertheless, I hope this article gave you some added insight into how data flows from content files, to templates, to subtemplates and how it can be modified along the way.
Note : If you already have some Hugo experience, we have a nice resource for you, quite appropriately residing on our aforementioned, Hugo-driven “Learn” site! When you just need to check the order of the arguments to the replaceRE
function, how to retrieve the next page in a section, or what the “expiration date” front matter field is called, a cheat sheet comes in handy. We've put together just such a reference, so download a Hugo cheat sheet, in a package also featuring a host of other cheat sheets on everything from Git to the Visual Studio Code editor.
Otras lecturas
If you're looking for more information on Hugo, here are some nice resources:
- The official Hugo documentation is always a good place to start!
- A great series of in-depth posts on Hugo on Regis Philibert's blog.