- Partie 1 : PWA et Drupal, généralités
- Partie 2 : Découplage
- Partie 3 : Exemple de PWA avec Drupal 8 et Gatsby
Nous utiliserons la distribution Contenta qui active par défaut le module JSON:API (intégré au core depuis la version 8.7), mais ce n’est pas absolument nécessaire et un simple Drupal pourrait faire l’affaire.
Pour faire simple, nous utiliserons les contenus de démo installés avec la distribution et plus particulièrement le type de contenu Recipes.
Pré-requis
- Connaître Drupal, la gestion du contenu et l’activation / ajout de modules ;
- Avoir NodeJS (version 10+) installé sur sa machine ;
- Connaître un peu React.
Drupal et Contenta
Nous ne nous attarderons pas ici sur l’installation de cette distribution, vous pouvez la faire en local, sur un serveur à vous ou chez Pantheon par exemple. Par défaut la liste des recettes est accessible à cette URL : http://ndd.tld/api/allRecipes
En voici un extrait :
{
"jsonapi": {
"version": "1.0",
"meta": {
"links": { "self": { "href": "http://jsonapi.org/format/1.0/" } }
}
},
"data": [
{
"type": "recipes",
"id": "b585c399-284f-443f-8440-64df2225ada6",
"attributes": {
"internalId": 66,
"isPublished": true,
"title": "La Trouchia (omelette aux blettes)",
"createdAt": "2019-05-15T16:07:04+0200",
"updatedAt": "2019-05-20T14:55:31+0200",
"isPromoted": true,
"revision_translation_affected": true,
"path": null,
"cookingTime": 10,
"difficulty": "easy",
"ingredients": [
"6 oeufs",
"1 kg de vert de blettes (sans les cotes)",
"100g de parmesan r\u00e2p\u00e9",
"75 de pignon",
"huile d\u0027olive",
"sel et poivre"
],
"numberOfservings": 4,
"preparationTime": 10,
"instructions": "\u003Cp\u003E\u0026nbsp;\u003C/p\u003E\r\n\r\n\u003Cp\u003ERincer abondamment les verts de blettes, \u00e9goutter. Faire tomber dans un faitout , en remuant les blettes avec une fourchette piqu\u00e9e d\u2019une gousse d\u2019ail. A cuisson \u00e9goutter et presser entre les mains pour \u00e9liminer l\u2019humidit\u00e9.\u003C/p\u003E\r\n\r\n\u003Cp\u003EHacher la blette au couteau. Battre les \u0153ufs \u00e0 la fourchette, saler, poivrer, incorporer la blette, le parmesan. Ajouter les pignons juste torr\u00e9fi\u00e9s.\u003C/p\u003E\r\n\r\n\u003Ch3\u003ECuisson\u003C/h3\u003E\r\n\r\n\u003Cp\u003EDans une po\u00eale tenue chaude, verser trois bonnes cuill\u00e8res \u00e0 soupe d\u2019huile d\u2019olive, verser la pr\u00e9paration aux \u0153ufs.\u003C/p\u003E\r\n\r\n\u003Cp\u003E\u0026nbsp;\u003C/p\u003E\r\n\r\n\u003Cp\u003ELaisser cuire \u00e0 feu tr\u00e8s lent en veillant \u00e0 ce que l\u2019omelette n\u2019attache pas.\u003C/p\u003E\r\n\r\n\u003Cp\u003EPuis retourner sur une assiette, huiler encore un peu la po\u00eale et cuire l\u2019autre cot\u00e9.\u003C/p\u003E\r\n\r\n\u003Cp\u003EServir chaud ou ti\u00e8de, accompagn\u00e9 d\u2019une salade de mesclun.\u003C/p\u003E\r\n\r\n\u003Cp\u003EMais aussi emporter en pique nique, sur l\u2019herbe au festin des Mais\u003C/p\u003E\r\n\r\n\u003Cp\u003E\u0026nbsp;\u003C/p\u003E\r\n",
"summary": {
"value": "\u003Cp\u003ELa c\u00e9l\u00e8bre recette de l\u0027omelette ni\u00e7oise\u003C/p\u003E\r\n",
"format": "basic_html",
"processed": "\u003Cp\u003ELa c\u00e9l\u00e8bre recette de l\u0027omelette ni\u00e7oise\u003C/p\u003E"
}
},
"relationships": {...},
"links": {
"self": {
"href": "http://ndd.tld/api/allRecipes/b585c399-284f-443f-8440-64df2225ada6?resourceVersion=id%3A68"
}
}
},
Gatsby
C’est le moment de sortir son terminal et d’installer Gatsby !
npm install -g gatsby-cli
Quelques secondes plus tard, Gatsby est installé et nous pouvons commencer à créer notre premier site
gatsby new pwa3
Déplacez-vous dans ce répertoire et lancez la commande gatsby develop
pour exécuter le serveur de développement.
Vous pouvez voir votre premier site Gatsby
à l’adresse : http://localhost:8000/
En parallèle, lancez votre éditeur de code favori pour faire un petit tour du propriétaire et intéressons nous en premier au répertoire src/pages
qui contient quelques pages statiques.
La page d’accueil étant représentée par le fichier index.js
.
La voici avec quelques petits changements :
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<SEO title="Home" />
<h1>Salut tout le monde</h1>
<p>Bienvenue sur mon premier site Gatsby</p>
<p>Cela manque un peu de recettes, vous ne trouvez pas ?</p>
<div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}>
<Image />
</div>
<Link to="/page-2/">Go to page 2</Link>
</Layout>
)
export default IndexPage
Vous remarquerez que le code HTML important de cette page est inclus dans une balise <Layout>
qui est un component React.
Il se trouve justement dans src/components
!
import React from "react"
import PropTypes from "prop-types"
import { StaticQuery, graphql } from "gatsby"
import Header from "./header"
import "./layout.css"
const Layout = ({ children }) => (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`}
render={data => (
<>
<Header siteTitle={data.site.siteMetadata.title} />
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `0px 1.0875rem 1.45rem`,
paddingTop: 0,
}}
>
<main>{children}</main>
<footer>
© {new Date().getFullYear()}, Built with
{` `}
<a href="https://www.gatsbyjs.org">Gatsby</a>
</footer>
</div>
</>
)}
/>
)
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
On retrouve <main>{children}</main>
qui correspond à l’emplacement prévu pour le code de notre page d’accueil.
On retrouve également un autre composant <Header siteTitle={data.site.siteMetadata.title} />
qui prend en paramètre une étrange propriété (props dans la terminologie React) : {data.site.SiteMetadata.title}
. D’où vient-elle ?
La réponse est un peu plus haut sur la page, il y a un composant <StaticQuery>
qui exécute une requête GraphQL pour récupérer ces fameuses données. Le titre du Header est donc extrait à partir d’une requête.
La source de ce titre se trouve dans src/gatsby-config.js
:
module.exports = {
siteMetadata: {
title: `Gatsby Default Starter`,
description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
author: `Nous-mêmes`,
},
Vous pouvez faire quelques changements et à la sauvegarde du fichier, notez comme le rafraichissement de la page est automatique, merci Gatsby !
Tout ceci semble un peux obscur mais heureusement Gatsby nous fournit également une interface pour tester nos requêtes GraphQL à cette adresse :
http://localhost:8000/_graphql
Dans la colonne de gauche, copier / coller cette requête (qui correspond à notre StaticQuery
et appuyez sur play
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
Le voilà notre SiteMetada.title
:
{
"data": {
"site": {
"siteMetadata": {
"title": "Un site de recettes"
}
}
}
}
Vous remarquerez que le fichier gatsby-config.js
contient d’autres valeurs comme une description et un auteur. Vous voulez les récupérer ? Voici la requête :
query SiteTitleQuery {
site {
siteMetadata {
title,
author,
description
}
}
}
Notez au passage avec bonheur que l’interface vous offre de l’auto-complétion sur les données existantes.
{
"data": {
"site": {
"siteMetadata": {
"title": "Un site de recettes",
"author": "@gatsbyjs",
"description": "Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need."
}
}
}
}
Retournons dans notre fichier src/Layout.js
et modifions la requête avec nos nouvelles données.
const Layout = ({ children }) => (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
author
description
}
}
}
`}
render={data => (
<>
<Header siteTitle={data.site.siteMetadata.title} />
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `0px 1.0875rem 1.45rem`,
paddingTop: 0,
}}
>
<main>{children}</main>
<footer>
© {new Date().getFullYear()}, Built with
{` `}
<a href="https://www.gatsbyjs.org">{data.site.siteMetadata.author}</a>
</footer>
</div>
</>
)}
/>
)
N’est-ce pas magnifique ?
C’est sympa, mais il est où Drupal ?
Gatsby utilise GraphQL pour chercher les données à afficher, il nous faut donc un module pour pouvoir interroger l’API JSON de Contenta et récupérer nos recettes en GraphQL.
Heureusement il y a déjà un plugin pour cela.
Arrêtons notre serveur de développement et installons ce fameux module !
npm install --save gatsby-source-drupal
Après (encore…) quelques instants, le module est installé, mais il faut l’ajouter à gatsby-config.js
, dans la section plugins
.
{
resolve: `gatsby-source-drupal`,
options: {
baseUrl: `http://ndd.tld/`,
apiBase: `api`,
},
},
Lançons de nouveau le serveur de dev avec gatsby develop
et jouons avec l’interface graphQL.
Vous remarquerez que l’auto-complétion nous fournit de nouvelles sources !
{
allRecipes {
edges {
node {
title
}
}
}
}
allRecipes
nous donnant accès à tous les résultats, la requête précédente affiche le titre de l’ensemble des nodes de ce type de contenu.
{
"data": {
"allRecipes": {
"edges": [
{
"node": {
"title": "La Trouchia (omelette aux blettes)"
}
},
{
"node": {
"title": "Fiery chili sauce"
}
},
{
"node": {
"title": "Crema catalana"
}
}
]
}
}
}
Victoire ! Mais comment faire pour utiliser cela dans Gatsby ?
Utilisons la page src/pages/page-2.js
pour nos expérimentations et importons en premier lieu GraphQL en haut de page :
import { graphql } from "gatsby"
Puis créons une variable query
avec notre requête Drupal avec quelques champs en plus (nombre de couverts et instructions) !
import React from "react"
import { Link } from "gatsby"
import { graphql } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"
export const query = graphql`
query {
allRecipes {
edges {
node {
title,
numberOfservings,
instructions
}
}
}
}
`
const SecondPage = ({data}) => (
<Layout>
<SEO title="Page two" />
<h1>{ data.allRecipes.edges[0].node.title }</h1>
<p>Une recette pour { data.allRecipes.edges[0].node.numberOfservings} personne(s).</p>
<h2>Instructions</h2>
<div dangerouslySetInnerHTML={{ __html : data.allRecipes.edges[0].node.instructions }}></div>
<Link to="/">Go back to the homepage</Link>
</Layout>
)
export default SecondPage
C’est basique et ce n’est absolument pas dynamique dans le sens où j’ai spécifié le résultat dans la liste des recettes que je voulais ( edges[Ø]
) mais cela fonctionne.
Nous utilisons une directive propre à Gatsby pour traiter l’HTML brut renvoyé par la requête :
<div dangerouslySetInnerHTML={{ __html: data.allRecipes.edges[0].node.instructions }}></div>
Générer le site
gatsby build
et le site statique généré se trouve dans le répertoire public
.
Dans un prochain article nous verrons comment générer dynamiquement une page pour chaque noeud du type recette et une belle liste des dernières recettes.
Ludovic Coullet