Contexte et variables dans le générateur de site statique Hugo
Publié: 2022-03-10Dans cet article, nous allons examiner de près le fonctionnement du contexte dans le générateur de site statique Hugo. Nous examinerons comment les données circulent du contenu vers les modèles, comment certaines constructions modifient les données disponibles et comment nous pouvons transmettre ces données aux partiels et aux modèles de base.
Cet article n'est pas une introduction à Hugo . Vous en tirerez probablement le meilleur parti si vous avez une certaine expérience avec Hugo, car nous n'allons pas parcourir chaque concept à partir de zéro, mais plutôt nous concentrer sur le sujet principal du contexte et des variables. Cependant, si vous vous référez à la documentation Hugo tout au long, vous pourrez peut-être suivre même sans expérience préalable !
Nous étudierons différents concepts en construisant une page d'exemple. Tous les fichiers requis pour l'exemple de site ne seront pas couverts en détail, mais le projet complet est disponible sur GitHub. Si vous voulez comprendre comment les pièces s'emboîtent, c'est un bon point de départ. Veuillez également noter que nous ne couvrirons pas la configuration d' un site Hugo ou l'exécution du serveur de développement — les instructions pour exécuter l'exemple se trouvent dans le référentiel.
Qu'est-ce qu'un générateur de site statique ?
Si le concept des générateurs de sites statiques est nouveau pour vous, voici une introduction rapide ! Les générateurs de sites statiques sont peut-être mieux décrits en les comparant à des sites dynamiques. Un site dynamique comme un CMS assemble généralement une page à partir de zéro pour chaque visite, peut-être en récupérant des données d'une base de données et en combinant divers modèles pour ce faire. En pratique, l'utilisation de la mise en cache signifie que la page n'est pas régénérée aussi souvent, mais aux fins de cette comparaison, nous pouvons y penser de cette façon. Un site dynamique est bien adapté au contenu dynamique : contenu qui change souvent, contenu qui est présenté dans de nombreuses configurations différentes en fonction de l'entrée et contenu qui peut être manipulé par le visiteur du site.
En revanche, de nombreux sites changent rarement et acceptent peu de commentaires des visiteurs. Une section « aide » pour une application, une liste d'articles ou un livre électronique pourraient être des exemples de tels sites. Dans ce cas, il est plus logique d'assembler les pages finales une fois lorsque le contenu change, puis de servir les mêmes pages à chaque visiteur jusqu'à ce que le contenu change à nouveau.
Les sites dynamiques ont plus de flexibilité, mais sollicitent davantage le serveur sur lequel ils s'exécutent. Ils peuvent également être difficiles à répartir géographiquement, surtout si des bases de données sont impliquées. Les générateurs de sites statiques peuvent être hébergés sur n'importe quel serveur capable de fournir des fichiers statiques et sont faciles à distribuer.
Une solution courante aujourd'hui, qui mélange ces approches, est la JAMstack. "JAM" signifie JavaScript, API et balisage et décrit les éléments constitutifs d'une application JAMstack : un générateur de site statique génère des fichiers statiques à livrer au client, mais la pile a un composant dynamique sous la forme de JavaScript s'exécutant sur le client - ce composant client peut ensuite utiliser des API pour fournir des fonctionnalités dynamiques à l'utilisateur.
Hugo
Hugo est un générateur de site statique populaire. Il est écrit en Go, et le fait que Go soit un langage de programmation compilé fait allusion à certains des avantages et des inconvénients d'Hugo. D'une part, Hugo est très rapide , ce qui signifie qu'il génère très rapidement des sites statiques. Bien sûr, cela n'a aucune incidence sur la rapidité ou la lenteur des sites créés avec Hugo pour l'utilisateur final, mais pour le développeur, le fait qu'Hugo compile même des sites volumineux en un clin d'œil est très précieux.
Cependant, comme Hugo est écrit dans un langage compilé, son extension est difficile . Certains autres générateurs de sites vous permettent d'insérer votre propre code - dans des langages comme Ruby, Python ou JavaScript - dans le processus de compilation. Pour étendre Hugo, vous devez ajouter votre code à Hugo lui-même et le recompiler . Sinon, vous êtes bloqué avec les fonctions de modèle fournies par Hugo.
Bien qu'il offre une riche variété de fonctions, ce fait peut devenir limitant si la génération de vos pages implique une logique compliquée. Comme nous l'avons constaté, ayant un site développé à l'origine sur une plate-forme dynamique, les cas où vous avez pris la possibilité d'ajouter votre code personnalisé pour acquis ont tendance à s'accumuler.
Notre équipe gère une variété de sites Web liés à notre produit principal, le client Tower Git, et nous avons récemment envisagé de déplacer certains d'entre eux vers un générateur de site statique. L'un de nos sites, le site « Apprendre », semblait convenir particulièrement bien à un projet pilote. Ce site contient une variété de supports d'apprentissage gratuits, notamment des vidéos, des livres électroniques et des FAQ sur Git, mais également d'autres sujets techniques.
Son contenu est en grande partie de nature statique, et toutes les fonctionnalités interactives existantes (comme les inscriptions à la newsletter) étaient déjà alimentées par JavaScript. Fin 2020, nous avons converti ce site de notre précédent CMS en Hugo , et aujourd'hui il fonctionne comme un site statique. Naturellement, nous avons beaucoup appris sur Hugo au cours de ce processus. Cet article est un moyen de partager certaines des choses que nous avons apprises.
Notre exemple
Comme cet article est né de notre travail sur la conversion de nos pages vers Hugo, il semble naturel de créer une page de destination hypothétique (très !) simplifiée à titre d'exemple. Notre objectif principal sera un modèle réutilisable dit de "liste".
En bref, Hugo utilisera un modèle de liste pour toute page contenant des sous-pages. Il y a plus dans la hiérarchie des modèles Hugos que cela, mais vous n'avez pas à implémenter tous les modèles possibles. Un modèle de liste unique va très loin. Il sera utilisé dans toute situation nécessitant un modèle de liste où aucun modèle plus spécialisé n'est disponible.
Les cas d'utilisation potentiels incluent une page d'accueil, un index de blog ou une liste de FAQ. Notre modèle de liste réutilisable résidera dans layouts/_default/list.html
dans notre projet. Encore une fois, le reste des fichiers nécessaires à la compilation de notre exemple est disponible sur GitHub, où vous pouvez également mieux voir comment les pièces s'emboîtent. Le référentiel GitHub est également livré avec un modèle single.html
- comme son nom l'indique, ce modèle est utilisé pour les pages qui n'ont pas de sous-pages, mais agissent comme des éléments de contenu à part entière.
Maintenant que nous avons préparé le terrain et expliqué ce que nous allons faire, commençons !
Le contexte ou "le point"
Tout commence par le point. Dans un modèle Hugo, l'objet .
— "le point" — fait référence au contexte actuel. Qu'est-ce que ça veut dire? Chaque modèle rendu dans Hugo a accès à un ensemble de données — son contexte . Ceci est initialement défini sur un objet représentant la page en cours de rendu, y compris son contenu et certaines métadonnées. Le contexte comprend également des variables à l'échelle du site telles que les options de configuration et des informations sur l'environnement actuel. Vous accéderez à un champ comme le titre de la page actuelle en utilisant .Title
et la version de Hugo utilisée via .Hugo.Version
— en d'autres termes, vous accédez aux champs du .
structure.
Il est important de noter que ce contexte peut changer, en faisant une référence comme `.Title` ci-dessus pointer vers autre chose ou même en la rendant invalide. Cela se produit, par exemple, lorsque vous effectuez une boucle sur une collection quelconque à l'aide range
, ou lorsque vous divisez des modèles en partials et en templates de base . Nous verrons cela en détail plus tard !
Hugo utilise le package Go "templates", donc lorsque nous nous référons aux modèles Hugo dans cet article, nous parlons vraiment de modèles Go. Hugo ajoute de nombreuses fonctions de modèle non disponibles dans les modèles Go standard.
À mon avis, le contexte et la possibilité de le relier est l'une des meilleures caractéristiques de Hugo. Pour moi, il est très logique de toujours avoir "le point" représentant n'importe quel objet qui est au centre de mon modèle à un certain point, en le reliant autant que nécessaire au fur et à mesure. Bien sûr, il est également possible de se retrouver dans un désordre emmêlé, mais j'en ai été satisfait jusqu'à présent, dans la mesure où j'ai rapidement commencé à le manquer dans tout autre générateur de site statique que j'ai regardé.
Avec cela, nous sommes prêts à regarder l'humble point de départ de notre exemple - le modèle ci-dessous, résidant dans l'emplacement layouts/_default/list.html
de notre projet :
<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 majeure partie du modèle consiste en une structure HTML simple, avec un lien de feuille de style, un menu pour la navigation et quelques éléments et classes supplémentaires utilisés pour le style. La chose intéressante se trouve entre les accolades , qui signalent à Hugo d'intervenir et de faire sa magie, en remplaçant tout ce qui se trouve entre les accolades par le résultat d'évaluer une expression et de manipuler potentiellement le contexte également.
Comme vous pouvez le deviner, {{ .Title }}
dans la balise title fait référence au titre de la page actuelle, tandis que {{ .Site.Title }}
fait référence au titre de l'ensemble du site, défini dans la configuration Hugo . Une balise comme {{ .Title }}
indique simplement à Hugo de remplacer cette balise par le contenu du champ Title
dans le contexte actuel.
Nous avons donc accédé à certaines données appartenant à la page dans un modèle. D'où viennent ces données ? C'est le sujet de la section suivante.
Contenu et avant-propos
Certaines des variables disponibles dans le contexte sont automatiquement fournies par Hugo. D'autres sont définis par nous, principalement dans les fichiers de contenu . Il existe également d'autres sources de données comme les fichiers de configuration, les variables d'environnement, les fichiers de données et même les API. Dans cet article, nous nous concentrerons sur les fichiers de contenu en tant que source de données.
En général, un seul fichier de contenu représente une seule page. Un fichier de contenu typique comprend le contenu principal de cette page mais également des métadonnées sur la page, comme son titre ou la date de sa création. Hugo supporte plusieurs formats tant pour le contenu principal que pour les métadonnées. Dans cet article, nous utiliserons peut-être la combinaison la plus courante : le contenu est fourni en tant que Markdown dans un fichier contenant les métadonnées en tant que matière première YAML.
En pratique, cela signifie que le fichier de contenu commence par une section délimitée par une ligne contenant trois tirets à chaque extrémité. Cette section constitue l' avant-propos , et ici les métadonnées sont définies à l'aide d'une key: value
(comme nous le verrons bientôt, YAML prend également en charge des structures de données plus élaborées). L'avant-propos est suivi du contenu réel, spécifié à l'aide du langage de balisage Markdown.
Rendons les choses plus concrètes en regardant un exemple. Voici un fichier de contenu très simple avec un champ liminaire et un paragraphe de contenu :
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(Ce fichier réside dans content/_index.md
dans notre projet, avec _index.md
désignant le fichier de contenu d'une page qui a des sous-pages. Encore une fois, le référentiel GitHub indique clairement où le fichier est censé aller.)
Rendu à l'aide du modèle précédent, ainsi que de certains styles et fichiers périphériques (tous trouvés sur GitHub), le résultat ressemble à ceci :
Vous vous demandez peut-être si les noms de champ dans les premières pages de notre fichier de contenu sont prédéterminés ou si nous pouvons ajouter n'importe quel champ que nous aimons. La réponse est "les deux". Il existe une liste de champs prédéfinis, mais nous pouvons également ajouter tout autre champ que nous pouvons proposer. Cependant, ces champs sont accessibles un peu différemment dans le modèle. Alors qu'un champ prédéfini comme title
est accessible simplement en tant que .Title
, un champ personnalisé comme author
est accessible en utilisant .Params.author
.
(Pour une référence rapide sur les champs prédéfinis, ainsi que des éléments tels que les fonctions, les paramètres de fonction et les variables de page, consultez notre propre aide-mémoire Hugo !)
La variable .Content
, utilisée pour accéder au contenu principal du fichier de contenu de votre modèle, est spéciale. Hugo dispose d'une fonction "shortcode" vous permettant d'utiliser des balises supplémentaires dans votre contenu Markdown. Vous pouvez également définir le vôtre. Malheureusement, ces shortcodes ne fonctionneront que via la variable .Content
- alors que vous pouvez exécuter n'importe quel autre élément de données via un filtre Markdown, cela ne gérera pas les shortcodes dans le contenu.
Une note ici à propos des variables indéfinies : l'accès à un champ prédéfini comme .Date
fonctionne toujours, même si vous ne l'avez pas défini — une valeur vide sera renvoyée dans ce cas. L'accès à un champ personnalisé non défini, comme .Params.thisHasNotBeenSet
, fonctionne également, renvoyant une valeur vide. Cependant, l'accès à un champ de niveau supérieur non prédéfini tel que .thisDoesNotExist
empêchera la compilation du site.
Comme indiqué par .Params.author
ainsi que .Hugo.version
et .Site.title
plus tôt, les invocations chaînées peuvent être utilisées pour accéder à un champ imbriqué dans une autre structure de données. Nous pouvons définir de telles structures dans notre introduction. Regardons un exemple, où nous définissons une map , ou un dictionnaire, en spécifiant certaines propriétés pour une bannière sur la page dans notre fichier de contenu. Voici le content/_index.md
:
--- 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!
Maintenant, ajoutons une bannière à notre modèle, en nous référant aux données de la bannière en utilisant .Params
de la manière décrite ci-dessus :
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
Voici à quoi ressemble notre site maintenant :
Bien! Pour le moment, nous accédons aux champs du contexte par défaut sans aucun problème. Cependant, comme mentionné précédemment, ce contexte n'est pas fixe, mais peut changer.
Voyons comment cela pourrait se produire.
Contrôle de flux
Les instructions de contrôle de flux sont une partie importante d'un langage de modèles, vous permettant de faire différentes choses en fonction de la valeur des variables, de parcourir les données, etc. Les modèles Hugo fournissent l'ensemble de constructions attendu, y compris if/else
pour la logique conditionnelle et la range
pour les boucles. Ici, nous n'aborderons pas le contrôle de flux dans Hugo en général (pour en savoir plus, consultez la documentation), mais nous nous concentrerons sur la manière dont ces instructions affectent le contexte. Dans ce cas, les instructions les plus intéressantes sont with
et range
.
Commençons par with
. Cette instruction vérifie si une expression a une valeur "non vide" et, si c'est le cas, relie le contexte pour faire référence à la valeur de cette expression . Une balise de end
indique le point où l'influence de l'instruction with
s'arrête, et le contexte est lié à ce qu'il était avant. La documentation Hugo définit une valeur non vide comme fausse, 0 et tout tableau, tranche, carte ou chaîne de longueur nulle.
Actuellement, notre modèle de liste ne fait pas beaucoup de listes. Il peut être logique qu'un modèle de liste présente certaines de ses sous-pages d'une manière ou d'une autre. Cela nous donne une occasion parfaite pour des exemples de nos déclarations de contrôle de flux.
Peut-être voulons-nous afficher du contenu en vedette en haut de notre page. Il peut s'agir de n'importe quel élément de contenu - un article de blog, un article d'aide ou une recette, par exemple. À l'heure actuelle, supposons que notre site d'exemple Tower ait des pages mettant en évidence ses fonctionnalités, ses cas d'utilisation, une page d'aide, une page de blog et une page "plate-forme d'apprentissage". Ils sont tous situés dans le répertoire content/
. Nous configurons le contenu à présenter en ajoutant un champ dans le fichier de contenu de notre page d'accueil, content/_index.md
. La page est référencée par son chemin, en supposant que le répertoire de contenu est racine, comme ceci :
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
Ensuite, notre modèle de liste doit être modifié pour afficher ce contenu. Hugo a une fonction de modèle, .GetPage
, qui nous permettra de faire référence à des objets de page autres que celui que nous sommes en train de rendre. Rappelez-vous comment le contexte, .
, était initialement lié à un objet représentant la page en cours de rendu ? En utilisant .GetPage
et with
, nous pouvons temporairement relier le contexte à une autre page, en nous référant aux champs de cette page lors de l'affichage de notre contenu :
<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>
Ici, {{ .Title }}
, {{ .Summary }}
et {{ .Permalink }}
entre les balises with
et end
font référence à ces champs dans la page en vedette , et non à la principale en cours de rendu.
En plus d'avoir un élément de contenu en vedette, énumérons quelques autres éléments de contenu plus bas. Tout comme le contenu présenté, les éléments de contenu répertoriés seront définis dans content/_index.md
, le fichier de contenu de notre page d'accueil. Nous ajouterons une liste de chemins de contenu à notre introduction comme celle-ci (dans ce cas, en spécifiant également le titre de la section) :
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
La raison pour laquelle la page de blog a son propre répertoire et un fichier _index.md
est que le blog aura ses propres sous-pages — articles de blog.
Pour afficher cette liste dans notre modèle, nous utiliserons range
. Sans surprise, cette instruction bouclera sur une liste, mais elle reliera également le contexte à chaque élément de la liste à tour de rôle. C'est très pratique pour notre liste de contenu.
Notez que, du point de vue de Hugo, "listing" ne contient que quelques chaînes. Pour chaque itération de la boucle "range", le contexte sera lié à l'une de ces chaînes . Pour accéder à l'objet page réel, nous fournissons sa chaîne de chemin (maintenant la valeur de .
) comme argument à .GetPage
. Ensuite, nous utiliserons à nouveau l'instruction with
pour relier le contexte à l'objet de page répertorié plutôt qu'à sa chaîne de chemin. Désormais, il est facile d'afficher tour à tour le contenu de chaque page répertoriée :
<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>
Voici à quoi ressemble le site à ce stade :
Mais attendez, il y a quelque chose de bizarre dans le modèle ci-dessus - plutôt que d'appeler .GetPage
, nous appelons $.GetPage
. Pouvez-vous deviner pourquoi .GetPage
ne fonctionnerait pas ?
La notation .GetPage
indique que la fonction GetPage
est une méthode du contexte courant. En effet, dans le contexte par défaut, il existe une telle méthode, mais nous venons juste d'aller de l'avant et de changer le contexte ! Lorsque nous appelons .GetPage
, le contexte est lié à une chaîne, qui n'a pas cette méthode. La façon dont nous travaillons autour de cela est le sujet de la section suivante.
Le contexte mondial
Comme vu ci-dessus, il existe des situations où le contexte a été modifié, mais nous aimerions toujours accéder au contexte d'origine. Ici, c'est parce que nous voulons appeler une méthode existant dans le contexte d'origine - une autre situation courante est lorsque nous voulons accéder à une propriété de la page principale en cours de rendu. Pas de problème, il existe un moyen simple de le faire.
Dans un modèle Hugo, $
, connu sous le nom de contexte global , fait référence à la valeur d'origine du contexte - le contexte tel qu'il était lorsque le traitement du modèle a commencé. Dans la section précédente, il a été utilisé pour appeler la méthode .GetPage
même si nous avions lié le contexte à une chaîne. Maintenant, nous allons également l'utiliser pour accéder à un champ de la page en cours de rendu.
Au début de cet article, j'ai mentionné que notre modèle de liste est réutilisable. Jusqu'à présent, nous ne l'avons utilisé que pour la page d'accueil, rendant un fichier de contenu situé dans content/_index.md
. Dans l'exemple de référentiel, il existe un autre fichier de contenu qui sera rendu à l'aide de ce modèle : content/blog/_index.md
. Il s'agit d'une page d'index pour le blog, et tout comme la page d'accueil, elle affiche un élément de contenu en vedette et en répertorie quelques autres - des articles de blog, dans ce cas.
Maintenant, disons que nous voulons afficher le contenu listé légèrement différemment sur la page d'accueil - pas assez pour justifier un modèle séparé, mais quelque chose que nous pouvons faire avec une instruction conditionnelle dans le modèle lui-même. Par exemple, nous afficherons le contenu répertorié dans une grille à deux colonnes, par opposition à une liste à une seule colonne, si nous détectons que nous affichons la page d'accueil.
Hugo est livré avec une méthode de page, .IsHome
, qui fournit exactement la fonctionnalité dont nous avons besoin. Nous gérerons le changement réel de présentation en ajoutant une classe aux éléments de contenu individuels lorsque nous constaterons que nous sommes sur la page d'accueil, permettant à notre fichier CSS de faire le reste.
Nous pourrions, bien sûr, ajouter la classe à l'élément body ou à un élément conteneur à la place, mais cela ne permettrait pas une aussi bonne démonstration du contexte global. Au moment où nous écrivons le code HTML pour le contenu répertorié, .
fait référence à la page répertoriée , mais IsHome
doit être appelé sur la page principale en cours de rendu. Le contexte mondial vient à notre rescousse :
<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>
L'index du blog ressemble à notre page d'accueil, mais avec un contenu différent :
… mais notre page d'accueil affiche désormais son contenu en vedette dans une grille :
Modèles partiels
Lors de la création d'un véritable site Web, il devient rapidement utile de diviser vos modèles en plusieurs parties. Peut-être souhaitez-vous réutiliser une partie particulière d'un modèle, ou peut-être souhaitez-vous simplement diviser un modèle énorme et peu maniable en éléments cohérents. À cette fin, les modèles partiels d'Hugo sont la voie à suivre.
Du point de vue du contexte, l'important ici est que lorsque nous incluons un modèle partiel, nous lui transmettons explicitement le contexte que nous voulons mettre à sa disposition. Une pratique courante consiste à passer dans le contexte tel qu'il est lorsque le partiel est inclus, comme ceci : {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. Si le point ici fait référence à la page en cours de rendu, c'est ce qui sera passé au partiel ; si le contexte a été renvoyé à autre chose, c'est ce qui est transmis.
Vous pouvez, bien sûr, relier le contexte dans des modèles partiels comme dans les modèles normaux. Dans ce cas, le contexte global, $
, fait référence au contexte d'origine transmis au partiel, et non à la page principale en cours de rendu (sauf si c'est ce qui a été transmis).
Si nous voulons qu'un modèle partiel ait accès à une donnée particulière, nous pourrions rencontrer des problèmes si nous ne transmettons que cela au partiel. Rappelez-vous notre problème plus tôt avec l'accès aux méthodes de page après avoir reconnecté le contexte ? Il en va de même pour les partiels , mais dans ce cas, le contexte global ne peut pas nous aider — si nous avons passé, disons, une chaîne à un modèle partiel, le contexte global dans le partiel fera référence à cette chaîne, et nous avons gagné 't être en mesure d'appeler les méthodes définies sur le contexte de la page.
La solution à ce problème consiste à transmettre plus d'un élément de données lors de l'inclusion du partiel. Cependant, nous ne sommes autorisés à fournir qu'un seul argument à l'appel partiel. Nous pouvons cependant faire de cet argument un type de données composé, généralement une carte (appelée dictionnaire ou hachage dans d'autres langages de programmation).
Dans cette carte, nous pouvons, par exemple, avoir une clé de Page
définie sur l'objet de page actuel, ainsi que d'autres clés pour toute donnée personnalisée à transmettre. L'objet de page sera alors disponible en tant que .Page
dans le partiel, et l'autre les valeurs de la carte sont accessibles de la même manière. Une carte est créée à l'aide de la fonction de modèle dict
, qui prend un nombre pair d'arguments, interprétés alternativement comme une clé, sa valeur, une clé, sa valeur, etc.
Dans notre modèle d'exemple, déplaçons le code de notre contenu présenté et répertorié dans des partiels. Pour le contenu en vedette, il suffit de passer l'objet de la page en vedette. Le contenu répertorié, cependant, a besoin d'accéder à la méthode .IsHome
en plus du contenu répertorié particulier en cours de rendu. Comme mentionné précédemment, bien que .IsHome
soit également disponible sur l'objet page pour la page répertoriée, cela ne nous donnera pas la bonne réponse - nous voulons savoir si la page principale en cours de rendu est la page d'accueil.
Nous pourrions à la place transmettre un ensemble booléen au résultat de l'appel de .IsHome
, mais peut-être que le partiel aura besoin d'accéder à d'autres méthodes de page à l'avenir — allons-y en transmettant l'objet de page principal ainsi que l'objet de page répertorié. Dans notre exemple, la page principale se trouve dans $
et la page répertoriée dans .
. Ainsi, dans la carte transmise au partiel listed
, la clé Page
prend la valeur $
tandis que la clé « Listed » prend la valeur .
. Voici le modèle principal mis à jour :
<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>
Le contenu de notre partiel "en vedette" ne change pas par rapport au moment où il faisait partie du modèle de liste :
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
Notre partiel pour le contenu listé, cependant, reflète le fait que l'objet de la page d'origine se trouve maintenant dans .Page
tandis que le contenu listé se trouve dans .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 fournit également une fonctionnalité de modèle de base qui vous permet d' étendre un modèle de base commun , au lieu d'inclure des sous-modèles. Dans ce cas, le contexte fonctionne de la même manière : lors de l'extension d'un modèle de base, vous fournissez les données qui constitueront le contexte d'origine dans ce modèle.
Variables personnalisées
Il est également possible d'affecter et de réaffecter vos propres variables personnalisées dans un modèle Hugo. Ceux-ci seront disponibles dans le modèle où ils sont déclarés, mais ne feront pas leur chemin dans les partiels ou les modèles de base à moins que nous ne les transmettions explicitement. Une variable personnalisée déclarée à l'intérieur d'un "bloc" comme celle spécifiée par une instruction if
ne sera disponible qu'à l'intérieur de ce bloc - si nous voulons nous y référer en dehors du bloc, nous devons la déclarer en dehors du bloc, puis la modifier à l'intérieur du bloquer au besoin.
Les variables personnalisées ont des noms précédés du signe dollar ( $
). Pour déclarer une variable et lui donner une valeur en même temps, utilisez l'opérateur :=
. Les affectations suivantes à la variable utilisent l'opérateur =
(sans deux-points). Une variable ne peut pas être assignée à avant d'être déclarée, et elle ne peut pas être déclarée sans lui donner une valeur.
Un cas d'utilisation des variables personnalisées consiste à simplifier les appels de fonction longs en affectant un résultat intermédiaire à une variable nommée de manière appropriée. Par exemple, nous pourrions affecter l'objet de la page en vedette à une variable nommée $featured
, puis fournir cette variable à l'instruction with
. Nous pourrions également mettre les données à fournir au partiel "listé" dans une variable et les donner à l'appel partiel.
Voici à quoi ressemblerait notre modèle avec ces modifications :
<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>
D'après mon expérience avec Hugo, je vous recommande d'utiliser généreusement les variables personnalisées dès que vous essayez d'implémenter une logique plus complexe dans un modèle. Bien qu'il soit naturel d'essayer de garder votre code concis, cela peut facilement rendre les choses moins claires qu'elles ne pourraient l'être, vous déroutant ainsi que les autres.
Au lieu de cela, utilisez des variables nommées de manière descriptive pour chaque étape et ne vous inquiétez pas d'utiliser deux lignes (ou trois, ou quatre, etc.) là où une seule ferait l'affaire.
.Rayure
Enfin, couvrons le mécanisme .Scratch
. Dans les versions antérieures de Hugo, les variables personnalisées ne pouvaient être affectées qu'une seule fois ; il n'était pas possible de redéfinir une variable personnalisée. De nos jours, les variables personnalisées peuvent être redéfinies, ce qui rend .Scratch
moins important, bien qu'il ait toujours son utilité.
En bref, .Scratch
est une zone de travail vous permettant de définir et de modifier vos propres variables , comme les variables personnalisées. Contrairement aux variables personnalisées, .Scratch
appartient au contexte de la page, donc transmettre ce contexte à un partiel, par exemple, amènera automatiquement les variables scratch avec lui.
Vous pouvez définir et récupérer des variables sur .Scratch
en appelant ses méthodes Set
et Get
. Il existe plus de méthodes que celles-ci, par exemple pour définir et mettre à jour des types de données composés, mais ces deux méthodes suffiront à nos besoins ici. Set
prend deux paramètres : la clé et la valeur des données que vous souhaitez définir. Get
n'en prend qu'une : la clé des données que vous souhaitez récupérer.
Auparavant, nous dict
pour créer une structure de données de carte afin de transmettre plusieurs éléments de données à un partiel. Cela a été fait pour que le partiel d'une page répertoriée ait accès à la fois au contexte de la page d'origine et à l'objet de page répertorié particulier. L'utilisation .Scratch
n'est pas nécessairement une meilleure ou une pire façon de le faire - celle qui est préférable peut dépendre de la situation.
Voyons à quoi ressemblerait notre modèle de liste en utilisant .Scratch
au lieu de dict
pour transmettre des données au partiel. Nous appelons $.Scratch.Get
(à nouveau en utilisant le contexte global) pour définir la variable scratch « listée » sur .
— dans ce cas, l'objet de page listé. Ensuite, nous passons uniquement l'objet page, $
, au partiel. Les variables scratch suivront automatiquement.
<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>
Cela nécessiterait également une modification de la listed.html
- le contexte de la page d'origine est désormais disponible en tant que "point" tandis que la page répertoriée est récupérée à partir de l'objet .Scratch
. Nous allons utiliser une variable personnalisée pour simplifier l'accès à la page répertoriée :
<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 argument pour faire les choses de cette façon est la cohérence. En utilisant .Scratch
, vous pouvez prendre l'habitude de toujours passer l'objet de la page en cours à n'importe quel partiel, en ajoutant des données supplémentaires en tant que variables scratch. Ensuite, chaque fois que vous écrivez ou éditez vos partiels, vous savez que .
est un objet page. Bien sûr, vous pouvez également établir une convention pour vous-même en utilisant une carte transmise : toujours envoyer l'objet page en tant que .Page
, par exemple.
Conclusion
En ce qui concerne le contexte et les données, un générateur de site statique présente à la fois des avantages et des limites. D'une part, une opération qui est trop inefficace lorsqu'elle est exécutée pour chaque visite de page peut être parfaitement bonne lorsqu'elle n'est exécutée qu'une seule fois lorsque la page est compilée. D'un autre côté, vous serez peut-être surpris de la fréquence à laquelle il serait utile d'avoir accès à une partie de la requête réseau, même sur un site à prédominance statique.
Pour gérer les paramètres de chaîne de requête , par exemple, sur un site statique, vous devrez recourir à JavaScript ou à une solution propriétaire comme les redirections de Netlify. Le point ici est que si le passage d'un site dynamique à un site statique est simple en théorie, il faut un changement d'état d'esprit. Au début, il est facile de se rabattre sur ses anciennes habitudes, mais la pratique rendra parfait.
Avec cela, nous concluons notre regard sur la gestion des données dans le générateur de site statique 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.
Lectures complémentaires
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.