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?
Sorry for the interrupt!
If you're interested in learning JavaScript in a comprehensive way, I highly recommend this course: JavaScript - The Complete Guide 2020 (Beginner + Advanced)
It's an affiliate link, so by purchasing, you support the blog at the same time.- Qu'est-ce qu'un Progressive Web App (PWA)?
- Balisage
- Stylisation
- Afficher les données avec JavaScript
- Manifeste de l'application Web
- Enregistrer le service worker
- Dernières pensées
- Prochaines étapes
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)
.
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