I. Le pourquoi des PWA▲
Si avez plusieurs versions (IOS, Android, Web…) d'une même application, vous avez probablement dû constater l'effort requis pour maintenir et faire évoluer toutes les versions. Avec les PWA, il n'y a qu'une seule application qui fonctionne pour toutes les plates-formes, et qui est accessible à partir d'un lien dans le navigateur. Les PWA doivent en principe être conçues en utilisant une approche « Mobile First ». Elles peuvent être installées, mais marchent aussi bien comme des sites réguliers. Google a créé un site web dédié aux PWA et présente différents cas d'entreprises qui ont profité de la conversion de leurs applications grâce à des PWA.
II. Les caractéristiques des PWA▲
Dans une de ses présentations, Rob Dodson, un developer advocate chez Google, a mis en évidence les différentes caractéristiques d'une PWA :
- responsive : s'adapte aux dispositifs ;
- se charge rapidement : optimisée pour se charger ou se peindre rapidement ;
- utilisable hors-ligne : l'utilisation des services workers pour la mise en cache de l'application et de son contenu afin de permettre l'utilisation de l'application en mode hors connexion ou lorsque la connectivité réseau est lente ;
- installable : l'application peut être installée dans l'écran d'accueil (comme une application native) ;
- engageante : l'application informe l'utilisateur des mises à jour, des nouvelles… en utilisant les notifications push.
Maintenant que nous savons à quoi ressemble une application Web progressive, nous pouvons commencer à chercher les différents outils qui peuvent nous aider à rendre notre application GWT progressive. Le but de ce tutoriel n'est pas de répéter ce qui a déjà été dit sur les PWA, mais de l'appliquer dans le contexte d'une application GWT.
II-A. Recettes GWT pour les PWA▲
II-A-1. Responsive▲
Pour rendre votre application GWT responsive, plusieurs options s'offrent à vous. Si vous avez des compétences de conception, vous pouvez faire votre propre code GWT et CSS. Sinon, Bootstrap pour GWT est la première chose qui vient à l'esprit. Une autre alternative est le framework GWTMaterialDesign qui fournit des éléments « material design » prêts à être utilisés dans votre application. Enfin, gwt-polymer-elements, la version GWT de Polymer, fournit également des composants Web responsive, et peut être pratique dans la conception de la construction d'une application responsive.
II-A-2. Chargement rapide▲
Pour réduire le temps de la première peinture, il y a un certain nombre de choses qui peuvent être faites. Tout d'abord, le Code Splitting peut être utilisé pour réduire la taille du fichier du module GWT. Il divise le module en fragments permettant au module GWT de télécharger uniquement ceux nécessaires au premier démarrage. Deuxièmement, la méthode de l'app shell, comme définie par les spécifications, peut être appliquée à une application GWT. Cela peut être fait en identifiant les éléments et données statiques dans le code Java de l'application et en les mettant directement dans le point d'entrée .html.
Par exemple :
une pratique courante parmi les développeurs GWT est de laisser le .html vide et d'initialiser les vues depuis l'application :
2.
3.
4.
5.
6.
7.
8.
9.
<body>
</body>
```
```java
AppMainView view = AppMainView();
RootPanel.get().add(view);
Bien qu'il n'y ait rien de mal avec cette pratique, elle peut causer le ralentissement du temps de chargement de l'application, car le fichier .js du module aura plus d'instructions. Comme remède, nous pouvons essayer d'identifier tous les éléments statiques dans nos vues et de les mettre dans le .html, puis nous pouvons charger les vues individuelles depuis le code :
2.
3.
4.
5.
6.
//...
MenuView menu =
new
MenuMeview
(
);
ContentView content =
new
ContentView
(
);
RootPanel.get
(
"menu"
).add
(
menu);
RootPanel.get
(
"mainContent"
).add
(
content);
Cela est, bien sûr, un exemple simplifié. Nous avons vu jusqu'à présent comment le code splitting et la méthode app shell peut réduire le temps de chargement d'une application GWT. L'attribut async du tag script offert par HTML5 peut aussi rendre le chargement de la page plus rapide. L'attribut async indique au navigateur de ne pas se bloquer pendant que le script se charge et s'exécute. Ce n'est pas vraiment spécifique à GWT, mais on sait que la taille d'un module GWT peut être considérable, et donc cela peut s'avérer utile.
<script type
=
"text/javascript"
language
=
"javascript"
src
=
"polymerstarter/polymerstarter.nocache.js"
async />
II-A-3. Utilisable hors-ligne▲
Cela peut être fait en utilisant principalement les services workers du navigateur. Il n'y a pas de bibliothèques officielles GWT pour interagir avec les services workers. Même la bibliothèque gwt-polymer-elements n'enveloppe pas les Platinum Elements qui sont les éléments Polymer destinés à interagir avec les services workers. Les utilisateurs GWT devront écrire du JavaScript manuellement pour mettre en œuvre le mécanisme de mise en cache pour les ressources de l'application. JSNI ou Jsinterop peuvent être utilisés pour interagir avec le navigateur et appeler des services workers. Le script des SW qui définit les événements de mise en cache doit être dans un script séparé et donc, pour l'instant, il n'est pas évident de mélanger à la fois le code du SW et le code du module GWT d'application dans le même fichier. La seule tâche qui peut être faite à partir de GWT est l'enregistrement du SW. Nous allons démontrer cela dans la section suivante. Il est également important de noter que les SW ne sont pas disponibles sur tous les navigateurs, vous pouvez trouver plus de détails dans la documentation de l'API de Mozilla.
Pour plus de détails sur la façon de cacher les données, les ressources et les données d'une application en utilisant les SW, Google fournit quelques recommandations utiles : https://developers.google.com/web/fundamentals/getting-started/your-first-progressive-web-app/step-04?hl=en.
II-A-4. Installable▲
Cette recette n'est pas spécifique à GWT. Pour rendre une application Web installable, vous devez rajouter un fichier JSON appelé « app manifest » et le relier au point d'entrée .html :
<link rel
=
"manifest"
href
=
"manifest.json"
>
Vous pouvez consulter les spécifications de W3C pour les instructions sur la façon d'écrire le manifest. Vous pouvez également utiliser cet outil en ligne : http://brucelawson.github.io/manifest/ qui génère votre manifest pour vous, mais votre application doit déjà être en ligne pour pouvoir l'utiliser. Vous pouvez soit utiliser une bannière pour demander à l'utilisateur d'installer l'application, soit le laisser le faire manuellement à partir des options du navigateur. Cependant, ce serait bien d'avoir un outil qui permette de générer le manifest à partir du code GWT.
#4 Engageante :
Encore une fois, il n'y a pas de bibliothèque de notification push officielle pour GWT. Cela peut être un appel à la communauté GWT pour combler cette lacune. Jusque-là, les utilisateurs peuvent utiliser soit JSNI, soit Jsinterop pour interagir avec le service worker responsable des notifications push.
III. Application Demo▲
Pour illustrer les caractéristiques ci-dessus, nous avons construit une application en utilisant gwt-polymer-elements et gwty-leaflet. L'application affiche les cartes préférées de l'utilisateur.
Code source: https://github.com/gwidgets/gwt-pwa-demo live: https://gwt-pwa-demo.herokuapp.com/pwademo.html
En utilisant Polymer, notre application est responsive par défaut.
Pour réduire le temps de chargement de l'application, nous avons tout d'abord identifié tout le HTML statique que nous avons mis dans le point d'entrée .html: https://github.com/gwidgets/gwt-pwa-demo/blob/master/src/main/webapp/pwademo.html.
On a utilisé Polymer elemental pour interagir avec les éléments du DOM. Par exemple :
2.
PaperMenuLEement paperMenu =
(
PaperMenuElement) Polymer.getDocument
(
).getElementById
(
"paperMenu"
);
paperMenu.select
(
"paris"
);
Et nous avons aussi mis en place du code splitting, parce que nous n'avons qu'une carte par section, donc nous avons seulement besoin de charger la carte de la section affichée lorsque la page est chargée :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
loadStartupMap
(
);
// les autres cartes ne sont pas chargées au démarrage, mais lorsque l'iron-selector sélectionne une section
ironPages.addEventListener
(
"iron-select"
, e ->
{
if
(
ironPages.getSelected
(
).equals
(
"london"
) &&
!
londonMapInitialized) {
// Some code splitting to reduce initial module size
GWT.runAsync
(
new
RunAsyncCallback
(
){
@Override
public
void
onFailure
(
Throwable reason) {
Document.get
(
).getElementById
(
"londonMap"
).setInnerHTML
(
"Could not load this map, please try again later"
);
}
@Override
public
void
onSuccess
(
) {
Maps.initializeLondonMap
(
);
}
}
);
londonMapInitialized =
true
;
}
}
);
Nous avons également rajouté un app manisfest pour permettre à l'application de s' installer :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
{
"name"
:
"Favorite Maps PWA"
,
"short_name"
:
"Favorite Maps PWA"
,
"icons"
:
[{
"src"
:
"image/mapicon.png"
,
"sizes"
:
"144x144"
,
"type"
:
"image/png"
}],
"start_url"
:
"/pwademo.html"
,
"display"
:
"standalone"
,
"background_color"
:
"#3E4EB8"
,
"theme_color"
:
"#2E3AA1"
}
Pour terminer, nous avons ajouté les classes JsInterop pour inscrire le travailleur de service :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
if
(
Navigator.serviceWorker !=
null
) {
Navigator.serviceWorker.register
(
"service-worker/sw.js"
).then
(
new
Function<
JavaScriptObject, JavaScriptObject>(
) {
@Override
public
JavaScriptObject call
(
JavaScriptObject arg) {
GWT.log
(
"registred service worker successfully"
);
return
null
;
}
}
);
}
else
{
GWT.log
(
"service worker unavailable in this browser"
);
}
Et nous avons créé un travailleur de service dans un script nommé sw.js que nous avons rajouté aux ressources de l'application :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
var cacheName =
'GWT-PWA'
;
var filesToCache =
[
'/gwt-pwa/pwademo.html'
,
'/gwt-pwa/pwademo.css'
,
'/gwt-pwa/styles/app-theme.html'
,
'/gwt-pwa/styles/shared-styles.html'
,
'/gwt-pwa/leaflet/leaflet.js'
,
'/gwt-pwa/leaflet/leaflet.css'
,
'/gwt-pwa/image/mapicon.png'
,
'/gwt-pwa/pwademo/pwademo.nocache.js'
];
self
.addEventListener
(
'install'
,
function(
e) {
console.log
(
'[ServiceWorker] Install'
);
e.waitUntil
(
caches.open
(
cacheName).then
(
function(
cache) {
console.log
(
'[ServiceWorker] Caching app shell'
);
return cache.addAll
(
filesToCache);
}
)
);
}
);
self
.addEventListener
(
'activate'
,
function(
e) {
console.log
(
'[ServiceWorker] Activate'
);
e.waitUntil
(
caches.keys
(
).then
(
function(
keyList) {
return Promise.all
(
keyList.map
(
function(
key) {
console.log
(
'[ServiceWorker] Removing old cache'
,
key);
if (
key !==
cacheName) {
return caches.delete
(
key);
}
}
));
}
)
);
}
);
self
.addEventListener
(
'fetch'
,
function(
e) {
console.log
(
'[ServiceWorker] Fetch'
,
e.
request.
url);
e.respondWith
(
caches.match
(
e.
request).then
(
function(
response) {
return response ||
fetch
(
e.
request);
}
)
);
}
);
Le script installe et active le service worker. Il permet également au SW de s'abonner à l'événement « fetch » qui se déclenche avant chaque requête. Basé sur son état actuel, le SW décide soit d'utiliser le cache local, soit d'aller chercher la ressource en utilisant le réseau.
Après le chargement de l'application, la section cache dans les outils dev de Google Chrome affiche les éléments mis en cache.
Si nous désactivons la connexion réseau sur Google Chrome, on remarque qu'on peut toujours naviguer dans l'application :
Si nous jetons un coup d'œil dans la section requête réseau dans les outils de développement de Chrome, on remarque que les ressources de notre application sont servies à partir du service worker :
Comme il s'agit d'une application de démonstration, nous n'avons pas configuré de notification push, car cela nécessite l'installation d'un serveur Push.
Nous avons installé l'application dans l'écran d'accueil d'un téléphone Android, et nous avons obtenu le résultat montré sur les figures suivantes :
IV. Conclusion▲
Les PWA représentent un nouveau paradigme dans le monde Web. Certains prédisent qu'elles vont remplacer les applications natives dans les années à venir. Nous savons que les développeurs GWT utilisent Phonegap pour convertir leur application Web en une application mobile native, et peut-être qu'avec les PWA, ils n'auront plus à le faire. Nous avons vu dans ce tutoriel comment GWT peut être utilisé pour construire une PWA, en utilisant des bibliothèques telles que Polymer. Il n'y a jusqu'à présent pas de bibliothèques GWT pour interagir avec les service workers du navigateur, et donc il faudra attendre que ce vide soit comblé par la communauté GWT.
V. Remerciements▲
Cet article a été publié avec l'aimable autorisation d' Zakaria Amine. L'article original est disponible à cette adresse : Progressive Web apps recipes for GWT.
Nous tenons à remercier ced pour la relecture orthographique attentive de cet article et Mickael Baron pour la mise au gabarit.