Slide 1

Slide 1 text

@guillaumeehret #softshake16 Optimiser une webapp Serveur et standards du web @guillaumeehret Guillaume EHRET - @guillaumeehret 2016

Slide 2

Slide 2 text

@guillaumeehret #softshake16 @guillaumeehret Guillaume EHRET https://www.dev-mind.fr https://javamind-fr.blogspot.fr/

Slide 3

Slide 3 text

@guillaumeehret #softshake16 https://giphy.com #softshake16 @guillaumeehret

Slide 4

Slide 4 text

@guillaumeehret #softshake16 http://blog.mobileangelo.fr #softshake16 @guillaumeehret

Slide 5

Slide 5 text

@guillaumeehret #softshake16 Nous voulons des sites webs performants

Slide 6

Slide 6 text

@guillaumeehret #softshake16 https://giphy.com #softshake16 @guillaumeehret 57% des visiteurs abandonnent votre page si elle prend plus de 3 secondes à charger (loadtime)

Slide 7

Slide 7 text

@guillaumeehret #softshake16 Le page rank est calculé sur des critères de performance : rapidité et sécurité #softshake16

Slide 8

Slide 8 text

@guillaumeehret #softshake16 loadtime < 1 sec

Slide 9

Slide 9 text

@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)

Slide 10

Slide 10 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret La qualité du réseau dépend du contexte

Slide 11

Slide 11 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret ou de la zone géographique...

Slide 12

Slide 12 text

@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

Slide 13

Slide 13 text

@guillaumeehret #softshake16 Exemple Backend : Java et Spring Boot Frontend : JS, HTML, CSS @guillaumeehret

Slide 14

Slide 14 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret

Slide 15

Slide 15 text

@guillaumeehret #softshake16 Mesurez Constatez les performances et les gains éventuels @guillaumeehret

Slide 16

Slide 16 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com Utilisez les outils pour les développeurs dans les navigateurs

Slide 17

Slide 17 text

@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

Slide 18

Slide 18 text

@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

Slide 19

Slide 19 text

@guillaumeehret #softshake16 HTTP 2 Il est temps de changer @guillaumeehret 1 Intro

Slide 20

Slide 20 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Le navigateur limite le nb d’appels en parallèle

Slide 21

Slide 21 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret HTTP 1 vers HTTP 2 La solution en HTTP1 ⇒ concaténer les ressources quand c’est possible

Slide 22

Slide 22 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret avec HTTP 2

Slide 23

Slide 23 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret avec HTTP 1

Slide 24

Slide 24 text

@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

Slide 25

Slide 25 text

@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 ⇒

Slide 26

Slide 26 text

@guillaumeehret #softshake16 Limiter Moins il y a de choses à transférer plus le chargement est rapide @guillaumeehret 2 First load

Slide 27

Slide 27 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com

Slide 28

Slide 28 text

@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

Slide 29

Slide 29 text

@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%

Slide 30

Slide 30 text

@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

Slide 31

Slide 31 text

@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%

Slide 32

Slide 32 text

@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; ... }

Slide 33

Slide 33 text

@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; ... }

Slide 34

Slide 34 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Limiter la taille des Json (2/2) @GetMapping @JsonView(SessionDto.SessionList. class) public List getAllSessions() { return sessionRepository.findAllSessions().stream() .filter(session -> nonNull(session.getStart())) .sorted(Comparator. comparing(Session::getStart)) .map(SessionDto:: convert) .collect(Collectors. toList()); }

Slide 35

Slide 35 text

@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%

Slide 36

Slide 36 text

@guillaumeehret #softshake16 Les images Optimisez l’utilisation des images @guillaumeehret 3 First load

Slide 37

Slide 37 text

@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

Slide 38

Slide 38 text

@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%

Slide 39

Slide 39 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple d’images

Slide 40

Slide 40 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Gérer le responsive en CSS
Mon texte
.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"); } }

Slide 41

Slide 41 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Et quid de la balise img ?

Slide 42

Slide 42 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Propriété srcset

Slide 43

Slide 43 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Propriété srcset 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

Slide 44

Slide 44 text

@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)

Slide 45

Slide 45 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple balise picture

Slide 46

Slide 46 text

@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

Slide 47

Slide 47 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Exemple balise picture avec type webp Mix-IT amphi

Slide 48

Slide 48 text

@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 ⇒

Slide 49

Slide 49 text

@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

Slide 50

Slide 50 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Préférez le vectoriel png ou jpg svg

Slide 51

Slide 51 text

@guillaumeehret #softshake16 Les feuilles de styles Keep it simple stupid @guillaumeehret 4 First load

Slide 52

Slide 52 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Framework CSS Material Design Lite

Slide 53

Slide 53 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret https://giphy.com

Slide 54

Slide 54 text

@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

Slide 55

Slide 55 text

@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`));

Slide 56

Slide 56 text

@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%

Slide 57

Slide 57 text

@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

Slide 58

Slide 58 text

@guillaumeehret #softshake16 Les fonts Utilisez le bon format @guillaumeehret 5 First load

Slide 59

Slide 59 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Utilisez le moins de font possible

Slide 60

Slide 60 text

@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

Slide 61

Slide 61 text

@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 }

Slide 62

Slide 62 text

@guillaumeehret #softshake16 Mettre en cache Pourquoi recharger une ressource qui n’a pas changé ? @guillaumeehret 6 Second loads

Slide 63

Slide 63 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret La ressource la plus rapide et la mieux optimisée est une ressource qui n'est pas envoyée.

Slide 64

Slide 64 text

@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, ...

Slide 65

Slide 65 text

@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

Slide 66

Slide 66 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache des ressources

Slide 67

Slide 67 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration par défaut Par défaut pas de cache sur les ressources

Slide 68

Slide 68 text

@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.

Slide 69

Slide 69 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring pour ETag

Slide 70

Slide 70 text

@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%

Slide 71

Slide 71 text

@guillaumeehret #softshake16 Service worker Mode offline, cache de ressources côté client @guillaumeehret 7 Offline

Slide 72

Slide 72 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Que se passe t-il en offline ?

Slide 73

Slide 73 text

@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

Slide 74

Slide 74 text

@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);

Slide 75

Slide 75 text

@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

Slide 76

Slide 76 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Cycle de vie d’un service worker

Slide 77

Slide 77 text

@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.

Slide 78

Slide 78 text

@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 + '/' }); });

Slide 79

Slide 79 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Le script runtime-caching global.toolbox.router.get('/(.*)', global.toolbox.fastest, {});

Slide 80

Slide 80 text

@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

Slide 81

Slide 81 text

@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); }); }

Slide 82

Slide 82 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret

Slide 83

Slide 83 text

@guillaumeehret #softshake16 Asynchrone Reportez le chargement de ce qui n’est pas nécessaire au premier chargement @guillaumeehret 8 Critical

Slide 84

Slide 84 text

@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

Slide 85

Slide 85 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Quand vos scripts sont dans le

Slide 86

Slide 86 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 5 sec après le chargement CSS + JS

Slide 87

Slide 87 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Quand vos scripts sont à la fin de

Slide 88

Slide 88 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 1.5 sec après le chargement CSS

Slide 89

Slide 89 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Optimisation 1er paint GAIN 1.5s -70% 5s ⇒ Script Header Script body % Temps total 5 s 1.5 s -70 %

Slide 90

Slide 90 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des JS La propriété async permet de reporter le chargement de vos scripts

Slide 91

Slide 91 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Chargement asynchrone des CSS Les navigateurs pourront bientôt charger en asynchrone vos feuilles de styles LoadCSS permet d’anticiper cette nouvelle fonctionnalité

Slide 92

Slide 92 text

@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"; });

Slide 93

Slide 93 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 500 ms après le chargement HTML

Slide 94

Slide 94 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Oops une page pas très jolie

Slide 95

Slide 95 text

@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

Slide 96

Slide 96 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner votre logo

Slide 97

Slide 97 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Inliner vos styles principaux main { flex: 1 1 auto; overflow-x: hidden; overflow-y: scroll; } // ...

Slide 98

Slide 98 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Premier affichage à 650 ms Mais avec un rendu minimal

Slide 99

Slide 99 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Optimisation 1er paint GAIN 650ms -87% 5s ⇒ Script Header Script body + async + inline % First paint 5 s 650ms -87 %

Slide 100

Slide 100 text

@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 %

Slide 101

Slide 101 text

@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%

Slide 102

Slide 102 text

@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

Slide 103

Slide 103 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret https://github.com/Dev-Mind/web-performance

Slide 104

Slide 104 text

@guillaumeehret #softshake16 @guillaumeehret questions

Slide 105

Slide 105 text

@guillaumeehret #softshake16 Merci @guillaumeehret

Slide 106

Slide 106 text

@guillaumeehret #softshake16 Annexes @guillaumeehret

Slide 107

Slide 107 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration serveur https://github.com/h5bp/server-configs

Slide 108

Slide 108 text

@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"

Slide 109

Slide 109 text

@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; } }

Slide 110

Slide 110 text

@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

Slide 111

Slide 111 text

@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 )

Slide 112

Slide 112 text

@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

Slide 113

Slide 113 text

@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(); }

Slide 114

Slide 114 text

@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)); } }; } }

Slide 115

Slide 115 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Configuration Spring pour Etag @Configuration public class WpCacheHttpConfig { @Bean public WebMvcConfigurer configurer() { return ... } @Bean public Filter etagFilter() { return new ShallowEtagHeaderFilter(); } }

Slide 116

Slide 116 text

@guillaumeehret #softshake16 #softshake16 @guillaumeehret Cache sur appel REST en GET @GetMapping public ResponseEntity> getAllSponsors() { List 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); }

Slide 117

Slide 117 text

@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