Le but de cette présentation est de rappeler différentes étapes pour optimiser une webapp : loadtime, first load, refresh, offline. Les slides comportent à la fin des exemples de configuration SpringBoot. https://github.com/Dev-Mind/web-performance
@guillaumeehret #softshake16 https://giphy.com #softshake16 @guillaumeehret 57% des visiteurs abandonnent votre page si elle prend plus de 3 secondes à charger (loadtime)
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Nous utilisons de plus en plus notre mobile pour surfer 53% des utilisateurs dans le monde utilisent Internet depuis leur mobile (C’est 75% aux USA)
@guillaumeehret #softshake16 7 6 @guillaumeehret Exemple Mesurez 1 2 3 Feuilles de styles 4 HTTP 2 Limitez Images 8 Fonts 5 Asynchrone Service Worker Mise en cache Intro First load Other loads Offline Critical
@guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com Pour nos tests on désactive le cache et on se met en situation d’un réseau 3G
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Fonctionnement du navigateur Le navigateur charge le fichier HTML et charge les ressources décrites dans leur ordre de définition HTML CSS JS download JS parse API call 1er affichage
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Utiliser HTTP 2 1. Choisir un container Jetty 9.2, Undertow (JBoss) 1.3, Tomcat > 8.5 2. Activer le mode HTTP 2 3. Créer un keystore 4. Paramétrer le serveur 5. Ajouter le jar ALPN au jdk utilisé pour lancer la webapp
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Minifier vos ressources Minifier consiste à retirer tous les espaces, les commentaires et les retours à la ligne inutiles du code JS : https://github.com/mishoo/UglifyJS2 HTML : https://github.com/jserme/htmlmin CSS : https://github.com/ben-eb/cssnano
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Minifier vos ressources GAIN 13.2s -6.6% 14.13s ⇒ Début Step -1 Après % HTML 4.3 ko 3.1 ko - 28% CSS 142 ko 116 ko -18% JS 51.5 ko 12 ko -76% Temps total 14.13 s 13.83 s (-2.1%) 13.2s -6.6%
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + minifié GAIN 8.5s -39% 14.13s ⇒ Début Step -1 Après % HTML 4.3 ko 3.1 ko (-28%) 1.67 ko -62% CSS 142 ko 116 ko (-18%) 19.8 ko -86% JS 51.5 ko 12 ko (-76%) 9 ko -82% Json (rest) 187 ko 175 ko (-6.5%) 63 ko -66% Temps total 14.13 s 13.2s (-6.6%) 8.5s -39%
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Limiter la taille des Json avec JsonView public class SessionDto { public String lang; public String format; public String title; public String summary; public String description; public String ideaForNow; ... }
@guillaumeehret #softshake16 #softshake16 @guillaumeehret JsonView…. ou on crée des DTO qui collent à l’API public class SessionDto { public interface SessionList {} public String lang; public String format; @JsonView(SessionList.class) public String title; @JsonView(SessionList.class) public String summary; public String description; public String ideaForNow; ... } public class SessionListDto { public String title; public String summary; ... }
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + JsonView GAIN 7.9s -44% 14.13s ⇒ Début Step -1 Après % Json (rest) 187 ko 63 ko (-66% ) 34 ko -81% Temps total 14.13 s 8.5s (-39%) 7.9s -44%
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Images compressées GAIN 7.1s -50% 14.13s ⇒ Début Step -1 Après % Images 459 ko 386 ko -15.8% Temps total 14.13 s 7.9s (-44%) 7.1s -50%
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Propriété srcset srcset="img/mixit/mixit-amphi_640.jpg 640w, img/mixit/mixit-amphi_1024.jpg 1024w, img/mixit/mixit-amphi_2048.jpg"> srcset : liste des URLs des images séparées par des virgules. Chaque URL peut être suivie de 2 options permettant au navigateur de déterminer quelle image utilisée en fonction du contexte 1. la largeur de la fenêtre : nombre entier suivi par 'w' (640w), par défaut c’est l’infini 2. la densité de pixel : nombre entier suivi par 'x' (2x), par défaut c’est 1x. De 0 à 640px ⇒ mixit-amphi_640 De 641 à 1024px ⇒ mixit-amphi_1024 De 1025 à infini ⇒ mixit-amphi_2048
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Utiliser la balise picture Elle offre plus de souplesse pour faire des niveaux de zooms différents en fonction des devices (art direction)
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + webp Jpg, png Jpg +compres webp Gain Image 459 ko 386 ko 163.4 ko -64.4% Temps total 14.13 s 7.1s 4.33s -69% GAIN 4.33s -69% 14.13s ⇒
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Quel format utiliser? Format Transparence Couleurs Compression Animation Navigateur Use Case GIF Oui 256 Sans perte Oui Tous Animation PNG Oui Au choix Sans perte Non Tous Fond transparent PNG 8 bit Oui 256 Sans perte Non Tous Couleur lmitée JPEG Non Toutes Au choix Non Tous Photos WebP Oui Toutes Avec perte Oui Chrome, Opera, Android Photos SVG Oui Toutes Sans perte Non Tous Logo, diagramme
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Framework CSS…. un coût bootstrap.min.css : 121.2 Ko bootstrap.min.js : 37 Ko jquery.slim.min.js: 69.3 Ko Material Design Lite material.min.css : 141 Ko material.min.js : 62.3 Ko foundation.min.css : 77.4 Ko foundation.min.js : 107.9 Ko jquery.slim.min.js: 69.3 Ko base-min.css : 3 Ko pure-min.css : 17.2 Ko
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Nettoyer vos feuilles de styles uncss permet de supprimer les styles non utilisés return gulp.src(`${paths.main}/component/**/*.css`) .pipe($.if('*.css', $.uncss({ html : [`${paths.main}/*.html`, `${paths.main}/component/**/*.html`, `${paths.main}/component/**/*.js`] }))) .pipe($.if('*.css', $.cssnano())) .pipe(gulp.dest(`${paths.dist}/styles`));
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Compressé + uncss GAIN 4.16s -70% 14.13s ⇒ Début Step -1 Après % CSS 142 ko 19.8 ko (-86%) 2.8 ko -98% Temps total 14.13 s 8.5s (-39%) 4.16s -70.5%
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Quelques astuces N’utilisez que les modules nécessaires de votre framework CSS Apprenez le CSS : animations, les flexbox remplacent de longues lignes de code…. Utilisez normalize.css pour fixer les disparités entre navigateur et autoprefixer pour automatiquement ajouter les préfixes propres à chaque navigateur pour les fonctions expérimentales
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache base de données Coût élevé dans le cloud avec une tarification liée au volume de données Les temps d’accès fluctuent beaucoup Abstraction Spring pour différente solution de cache partagé ou non Generic, JCache, EhCache, Hazelcast, Infinispan, Couchbase, ...
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache des ressources Le serveur peut renvoyer des informations de cache pour le navigateur Cache-Control max-age exprime la durée pendant laquelle la ressource est valide ETag token qui permet d’identifier la version de la donnée
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring Ajoute un cache-control pour toutes les ressources statiques (ici de 10 jours) Mais on peut avoir des problèmes de mise à jour du cache.
@guillaumeehret #softshake16 #softshake16 @guillaumeehret ... + cache GAIN 2.1s -85% 14.13s ⇒ Début Step -1 Après % Temps total 14.13 s 4.16s (-70.5%) 2.1s -85.5%
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Les SW se basent sur les Webs workers Javascript est mono-thread et tout passe par le même endroit Les web workers permettent d’exécuter des traitements dans un thread séparé mais ils n’auront pas accès au DOM. La communication se fait par échange de messages
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Créer un Web worker Dans votre script principal var worker = new Worker('doWork.js'); worker.addEventListener ('message', function(e) { console.log('Worker said: ', e.data); }, false); worker.postMessage('Hello World'); // Send data to our worker. Le script dédié au web worker self.addEventListener ('message', function(e) { self.postMessage(e.data); }, false);
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Service worker Ils peuvent accéder à d’autres API comme IndexedDB API Équivaut à un proxy entre votre client et le serveur Les services workers ne sont accessibles qu’en HTTPS
@guillaumeehret #softshake16 #softshake16 @guillaumeehret sw-precache et sw-toolbox Grâce à Google les services workers peuvent être vus comme une boîte noire. sw-precache : module node qui fait le boulot pour vous. sw-toolbox : lib JS qui fournit des utilitaires pour créer vos propres service workers. Ils proposent entre autre différent pattern pour gérer le cache.
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Stratégie sw-toolbox toolbox.networkFirst fait la requête et stocke les résultats dans le cache. Sinon regarde dans le cache si on a rien (bien quand vous voulez toujours les données les plus fraîches) toolbox.cacheFirst si présent dans le cache répond immédiatement, sinon appelle le réseau. A n’utiliser que pour des ressources qui ne change pas toolbox.fastest lance les 2 en parallèles et c’est le plus rapide qui gagne. toolbox.cacheOnly ne regarde que le cache. Utile quand le réseau est tombé ou lorsque vous voulez préserver la batterie d’un mobile... toolbox.networkOnly un peu comme si on n’utilisait pas les services workers
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Le but est d’optimiser le chemin critique du rendu (Critical rendering path) En gros charger le plus important tout de suite et le reste plus tard
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Optimisation 1er paint GAIN 1.5s -70% 5s ⇒ Script Header Script body % Temps total 5 s 1.5 s -70 %
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des JS La propriété async permet de reporter le chargement de vos scripts
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des CSS Les navigateurs pourront bientôt charger en asynchrone vos feuilles de styles onload="this.rel='stylesheet'">
LoadCSS permet d’anticiper cette nouvelle fonctionnalité
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner vos critical CSS Pour avoir un rendu rapide inliner votre CSS minimal pour afficher une page qui ressemble à quelque chose Inliner aussi votre logo pour que celui-ci d’affiche directement
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Objectifs atteint sur 3G Script Header Script body % First paint 6 s 650ms -70 % Temps total 14.13 s 4.4s -87 % Offline/refresh 14.13 s 1.8s -87 %
@guillaumeehret #softshake16 #softshake16 @guillaumeehret 5. Activer ALPN (Application Layer Protocol Negotiation) ALPN permet de définir quel protocole utiliser pour une connection sécurisée. Vous devez l’ajouter dans vos options de démarrage de la JVM java -Xbootclasspath/p: ... Il existe des implémentations pour chaque version d’Open JDK (http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html #alpn-versions )
@guillaumeehret #softshake16 #softshake16 @guillaumeehret @Configuration @EnableCaching public class WpCacheConfig { public static final String CACHE_SPONSOR = "sponsor"; public static final String CACHE_SESSION = "session"; @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager( CACHE_SESSION, CACHE_SPONSOR); } } Configuration Cache serveur
@guillaumeehret #softshake16 #softshake16 @guillaumeehret public interface SessionRepository extends CrudRepository { @Cacheable(WpCacheConfig. CACHE_SESSION) @Query(value = "SELECT DISTINCT s FROM Session s left join fetch s.speakers sp left join fetch s.votes") List findAllSessions(); }
@guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring pour Etag @Configuration public class WpCacheHttpConfig { @Bean public WebMvcConfigurer configurer() { return ... } @Bean public Filter etagFilter() { return new ShallowEtagHeaderFilter(); } }