$30 off During Our Annual Pro Sale. View Details »

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  5. @guillaumeehret
    #softshake16
    Nous voulons des sites webs performants

    View Slide

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

    View Slide

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

    View Slide

  8. @guillaumeehret
    #softshake16
    loadtime < 1 sec

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret
    avec HTTP 2

    View Slide

  23. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret
    avec HTTP 1

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret
    Exemple balise picture

    srcset="img/mixit/mixit-amphi_640.jpg" />
    srcset="img/mixit/mixit-amphi_1024.jpg" />
    srcset="img/mixit/mixit-amphi_2048.jpg" />


    View Slide

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

    View Slide

  47. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret
    Exemple balise picture avec type webp

    type="image/webp" class="img-responsive"/>
    class="img-responsive"/>
    class="img-responsive">

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  66. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret
    Cache des ressources

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  82. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret

    View Slide

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

    View Slide

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

    View Slide

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








    View Slide

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

    View Slide

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








    View Slide

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

    View Slide

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

    View Slide

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


    View Slide

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


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  96. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret
    Inliner votre logo


    cy=".733" cx="27.206"/>d="M22.835.715h4.786v.694h-4.785z"/>stroke-width="0">ry="1.61" cy="47.825" cx="17.923"/>rx="2.436" ry="2.392" cy="47.049" cx="32.474"/>cy="36.403" cx="32.485"/>cx="25.362"/>d="M58.855 35.448h1.132v2.734h-1.132zM69.6 35.507h1.102v2.653H69.6zM64.213 35.502h1.117V48.5h-1.117z"/>fill="#7f7f7f" d="M50.95 34.496h5.653v1.14H50.95zM46.15 41.265h3.39v.853h-3.39z"/>fill-rule="evenodd" fill="#00adee"/>cy="47.068" cx="42.936" fill="#7f7f7f"/>fill="#005676"/>fill="#00adee" d="M4.88 34.196h10.903v3.716H4.88z"/>

    View Slide

  97. @guillaumeehret
    #softshake16
    #softshake16
    @guillaumeehret
    Inliner vos styles principaux






    <br/>main {<br/>flex: 1 1 auto;<br/>overflow-x: hidden;<br/>overflow-y: scroll;<br/>}<br/>// ...<br/>



    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  104. @guillaumeehret
    #softshake16
    @guillaumeehret
    questions

    View Slide

  105. @guillaumeehret
    #softshake16
    Merci
    @guillaumeehret

    View Slide

  106. @guillaumeehret
    #softshake16
    Annexes
    @guillaumeehret

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide