Comment construire un PWA à partir de zéro avec HTML, CSS et JavaScript

Jan 31, 2020☕ ☕ 13 min Follow me on Twitter

Subscribe to receive the free weekly article

Les applications Web progressives sont un moyen d'apporter le sentiment d'une application native dans des applications Web normales ou traditionnelles. En effet, avec les PWA, nous pouvons désormais améliorer notre site Web avec des fonctionnalités d'application mobile qui augmentent considérablement la convivialité et offrent une excellente expérience utilisateur à nos utilisateurs finaux.

Dans cet article, nous allons créer un PWA à partir de zéro avec HTML, CSS et JavaScript. Nous allons commencer par une question importante: qu'est-ce qu'un PWA?

Qu'est-ce qu'un Progressive Web App (PWA)?

Une application Web progressive est une application Web qui offre aux utilisateurs une expérience semblable à une application en utilisant des capacités Web modernes. En fin de compte, c'est juste votre site Web habituel qui s'exécute dans un navigateur avec quelques améliorations comme la capacité:

  • A l'installer sur un écran d'accueil mobile
  • A y accéder hors ligne
  • A accéder à la caméra
  • Recevoir des notifications push
  • A faire la synchronisation en arrière-plan

Et bien plus encore.

Cependant, pour pouvoir transformer notre application Web traditionnelle en PWA, nous devons l'ajuster, en ajoutant un fichier manifeste d'application Web et un service worker.

Ne vous inquiétez avec ces nouvelles termes, nous les couvrirons plus tard.

Mais d'abord, nous devons créer notre application Web ou notre application Web traditionnelle si vous voulez.

Alors commençons donc avec le balisage.

Balisage

Le fichier HTML est relativement simple. Nous enveloppons tout nos éléments avec la balise main.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link rel="stylesheet" href="css/style.css" />
    <title>Dev'Coffee PWA</title>
  </head>
  <body>
    <main>
      <nav>
        <h1>Dev'Coffee</h1>
        <ul>
          <li>Home</li>
          <li>About</li>
          <li>Blog</li>
        </ul>
      </nav>
      <div class="container"></div>
    </main>
    <script src="js/app.js"></script>
  </body>
</html>

Et on crée une barre de navigation avec la balise nav. Ensuite, le div avec la classe .container contiendra plus tard nos cartes ajoutées avec JavaScript.

Avec cela, nous pouvons maintenant le styliser avec CSS.

Stylisation

Comme d'habitude, nous commençons par importer la police nécessaire et nous effectuons quelques réinitialisations pour éviter le comportement par défaut de CSS.

@import url("https://fonts.googleapis.com/css?family=Nunito:400,700&display=swap");
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  background: #fdfdfd;
  font-family: "Nunito", sans-serif;
  font-size: 1rem;
}
main {
  max-width: 900px;
  margin: auto;
  padding: 0.5rem;
  text-align: center;
}
nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
ul {
  list-style: none;
  display: flex;
}

li {
  margin-right: 1rem;
}
h1 {
  color: #e74c3c;
  margin-bottom: 0.5rem;
}

Ensuite, nous limitons la largeur maximale de l'élément main à 900px, pour lui donner une belle apparence sur un grand écran.

Pour la barre de navigation, je veux que le logo soit à gauche et les liens à droite. Par conséquent, pour la balise nav, après en avoir fait un conteneur flexible, nous utilisons justify-content: space-between; pour les aligner.

.container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  grid-gap: 1rem;
  justify-content: center;
  align-items: center;
  margin: auto;
  padding: 1rem 0;
}
.card {
  display: flex;
  align-items: center;
  flex-direction: column;
  width: 15rem auto;
  height: 15rem;
  background: #fff;
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.19), 0 6px 6px rgba(0, 0, 0, 0.23);
  border-radius: 10px;
  margin: auto;
  overflow: hidden;
}
.card--avatar {
  width: 100%;
  height: 10rem;
  object-fit: cover;
}
.card--title {
  color: #222;
  font-weight: 700;
  text-transform: capitalize;
  font-size: 1.1rem;
  margin-top: 0.5rem;
}
.card--link {
  text-decoration: none;
  background: #db4938;
  color: #fff;
  padding: 0.3rem 1rem;
  border-radius: 20px;
}

Nous aurons plusieurs cartes à afficher, donc, pour l'élément conteneur, il sera affiché sous forme de grille. Et, avec grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)), nous pouvons maintenant rendre nos cartes réactives et les laisser utiliser au moins 15rem comme largeur s'il y a suffisamment d'espace et 1fr sinon.

Et pour les rendre plus belles, nous doublons l'effet d'ombre sur la classe .card et utilisons object-fit: cover sur .card-avatar pour empêcher l'étirement de l'image.

Maintenant, cela semble beaucoup mieux, mais nous n'avons toujours pas de données à afficher.

Corrigeons-le dans la section suivante

Afficher les données avec JavaScript

Notez que j'ai utilisé de grandes images qui prennent un certain temps à charger. Pour vous montrer de la meilleure façon le pouvoir du service worker.

Comme je l'ai dit plus tôt, la classe .container détiendra nos cartes. Par conséquent, nous devons le sélectionner.

const container = document.querySelector(".container")
const coffees = [
  { name: "Perspiciatis", image: "images/coffee1.jpg" },
  { name: "Voluptatem", image: "images/coffee2.jpg" },
  { name: "Explicabo", image: "images/coffee3.jpg" },
  { name: "Rchitecto", image: "images/coffee4.jpg" },
  { name: " Beatae", image: "images/coffee5.jpg" },
  { name: " Vitae", image: "images/coffee6.jpg" },
  { name: "Inventore", image: "images/coffee7.jpg" },
  { name: "Veritatis", image: "images/coffee8.jpg" },
  { name: "Accusantium", image: "images/coffee9.jpg" },
]

Ensuite, nous créons un tableau de cartes avec des noms et des images.

const showCoffees = () => {
  let output = ""
  coffees.forEach(
    ({ name, image }) =>
      (output += `
              <div class="card">
                <img class="card--avatar" src=${image} />
                <h1 class="card--title">${name}</h1>
                <a class="card--link" href="#">Taste</a>
              </div>
              `)
  )
  container.innerHTML = output
}

document.addEventListener("DOMContentLoaded", showCoffees)

Avec ce code ci-dessus, nous pouvons maintenant parcourir le tableau et les afficher dans le fichier HTML. Et pour que tout fonctionne, nous attendons que le contenu DOM (modèle d'objet de document) ait fini de se charger pour exécuter la méthode showCoffees.

Nous avons beaucoup fait, mais pour l'instant, nous avons juste une application web traditionnelle. Changeons cela dans la section suivante en introduisant le PWA.

Manifeste de l'application Web

Le manifeste de l'application Web est un simple fichier JSON qui informe le navigateur de votre application Web et de son comportement lorsqu'elle est installée sur l'appareil mobile ou le bureau de l'utilisateur. Le manifeste de l'application Web est requis pour ajouter l'application à l'écran d'accueil.

Maintenant que nous savons ce qu'est un manifeste Web, créons un nouveau fichier nommé manifest.json (vous devez le nommer comme ça) dans le répertoire racine et ajoutez ce bloc de code ci-dessous.

{
  "name": "Dev'Coffee",
  "short_name": "DevCoffee",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fdfdfd",
  "theme_color": "#db4938",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "/images/icons/icon-72x72.png",
      "type": "image/png", "sizes": "72x72"
    },
    {
      "src": "/images/icons/icon-96x96.png",
      "type": "image/png", "sizes": "96x96"
    },
    {
      "src": "/images/icons/icon-128x128.png",
      "type": "image/png","sizes": "128x128"
    },
    {
      "src": "/images/icons/icon-144x144.png",
      "type": "image/png", "sizes": "144x144"
    },
    {
      "src": "/images/icons/icon-152x152.png",
      "type": "image/png", "sizes": "152x152"
    },
    {
      "src": "/images/icons/icon-192x192.png",
      "type": "image/png", "sizes": "192x192"
    },
    {
      "src": "/images/icons/icon-384x384.png",
      "type": "image/png", "sizes": "384x384"
    },
    {
      "src": "/images/icons/icon-512x512.png",
      "type": "image/png", "sizes": "512x512"
    }
  ]
}

En fin de compte, c'est juste un fichier JSON avec des propriétés obligatoires et facultatives.

  • name: lorsque le navigateur lance l'écran de démarrage, ce sera le nom affiché à l'écran.
  • short_name: Ce sera le nom affiché sous le raccourci de votre application sur l'écran d'accueil.
  • start_url: Ce sera la page affichée à l'utilisateur lorsque votre application sera ouverte.
  • display: il indique au navigateur comment afficher l'application. Il y a plusieurs modes comme minimal-ui,fullscreen, browser etc.   Ici, nous utilisons le mode autonome pour masquer tout ce qui concerne le navigateur.
  • background_color: Lorsque le navigateur lance l'écran de démarrage, ce sera l'arrière-plan de l'écran.
  • theme_color: Ce sera la couleur de l'arrière-plan de la barre d'état lorsque nous allons ouvrir l'application.
  • orientation: il indique au navigateur l'orientation à avoir lors de l'affichage de l'application.
  • icons: lorsque le navigateur lance l'écran de démarrage, ce sera l'icône affichée à l'écran. Ici, j'ai utilisé toutes les tailles pour s'adapter à l'icône préférée de n'importe quel appareil. Mais vous pouvez simplement en utiliser un ou deux. C'est à vous de voir.

Maintenant que nous avons un manifeste d'application web, ajoutons-le au fichier HTML.

  • Dans index.html (balise head)
<link rel="manifest" href="manifest.json" />
<!-- ios support -->
<link rel="apple-touch-icon" href="images/icons/icon-72x72.png" />
<link rel="apple-touch-icon" href="images/icons/icon-96x96.png" />
<link rel="apple-touch-icon" href="images/icons/icon-128x128.png" />
<link rel="apple-touch-icon" href="images/icons/icon-144x144.png" />
<link rel="apple-touch-icon" href="images/icons/icon-152x152.png" />
<link rel="apple-touch-icon" href="images/icons/icon-192x192.png" />
<link rel="apple-touch-icon" href="images/icons/icon-384x384.png" />
<link rel="apple-touch-icon" href="images/icons/icon-512x512.png" />
<meta name="apple-mobile-web-app-status-bar" content="#db4938" />
<meta name="theme-color" content="#db4938" />

Comme vous pouvez le voir, nous avons lié notre fichier manifest.json à la balise head. Et ajoutez quelques autres liens qui gèrent le support IOS pour afficher les icônes et coloriser la barre d'état avec notre couleur de thème.

Avec cela, nous pouvons maintenant plonger dans la dernière partie et présenter le service worker.

Qu'est-ce qu'un service worker?

Notez que les PWA s'exécutent uniquement sur https car le service worker peut accéder à la requête et le gérer. Par conséquent, la sécurité est requise.

Un service worker est un script que votre navigateur exécute en arrière-plan dans un fil séparé. Cela signifie qu'il s'exécute dans un endroit différent, il est complètement séparé de votre page Web. C'est la raison pour laquelle il ne peut pas manipuler votre élément DOM.

Cependant, il est super puissant. Le service worker peut intercepter et gérer les demandes réseau, gérer le cache pour activer la prise en charge hors ligne ou envoyer des notifications push à vos utilisateurs.

Cela étant dit, créons notre tout premier service worker dans le dossier racine et appelons-le serviceWorker.js (le nom dépend de vous). Mais vous devez le mettre à la racine pour ne pas limiter sa portée à un dossier.

Mettre en cache les actifs

  • Dans serviceWorker.js
const staticDevCoffee = "dev-coffee-site-v1"
const assets = [
  "/",
  "/index.html",
  "/css/style.css",
  "/js/app.js",
  "/images/coffee1.jpg",
  "/images/coffee2.jpg",
  "/images/coffee3.jpg",
  "/images/coffee4.jpg",
  "/images/coffee5.jpg",
  "/images/coffee6.jpg",
  "/images/coffee7.jpg",
  "/images/coffee8.jpg",
  "/images/coffee9.jpg",
]

self.addEventListener("install", installEvent => {
  installEvent.waitUntil(
    caches.open(staticDevCoffee).then(cache => {
      cache.addAll(assets)
    })
  )
})

A première vue, ce fichier a l'air intimidant mais c'est juste du JavaScript (ne vous inquiétez pas).

Nous déclarons le nom de notre cache staticDevCoffee et les actifs à stocker dans le cache. Et pour effectuer cette action, nous devons attacher un écouteur à self.

self est le service worker lui-même. Nous pouvons écouter les événements du cycle de vie avec self et faire quelque chose.

Le service worker a plusieurs cycles de vie, et l'un d'eux est l'événement install. Il s'exécute lorsqu'un service worker est installé. Il est déclenché dès que le service s'exécute et il n'est appelé qu'une seule fois par service worker.

Lorsque l'événement install est déclenché, nous exécutons le callback et avons maintenant accès à l'objet event.

La mise en cache de quelque chose sur le navigateur peut prendre un certain temps car elle est asynchrone.

Donc, pour le gérer, nous devons utiliser waitUntil() comme vous pouvez le deviner, pour attendre que l'action se termine.

Une fois l'API de cache prête, nous pouvons maintenant exécuter la méthode open() et créer notre cache en passant son nom comme argument à caches.open(staticDevCoffee).

Ensuite, il renvoie une promesse, ce qui nous aide à stocker nos actifs dans le cache avec cache.addAll(assets).

cache-image

J'espère que tu es toujours avec moi.

Maintenant, nous avons réussi à mettre nos ressources en cache dans le navigateur. Et la prochaine fois que nous chargerons la page, le service worker traitera la demande et récupérera le cache si nous sommes hors ligne.

Alors, allons chercher notre cache.

Récupérer les actifs

  • Dans serviceWorker.js
self.addEventListener("fetch", fetchEvent => {
  fetchEvent.respondWith(
    caches.match(fetchEvent.request).then(res => {
      return res || fetch(fetchEvent.request)
    })
  )
})

Ici, nous utilisons l'événement fetch pour, eh bien, récupérer nos données. La fonction nous donne accès à fetchEvent, puis nous attachons respondWith() pour empêcher la réponse par défaut du navigateur et à la place de renvoyer une promesse. Parce que l'action de récupération peut prendre du temps.

Et une fois le cache prêt, nous appliquons caches.match(fetchEvent.request). Il vérifiera si quelque chose dans le cache correspond à fetchEvent.request. En passant, fetchEvent.request n'est que notre tableau d'actifs.

Ensuite, il renvoie une promesse, et enfin, nous renvoyons le résultat s'il existe ou la récupération initiale.

Désormais, nos ressources peuvent être mises en cache et récupérées par le service worker, ce qui augmente considérablement le temps de chargement de nos images.

Et surtout, il rend notre application disponible en mode hors ligne.

Un service worker seul, ne peut faire le travail, nous devons donc l'enregistrer.

Enregistrer le service worker

  • Dans js / app.js
if ("serviceWorker" in navigator) {
  window.addEventListener("load", function() {
    navigator.serviceWorker
      .register("/serviceWorker.js")
      .then(res => console.log("service worker registered"))
      .catch(err => console.log("service worker not registered", err))
  })
}

Ici, nous commençons par vérifier si le serviceWorker est pris en charge par le navigateur actuel. Parce qu'il n'est toujours pas pris en charge par tous les navigateurs.

Ensuite, nous écoutons l'événement de chargement de page pour enregistrer notre service worker en passant en paramètre le nom de notre fichier serviceWorker.js à navigator.serviceWorker.register() pour l'enregistrer.

Avec ce petit changement, nous avons maintenant fini de transformer notre application Web traditionnelle en PWA.

Dernières pensées

Tout au long de cet article, nous avons vu à quel point un PWA peut être incroyable. En ajoutant un fichier manifeste d'application Web et un service worker, on augmente considérablement l'expérience utilisateur de notre application Web traditionnelle. Étant donné que les PWA sont rapides, sécurisés, fiables et les plus important, ils prennent en charge le mode hors ligne.

De nombreux frameworks viennent maintenant avec un fichier de service worker déjà configuré pour nous, cependant, savoir comment l'implémenter avec Vanilla JavaScript peut vraiment vous aider à comprendre les PWA.

Et vous pouvez aller encore plus loin avec les service workers en mettant en cache les actifs de manière dynamique ou en limitant la taille de votre cache etc. Sur ce, merci d'avoir lu cet article.

Vous pouvez le visualiser en direct ici

Code source ici

Prochaines étapes

Documentation du manifeste Web

Documentation de Service Worker

Générateur de manifeste Web

Prise en charge du navigateur

#html#css#javascript

Support my work

Get articles in your inbox