Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Optimisez votre webapp

Dev-Mind
October 28, 2016

Optimisez votre webapp

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

Dev-Mind

October 28, 2016
Tweet

More Decks by Dev-Mind

Other Decks in Programming

Transcript

  1. @guillaumeehret #softshake16 Optimiser une webapp Serveur et standards du web

    @guillaumeehret Guillaume EHRET - @guillaumeehret 2016
  2. @guillaumeehret #softshake16 Le page rank est calculé sur des critères

    de performance : rapidité et sécurité #softshake16
  3. @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)
  4. @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
  5. @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
  6. @guillaumeehret #softshake16 #softshake16 @guillaumeehret HTTP 1 vers HTTP 2 La

    solution en HTTP1 ⇒ concaténer les ressources quand c’est possible
  7. @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
  8. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Début Après % Temps total 14.13

    s 13.83 s -2.1% Passage à HTTP2 GAIN 13.8s -2.1% 14.13s ⇒
  9. @guillaumeehret #softshake16 Limiter Moins il y a de choses à

    transférer plus le chargement est rapide @guillaumeehret 2 First load
  10. @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
  11. @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%
  12. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Compresser les ressources Les navigateurs acceptent

    tous le gzip server: compression: enabled: true mime-types: application/json,text/html,text/css,application/javascript min-response-size: 2048
  13. @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%
  14. @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; ... }
  15. @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; ... }
  16. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Limiter la taille des Json (2/2)

    @GetMapping @JsonView(SessionDto.SessionList. class) public List<SessionDto> getAllSessions() { return sessionRepository.findAllSessions().stream() .filter(session -> nonNull(session.getStart())) .sorted(Comparator. comparing(Session::getStart)) .map(SessionDto:: convert) .collect(Collectors. toList()); }
  17. @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%
  18. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Réduire la taille gulp.task('images', () =>

    gulp.src(`${paths.main}/images/**/*.{svg,png,jpg}`) .pipe($.newer(`${paths.tmp}/img`)) .pipe($.imagemin({ progressive: true, interlaced: true, arithmetic:true })) .pipe(gulp.dest(`${paths.dist}/img`)) ); https://github.com/imagemin/imagemin
  19. @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%
  20. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Gérer le responsive en CSS <div

    class="dm_img__inbackground">Mon texte</div> .dm_img__inbackground{ background-size: cover; @media only screen and (min-width: 0) { background-image: url("/img/mixit/mixit-amphi2_640.jpg"); } @media only screen and (min-width: 641px) and (max-width: 1024px) { background-image: url("/img/mixit/mixit-amphi2_1024.jpg"); } @media only screen and (min-width: 1025px) { background-image: url("/img/mixit/mixit-amphi2_2048.jpg"); } }
  21. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Propriété srcset <img src="img/mixit/mixit-amphi_640.jpg" 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
  22. @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)
  23. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple balise picture <picture> <source media="(max-width:

    640px)" srcset="img/mixit/mixit-amphi_640.jpg" /> <source media="(max-width: 1024px)" srcset="img/mixit/mixit-amphi_1024.jpg" /> <source srcset="img/mixit/mixit-amphi_2048.jpg" /> <img src="img/mixit/mixit-amphi_640.jpg"> </picture>
  24. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Tenter le format webp gulp.task('images-webp', ['images'],

    () => gulp.src(`${paths.tmp}/img/**/*.{png,jpg}`) .pipe($.webp()) .pipe(gulp.dest(`${paths.dist}/img`)) ); https://developers.google.com/speed/webp/ https://github.com/imagemin/imagemin-webp https://github.com/mozilla/mozjpeg
  25. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple balise picture avec type webp

    <picture class="img-responsive"> <source srcset="img/mixit/mixit-amphi_640.webp 640w, img/mixit/mixit-amphi_1024.webp 1024w, img/mixit/mixit-amphi_2048.webp" type="image/webp" class="img-responsive"/> <source srcset="img/mixit/mixit-amphi_640.jpg 640w, img/mixit/mixit-amphi_1024.jpg 1024w, img/mixit/mixit-amphi_2048.jpg" class="img-responsive"/> <img src="img/mixit/mixit-amphi_640.jpg" alt="Mix-IT amphi" class="img-responsive"> </picture>
  26. @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 ⇒
  27. @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
  28. @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
  29. @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`));
  30. @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%
  31. @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
  32. @guillaumeehret #softshake16 Format Extension Exemple Font Robot Compression EOT .eot

    179 ko ⬤ TrueType .ttf 179 ko ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ WOFF .woff 89 ko ⬤ ⬤ >10 ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ WOFF2 .woff2 64ko ⬤ 30% + que WOFF ⬤ ⬤ ⬤ ⬤ >10 ⬤ ⬤ ⬤ > 10 ⬤ SVG .svg 743 ko ⬤ ⬤ #softshake16 @guillaumeehret Les différents formats IE Edge Firefox Chrome Safari Opera Opera Mini Android Browser iOS Safari Chrome for Android
  33. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Déclarer une police avec @font-face @font-face

    { font-family: Roboto; src: local('Roboto'), url(../fonts/Roboto-Regular.woff2) format('woff2'), url(../fonts/Roboto-Regular.woff) format('woff'), url(../fonts/Roboto-Regular.eot), url(../fonts/Roboto-Regular.ttf) format('truetype'), url(../fonts/Roboto-Regular.svg#Roboto) format('svg'); font-weight: 300; font-style: normal } body { font-family: Roboto, Arial, sans-serif; font-weight: 300 }
  34. @guillaumeehret #softshake16 #softshake16 @guillaumeehret La ressource la plus rapide et

    la mieux optimisée est une ressource qui n'est pas envoyée.
  35. @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, ...
  36. @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
  37. @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.
  38. @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%
  39. @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
  40. @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);
  41. @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
  42. @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.
  43. @guillaumeehret #softshake16 #softshake16 @guillaumeehret gulp.task('generate-service-worker', ['copy-sw-scripts'], () => { const

    filepath = path.join(paths.tmp, 'service-worker.js'); return swPrecache.write(filepath, { cacheId: 'dev-mind', importScripts: [ 'scripts/sw/sw-toolbox.js', 'scripts/sw/runtime-caching.js' ], staticFileGlobs: [ paths.dist + '/**/*.{js,html,css,png,jpg,json,gif,svg,webp,eot,ttf,woff,woff2}'], stripPrefix: paths.dist + '/' }); });
  44. @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
  45. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Dans votre application if ('serviceWorker' in

    navigator) { navigator.serviceWorker.register('service-worker.js').then(function(registration) { if (typeof registration.update === 'function') { registration.update(); } }).catch(function(e) { console.error('Error during service worker registration:', e); }); }
  46. @guillaumeehret #softshake16 Asynchrone Reportez le chargement de ce qui n’est

    pas nécessaire au premier chargement @guillaumeehret 8 Critical
  47. @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
  48. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Quand vos scripts sont dans le

    <head> <html> <head> <!-- ... → <script src="scripts/vendors/vendor.min.js"></script> <script src="scripts/app.js"></script> <!-- ... --> </head> <body> <!-- ... --> </body> </html>
  49. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Quand vos scripts sont à la

    fin de <body> <html> <head> <!-- ... --> </head> <body> <!-- ... → <script src="scripts/vendors/vendor.min.js"></script> <script src="scripts/app.js"></script> <!-- ... --> </body> </html>
  50. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Optimisation 1er paint GAIN 1.5s -70%

    5s ⇒ Script Header Script body % Temps total 5 s 1.5 s -70 %
  51. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des JS La propriété

    async permet de reporter le chargement de vos scripts <script async src="scripts/vendors/jquery.slim.min.js"></script> <script async src="scripts/vendors/bootstrap.min.js"></script>
  52. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des CSS Les navigateurs

    pourront bientôt charger en asynchrone vos feuilles de styles <link rel="preload" href="styles/app.css" as="style" onload="this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="styles/app.css"></noscript> LoadCSS permet d’anticiper cette nouvelle fonctionnalité <script src="scripts/vendors/loadCSS.js"></script> <script src="scripts/vendors/cssrelpreload.js"></script>
  53. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des fonts Utilisez l’API

    Web Font Loading var font = new FontFace("Roboto", "url(/fonts/Roboto-Regular.woff2)", { style: 'normal', weight: '300' }); font.load(); font.ready().then(function() { document.fonts.add(font); document.body.style.fontFamily = "Roboto, Arial, sans-serif"; });
  54. @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
  55. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner votre logo <svg xmlns="http://www.w3.org/2000/svg" viewBox="0

    0 71 19"> <g stroke-width="0" fill="#00adee"> <ellipse rx=".416" ry=".36" cy=".742" cx="23.251"/><ellipse rx=".416" ry=".353" cy="1.435" cx="23.795"/><ellipse rx=".416" ry=".353" cy=".733" cx="27.206"/><ellipse rx=".416" ry=".353" cy="1.432" cx="27.203"/><path d="M23.21.385h3.967v1.403H23.21z"/><path d="M22.835.715h4.786v.694h-4.785z"/><ellipse rx=".416" ry=".36" cy="1.424" cx="23.251"/></g><g transform="translate(0 -31)" stroke-width="0"><g fill="#00adee"><g fill-rule="evenodd"><ellipse rx="2.317" ry="1.61" cy="47.814" cx="10.304"/><ellipse rx="2.251" ry="1.61" cy="47.825" cx="17.923"/><ellipse rx="2.117" ry="1.61" cy="47.832" cx="2.53"/><path d="M5.037 34.198c-2.547 0-4.612 1.364-4.612 3.047.002 1.59 1.74 1.4 4.14 1.522l.115-.05h3.323v.856c1.042-.578 1.644-1.43 1.646-2.33 0-1.682-2.066-3.045-4.613-3.046zM15.567 34.195a4.612 3.03 0 0 1 4.612 3.03 4.612 3.03 0 0 1-4.256 3.02v-1.553H12.6v.85a4.612 3.03 0 0 1-1.645-2.316 4.612 3.03 0 0 1 4.612-3.03z"/></g><path d="M.42 37.366h4.225v10.55H.42zM7.99 37.418h4.635V47.88H7.99zM15.67 37.315h4.506v10.478H15.67z"/><ellipse rx="2.436" ry="2.392" cy="47.049" cx="32.474"/><ellipse rx=".14" ry=".029" cy="46.435" cx="33.073"/><ellipse rx="2.422" ry="2.363" cy="36.403" cx="32.485"/><ellipse rx="2.345" ry="2.363" cy="47.06" cx="25.356"/><ellipse rx="2.348" ry="2.363" cy="36.462" cx="25.362"/><path d="M23.015 36.586h4.688v10.5h-4.688z"/></g><g fill="#7f7f7f"><path d="M58.855 34.515h11.87v1.066h-11.87z"/><path d="M58.855 35.448h1.132v2.734h-1.132zM69.6 35.507h1.102v2.653H69.6zM64.213 35.502h1.117V48.5h-1.117z"/><path d="M61.964 48.317h5.652v1.14h-5.652zM50.943 48.3h5.652v1.138h-5.652z"/></g><path fill="#787878" d="M53.18 35.446H54.3v12.988H53.18z"/><path fill="#7f7f7f" d="M50.95 34.496h5.653v1.14H50.95zM46.15 41.265h3.39v.853h-3.39z"/><path d="M30.773 38.067l3.56 3.477 3.32-3.2-3.41-3.565z" fill-rule="evenodd" fill="#00adee"/><ellipse rx="2.422" ry="2.363" cy="36.389" cx="42.899" fill="#7f7f7f"/><ellipse rx="2.422" ry="2.363" cy="47.068" cx="42.936" fill="#7f7f7f"/><g fill-rule="evenodd"><path d="M34.32 41.54l3.33-3.2 3.328 3.347-3.28 3.25z" fill="#005676"/><path d="M30.795 45.326l3.534-3.78 3.378 3.398-3.26 3.52z" fill="#00adee"/><path d="M41.106 48.618l-3.4-3.687 3.276-3.246 3.813 3.847-3.66 3.103z" fill="#7f7f7f"/><path d="M41.187 34.728l-3.57 3.626 3.36 3.34 3.9-3.916z" fill="#7f7f7f"/></g><path fill="#00adee" d="M4.88 34.196h10.903v3.716H4.88z"/></g> </svg>
  56. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner vos styles principaux <html lang="fr">

    <head> <!-- ... --> <link rel="preload" href="styles/app.css" as="style" onload="this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="styles/app.css"></noscript> <!-- Inlined styles for critical rendering path --> <style> main { flex: 1 1 auto; overflow-x: hidden; overflow-y: scroll; } // ... </style> </head> <body></body> </html>
  57. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Optimisation 1er paint GAIN 650ms -87%

    5s ⇒ Script Header Script body + async + inline % First paint 5 s 650ms -87 %
  58. @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 %
  59. @guillaumeehret #softshake16 13.2s -6.6% Compress @guillaumeehret Exemple Page des sessions

    Mesurer 14.3s 1 2 HTTP 2 Limiter 8 Asynchrone Intro First load Other loads Offline Critical 13.8s -2.1% Minify 8.5s -39% Json 7.9s -44% 7.1s -50% Format webp 3 Images Compress 4.3s -69% Résumé des temps observés sur une connexion 3G (750kb/s ⇓ 250kb/s ⇑) Feuilles de styles 4 4.16s -70% Uncss Fonts 5 7 6 Service Worker Mise en cache 2.1s -85%
  60. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Ressources https://developers.google.com/web/fundamentals/performance https://github.com/ben-eb/gulp-uncss https://github.com/filamentgroup/loadCSS https://github.com/necolas/normalize.css https://github.com/postcss/autoprefixer

    http://www.stylestats.org/ https://www.html5rocks.com/en/tutorials/workers/basics/ https://html.spec.whatwg.org/multipage/workers.html https://github.com/GoogleChrome/sw-precache https://github.com/GoogleChrome/sw-toolbox https://developer.mozilla.org/fr/docs/Web/API/ServiceWorker https://jakearchibald.com/2014/offline-cookbook/ https://www.youtube.com/watch?v=FEs2jgZBaQA
  61. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring Boot pour HTTP2 1.

    Utilisez un container compatible Jetty 9.2, Undertow, Tomcat > 8.5 Changez le serveur par défaut compile("org.springframework.boot:spring-boot-starter-web") { exclude module: "spring-boot-starter-tomcat" } compile "org.springframework.boot:spring-boot-starter-undertow"
  62. @guillaumeehret #softshake16 #softshake16 @guillaumeehret 2. Activer le mode HTTP 2

    @Configuration public class HttpConfig { @Bean public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() { UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory(); factory.addBuilderCustomizers(builder -> builder.setServerOption( UndertowOptions. ENABLE_HTTP2, true)); return factory; } }
  63. @guillaumeehret #softshake16 #softshake16 @guillaumeehret 3. Créer un keystore dans /src/main/resources

    keytool -genkeypair -alias mycert -keyalg RSA -sigalg MD5withRSA -keystore perf.jks -storepass DevMind -keypass DevMind -validity 9999 4. Le paramétrer dans application.yml server: ssl: key-store: classpath:perf.jks key-store-password: DevMind key-password: DevMind protocol: TLSv1.2
  64. @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:<path_to_alpn_boot_jar> ... Il existe des implémentations pour chaque version d’Open JDK (http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html #alpn-versions )
  65. @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
  66. @guillaumeehret #softshake16 #softshake16 @guillaumeehret public interface SessionRepository extends CrudRepository<Session, Long>

    { @Cacheable(WpCacheConfig. CACHE_SESSION) @Query(value = "SELECT DISTINCT s FROM Session s left join fetch s.speakers sp left join fetch s.votes") List<Session> findAllSessions(); }
  67. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration cache de ressources Spring @Configuration

    public class WpCacheHttpConfig { @Bean public WebMvcConfigurer configurer() { return new WebMvcConfigurerAdapter() { public void addResourceHandlers(ResourceHandlerRegistry reg) { reg.addResourceHandler( "/**/*") .addResourceLocations( "classpath:/static/") .setCacheControl(CacheControl. maxAge(10, TimeUnit.DAYS)); } }; } }
  68. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring pour Etag @Configuration public

    class WpCacheHttpConfig { @Bean public WebMvcConfigurer configurer() { return ... } @Bean public Filter etagFilter() { return new ShallowEtagHeaderFilter(); } }
  69. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache sur appel REST en GET

    @GetMapping public ResponseEntity<List<MemberDto>> getAllSponsors() { List<MemberDto> sponsors = sponsorRepository.findAllSponsor().stream() .map(MemberDto:: convert) .collect(Collectors. toList()); return ResponseEntity .ok() .cacheControl(CacheControl. maxAge(2, TimeUnit.DAYS)) .eTag(String. valueOf(sponsors.size() + sponsors.stream() .collect(Collectors. summingInt(MemberDto::getVersion)))) .body(sponsors); }
  70. @guillaumeehret #softshake16 #softshake16 @guillaumeehret Tester vos services workers google-chrome --user-data-dir=/tmp/foo

    --ignore-certificate-errors --unsafely-treat-insecure-origin-as-secure=https://localhost