Optimiser les performances d'une webapp

D3bcad37b1ec0fc8ecdeb199e54a61e4?s=47 Dev-Mind
April 05, 2017

Optimiser les performances d'une webapp

Avec les outils actuels, développer une application web (grand public ou privée), avec du JS en front et du Java en backend est de plus en plus rapide. Les premiers hics peuvent survenir le jour où vous basculez cette application en production.

Mon but est de vous montrer dans cette session, comment optimiser une webapp quelque soit les solutions techniques utilisées côté front ou côté serveur. Mes exemples se basent sur une application SpringBoot avec un frontend en JS (sans framework particulier) mais ceci est peu important. Nous allons nous focaliser sur les concepts qui restent vrais quelque soit les langages.

Nous verrons les différentes étapes pour améliorer la rapidité d'une application au premier chargement (load time), en mode offline, comment jouer sur le critical rendering... Au niveau des solutions nous parlerons cache, asynchronisme, services workers...

Conférence donnée à Devoxxfr http://cfp.devoxx.fr/2017/speaker/guillaume_ehret

Besoin d'une formation sur le sujet rendez vous sur https://www.dev-mind.fr

D3bcad37b1ec0fc8ecdeb199e54a61e4?s=128

Dev-Mind

April 05, 2017
Tweet

Transcript

  1. #DevoxxFR Optimiser les performances d’une webapp Guillaume EHRET @guillaumeehret #WebPerformance

    Jeudi 6 avril 2017
  2. #WebPerformance / 6 avril 2017 @guillaumeehret Guillaume EHRET @guillaumeehret #WebPerformance

    Optimiser les performances d’une webapp
  3. #WebPerformance / 6 avril 2017 @guillaumeehret Dev-Mind https://www.dev-mind.fr

  4. #WebPerformance / 6 avril 2017 @guillaumeehret https://www.dev-mind.fr Développement Formations

  5. #WebPerformance / 6 avril 2017 @guillaumeehret avec des crêpes et

    du coeur 20 et 21 avril à Lyon https://mixitconf.org/
  6. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Pourquoi une webapp doit être performante ?
  7. #WebPerformance / 6 avril 2017 @guillaumeehret 1 2 3 https://giphy.com

  8. #WebPerformance / 6 avril 2017 @guillaumeehret Mobile First mars 2017

  9. #WebPerformance / 6 avril 2017 @guillaumeehret > 3 sec sur

    mobile ⇒ 57% des users en moins 0 : 03 DoubleClick, “The Need for Mobile Speed”, September 2016 https://giphy.com
  10. #WebPerformance / 6 avril 2017 @guillaumeehret

  11. #WebPerformance / 6 avril 2017 @guillaumeehret

  12. #WebPerformance / 6 avril 2017 @guillaumeehret

  13. #WebPerformance / 6 avril 2017 @guillaumeehret 2010 2017 2016 2015

    2014 2013 2012 2011 http://httparchive.org mars 2017
  14. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Mb en 2017

    Wifi 30Mb/s ⇒ 0.5 sec
  15. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Mb en 2017

    Wifi 30Mb/s ⇒ 0.5 sec 4G 4Mb/s ⇒ 5 sec H+ 1.5Mb/s ⇒ 10 sec 3G 750kb/s ⇒ 25 sec Webpagetest.org, , February 2016
  16. #WebPerformance / 6 avril 2017 @guillaumeehret Webpagetest.org, , February 2016

    Temps mobile 10s 3/4 19s moy 200 moy Les performances sur mobile ne sont pas toujours au rendez vous
  17. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Et les applications d’entreprise ?
  18. #WebPerformance / 6 avril 2017 @guillaumeehret https://giphy.com

  19. #WebPerformance / 6 avril 2017 @guillaumeehret

  20. #WebPerformance / 6 avril 2017 @guillaumeehret

  21. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Que faire pour créer des webapps performantes ?
  22. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6
  23. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6
  24. #WebPerformance / 6 avril 2017 @guillaumeehret https://monsite/ 1

  25. #WebPerformance / 6 avril 2017 @guillaumeehret https://monsite/ HTML parsing 1

  26. #WebPerformance / 6 avril 2017 @guillaumeehret CSS parsing compile render

    parsing JavaScript Autres 1
  27. #WebPerformance / 6 avril 2017 @guillaumeehret parsing compile render JavaScript

    Autres parsing 1 CSS
  28. #WebPerformance / 6 avril 2017 @guillaumeehret render JavaScript Autres 1

    CSS
  29. #WebPerformance / 6 avril 2017 @guillaumeehret loadtime https://monsite/ HTML parsing

    parsing compile render JavaScript Autres CSS render JavaScript Autres CSS ... 1
  30. #WebPerformance / 6 avril 2017 @guillaumeehret first paint https://monsite/ parsing

    parsing compile render JavaScript Autres CSS render JavaScript Autres CSS ... 1 HTML
  31. #WebPerformance / 6 avril 2017 @guillaumeehret Notre objectif < 1s

    first load 1
  32. #WebPerformance / 6 avril 2017 @guillaumeehret Notre objectif first load

    refresh 1 < 1s
  33. #WebPerformance / 6 avril 2017 @guillaumeehret Notre objectif first load

    refresh offline 1 < 1s
  34. #WebPerformance / 6 avril 2017 @guillaumeehret Mes outils pour mesurer

    Webpagetest >> Pagespeed Insight >> Browser developper tools ... 1
  35. #WebPerformance / 6 avril 2017 @guillaumeehret 1

  36. #WebPerformance / 6 avril 2017 @guillaumeehret Mobile Cache Throttling 3G

    1
  37. #WebPerformance / 6 avril 2017 @guillaumeehret 18.2s Load time loadtime

    1 1.6Mb Size size
  38. #WebPerformance / 6 avril 2017 @guillaumeehret first paint 1 7.4s

    first paint
  39. #WebPerformance / 6 avril 2017 @guillaumeehret Avant de commencer 2

    7.4s first paint 18.2s loadtime Sans optim 1.6Mb size
  40. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6 2
  41. #WebPerformance / 6 avril 2017 @guillaumeehret CSS JavaScript Autres HTML

    2
  42. #WebPerformance / 6 avril 2017 @guillaumeehret <html> <head> <script .../>

    <body> <script .../> </body </html> JavaScript HTML 2
  43. #WebPerformance / 6 avril 2017 @guillaumeehret 1 7.4s first paint

    2
  44. #WebPerformance / 6 avril 2017 @guillaumeehret 2 6.4s first paint

  45. #WebPerformance / 6 avril 2017 @guillaumeehret Ordre de définition des

    scripts JS 2 7.4s first paint 18.2s loadtime Sans optim 1.6Mb size 6.4s first paint 18.2s loadtime 1.6Mb size -13.5% first paint 0% loadtime 0% size
  46. #WebPerformance / 6 avril 2017 @guillaumeehret 2

  47. #WebPerformance / 6 avril 2017 @guillaumeehret 2

  48. #WebPerformance / 6 avril 2017 @guillaumeehret 2 HTTP2 côté client

  49. #WebPerformance / 6 avril 2017 @guillaumeehret 2 HTTP2 et Java

    1. Container compatible Jetty 9.2, Undertow (JBoss) 1.3, Tomcat > 8.5 2. Activer le mode HTTP 2 3. Avoir un certificat 4. Ajouter le jar ALPN au jdk (lib pour Jdk < 9)
  50. #WebPerformance / 6 avril 2017 @guillaumeehret HTTP 2 2 6.4s

    first paint 18.2s loadtime Ordre JS 1.6Mb size 6.4s first paint 18.1s loadtime 1.6Mb size 0% first paint -0.5% loadtime 0% size
  51. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6 2 3
  52. #WebPerformance / 6 avril 2017 @guillaumeehret 3 https://giphy.com

  53. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Minification ressources txt

    CSS JavaScript HTML
  54. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Minification ressources txt

    JavaScript HTML sur mon exemple -22% -51% -26% CSS
  55. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Le JSON est

    aussi du texte JSON
  56. #WebPerformance / 6 avril 2017 @guillaumeehret 3 public class SessionDto

    { public String title; public String summary; public String lang; public String format; public String description; public Instant start; public Instant room; ... } des DTO qui collent à l’API
  57. #WebPerformance / 6 avril 2017 @guillaumeehret 3 public class SessionDto

    { public String title; public String summary; public String lang; public String format; public String description; public Instant start; public Instant room; ... } public class SessionListDto { public String title; public String summary; ... } des DTO qui collent à l’API
  58. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Le JSON est

    aussi du texte -80% JSON
  59. #WebPerformance / 6 avril 2017 @guillaumeehret Minification du texte 6.4s

    first paint 18.1s loadtime HTTP2 1.6Mb size 5.5s first paint 15.4s loadtime 1.3Mb size -14% first paint -15% loadtime -19% size 3
  60. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Le texte se

    compresse CSS JavaScript HTML JSON les navigateurs acceptent le gzip
  61. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Le texte se

    compresse JavaScript HTML JSON server: compression: enabled: true mime-types: application/json,text/html,text/css,application/javascript min-response-size: 2048 CSS
  62. #WebPerformance / 6 avril 2017 @guillaumeehret Compression du texte 5.5s

    first paint 15.4s loadtime Minification 1.3Mb size 1.5s first paint 10.8s loadtime 940Kb size -72% first paint -30% loadtime -28% size 3
  63. #WebPerformance / 6 avril 2017 @guillaumeehret CSS JavaScript Autres HTML

    3
  64. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret CSS
  65. #WebPerformance / 6 avril 2017 @guillaumeehret CSS 3

  66. #WebPerformance / 6 avril 2017 @guillaumeehret 3 https://giphy.com CSS

  67. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Material Design Lite

    185kb 228kb 203kb 20kb Les frameworks CSS CSS CSS +JS CSS +JS CSS +JS CSS JS JS JQuery 69kb JQuery 69kb
  68. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Quelques astuces Modularité

    frameworks Apprenez CSS CSS is awesome de Igor Laborie Nettoyage automatique avec uncss CSS
  69. #WebPerformance / 6 avril 2017 @guillaumeehret 3 UnCSS return gulp.src(`${paths.main}/**/*.css`)

    .pipe($.if('*.css', $.uncss({ html : [ `${paths.main}/*.html`, `${paths.main}/component/**/*.html`, `${paths.main}/component/**/*.js`] }))) .pipe(gulp.dest(`${paths.dist}/styles`)); CSS
  70. #WebPerformance / 6 avril 2017 @guillaumeehret Utilisation UnCSS 1.5s first

    paint 10.8s loadtime Compression 940Kb size 0.5s first paint 10.4s loadtime 920Kb size -67% first paint -3.7% loadtime -2.1% size 3 CSS
  71. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Images 3
  72. #WebPerformance / 6 avril 2017 @guillaumeehret Compression imagemin 0.5s first

    paint 10.4s loadtime UnCss 920Kb size 0.5s first paint 8.5s loadtime 735Kb size 0% first paint -18% loadtime -20% size 3 Images
  73. #WebPerformance / 6 avril 2017 @guillaumeehret Préférez le vectoriel 3

    svg png, jpg Images
  74. #WebPerformance / 6 avril 2017 @guillaumeehret Jpeg XR 3 Images

  75. #WebPerformance / 6 avril 2017 @guillaumeehret Webp 3 Images

  76. #WebPerformance / 6 avril 2017 @guillaumeehret <picture> la balise perdue

    3 Des images responsives en HTML avec la balise <picture> et la propriété srcset Images
  77. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Images Images

  78. #WebPerformance / 6 avril 2017 @guillaumeehret propriété srcset 3 <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"> la largeur de la fenêtre nombre entier suivi par 'w' (640w), par défaut c’est l’infini la densité de pixel nombre entier suivi par 'x' (2x), par défaut c’est 1x. Images
  79. #WebPerformance / 6 avril 2017 @guillaumeehret 3 <picture> <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"/> <source srcset="img/mixit/mixit-amphi_640.jpg 640w, img/mixit/mixit-amphi_1024.jpg 1024w, img/mixit/mixit-amphi_2048.jpg"/> <img src="img/mixit/mixit-amphi_640.jpg" alt="Mix-IT amphi"> </picture> Images
  80. #WebPerformance / 6 avril 2017 @guillaumeehret format webp 3 0.5s

    first paint 8.5s loadtime imagemin 735Kb size 0.5s first paint 6.7s loadtime 572Kb size 0% first paint -21% loadtime -22% size Images
  81. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Fonts 3
  82. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Utilisez le moins de font possible 3
  83. #WebPerformance / 6 avril 2017 @guillaumeehret 3 Fonts Format Extension

    Exemple Font Roboto 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 IE Edge Firefox Chrome Safari Opera Opera Mini Android Browser iOS Safari Chrome for Android
  84. #WebPerformance / 6 avril 2017 @guillaumeehret 3 @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; } Fonts
  85. #WebPerformance / 6 avril 2017 @guillaumeehret Utiliser un CDN 3

    <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> Fonts
  86. #WebPerformance / 6 avril 2017 @guillaumeehret CDN pour les fonts

    3 0.5s first paint 6.7s loadtime webp 572Kb size 440ms first paint 3.4s loadtime 231Kb size -12% first paint -49% loadtime -59% size Fonts
  87. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret JavaScript 3
  88. #WebPerformance / 6 avril 2017 @guillaumeehret Polyfill sur CDN 3

    <script src="https://cdn.polyfill.io/v2/polyfill.min.js"> </script> Size (min) 1370 8212 464 26430 6652 Size (min+gz) 514 2435 285 8497 2018 JavaScript
  89. #WebPerformance / 6 avril 2017 @guillaumeehret CDN JS 3 440ms

    first paint 3.4s loadtime CDN font 231Kb size 440ms first paint 2.9s loadtime 200Kb size 0% first paint -15% loadtime -13% size JavaScript
  90. #WebPerformance / 6 avril 2017 @guillaumeehret Mais encore 3 Tree

    shaking SSR Code splitting (modules) Pré compilation ... JavaScript
  91. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6 2 4 3
  92. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret La ressource la plus rapide et la mieux optimisée est une ressource qui n'est pas envoyée. 4
  93. #WebPerformance / 6 avril 2017 @guillaumeehret 4 App Data

  94. #WebPerformance / 6 avril 2017 @guillaumeehret Cache serveur Cloud base

    de données ou I/O Temps fluctuant Abtraction Spring @Cacheable 4
  95. #WebPerformance / 6 avril 2017 @guillaumeehret Cache ressource 4 Browser

    Server Cache Request
  96. #WebPerformance / 6 avril 2017 @guillaumeehret 4 Browser Cache Server

    Response
  97. #WebPerformance / 6 avril 2017 @guillaumeehret 4 Browser Cache Server

    Response
  98. #WebPerformance / 6 avril 2017 @guillaumeehret 4 Browser Server Cache

    Si max-age n’est pas dépassé
  99. #WebPerformance / 6 avril 2017 @guillaumeehret 4 Browser Cache Server

    Response
  100. #WebPerformance / 6 avril 2017 @guillaumeehret 4 Browser Server Cache

    Si max-age n’est pas dépassé
  101. #WebPerformance / 6 avril 2017 @guillaumeehret 4 Browser Server Cache

    Si max-age est dépassé Request
  102. #WebPerformance / 6 avril 2017 @guillaumeehret 4 Browser Cache Server

    Response 304
  103. #WebPerformance / 6 avril 2017 @guillaumeehret Refresh / Cache 440ms

    first paint 2.9s loadtime First load 200Kb size 427ms first paint 700ms loadtime 18Kb size -3% first paint -75% loadtime -91% size 4
  104. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6 2 4 3 5
  105. #WebPerformance / 6 avril 2017 @guillaumeehret 5 https://giphy.com

  106. #WebPerformance / 6 avril 2017 @guillaumeehret 4 5

  107. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Services workers 4 5
  108. #WebPerformance / 6 avril 2017 @guillaumeehret 4 5

  109. #WebPerformance / 6 avril 2017 @guillaumeehret Web workers 4 5

  110. #WebPerformance / 6 avril 2017 @guillaumeehret 4 5

  111. #WebPerformance / 6 avril 2017 @guillaumeehret 4 5 var worker

    = new Worker('doWork.js'); worker.addEventListener('message', (e) => { console.log('Worker said: ', e.data); }, false); worker.postMessage('Hello World'); self.addEventListener('message',(e) => { self.postMessage(e.data); }, false); doWork.js
  112. #WebPerformance / 6 avril 2017 @guillaumeehret Service worker 5 JS

    SW cache
  113. #WebPerformance / 6 avril 2017 @guillaumeehret Service worker 5 SW

    cache HTTPS JS
  114. #WebPerformance / 6 avril 2017 @guillaumeehret Service worker 5 sw-precache

    module node générant la configuration sw-toolbox lib JS qui fournit des utilitaires pour gérer le cache avec différentes stratégies : networkFirst, cacheFirst, fastest, cacheOnly, networkOnly
  115. #WebPerformance / 6 avril 2017 @guillaumeehret 5 SW cache JS

    networkFirst cacheFirst fastest cacheOnly networkOnly
  116. #WebPerformance / 6 avril 2017 @guillaumeehret 5 gulp.task('generate-service-worker', function(callback) {

    let config = { cacheId: 'dev-mind', runtimeCaching: [{ urlPattern: '/(.*)', handler: 'networkFirst', options : { networkTimeoutSeconds: 2, maxAgeSeconds: 43200 } }], staticFileGlobs: [ `${paths.dist}/**/*.{js,html,css,png,jpg,json,gif,svg,webp,woff2}`], stripPrefix: `${paths.dist}/`, verbose: true }; swPrecache.write(`${paths.tmp}/service-worker.js`, config, callback); });
  117. #WebPerformance / 6 avril 2017 @guillaumeehret 5 if ('serviceWorker' in

    navigator) { navigator .serviceWorker .register('service-worker.js') .then(registration => { if (typeof registration.update === 'function') { registration.update(); } }) .catch(function(e) { console.error('Error during SW registration:', e); }); }
  118. #WebPerformance / 6 avril 2017 @guillaumeehret 5

  119. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6
  120. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret Charger le plus important tout de suite et le reste plus tard
  121. #WebPerformance / 6 avril 2017 @guillaumeehret 6 preload

  122. #WebPerformance / 6 avril 2017 @guillaumeehret 6 preload <link rel="preload"

    href="styles/app.css" as="style" onload="this.rel='stylesheet'"> <link rel="preload" href="/session/session.html" as="document"> <link rel="preload" href="/app.bundle.js" as="script">
  123. #WebPerformance / 6 avril 2017 @guillaumeehret prefetch 6

  124. #WebPerformance / 6 avril 2017 @guillaumeehret prefetch 6 <link rel="prefetch"

    href="https://fonts.googleapis.com/css?family=Roboto:400" onload="this.rel='stylesheet'">
  125. #WebPerformance / 6 avril 2017 @guillaumeehret preload / prefecth 440ms

    first paint 2.9s loadtime First load 200Kb size 300ms first paint 2.9s loadtime 200Kb size -31% first paint 0% loadtime 0% size 4
  126. #WebPerformance / 6 avril 2017 @guillaumeehret 1 Mesurer Transport Limiter

    2 3 4 Cache Offline Anticiper 5 6
  127. #WebPerformance / 6 avril 2017 @guillaumeehret Notre objectif first load

    refresh offline 1 < 1s 7.4s first paint 18.2s loadtime 1.6Mb size Départ
  128. #WebPerformance / 6 avril 2017 @guillaumeehret Notre objectif first load

    refresh offline 1 < 1s 300ms first paint 2.9s loadtime 200Kb size 220ms 700ms 18Kb first paint loadtime size 17ms 1.2s 0Kb first paint loadtime size 7.4s first paint 18.2s loadtime 1.6Mb size Départ
  129. #WebPerformance / 6 avril 2017 @guillaumeehret https://github.com/Dev-Mind/devoxx2017

  130. #WebPerformance / 6 avril 2017 @guillaumeehret Référence https://developers.google.com/web/fundamentals/performance https://github.com/ben-eb/gulp-uncss https://github.com/filamentgroup/loadCSS

    https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf https://nolanlawson.github.io/frontendday-2016/#1 https://www.youtube.com/watch?v=RWLzUnESylc 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/
  131. #WebPerformance / 6 avril 2017 @guillaumeehret #WebPerformance / 6 avril

    2017 @guillaumeehret First do it, then do it right, then do it better... Addy Osmani
  132. #WebPerformance / 6 avril 2017 @guillaumeehret https://github.com/Dev-Mind/devoxx2017 Thanks