Frontend Web Performance - better, faster, stronger

22725c2d3eb331146549bf0d5d3c050c?s=47 stefan judis
October 10, 2017
1.3k

Frontend Web Performance - better, faster, stronger

22725c2d3eb331146549bf0d5d3c050c?s=128

stefan judis

October 10, 2017
Tweet

Transcript

  1. 2.

    Stefan Judis Frontend Developer, Occasional Teacher, Meetup Organizer ❤ Open

    Source, Performance and Accessibility ❤ @stefanjudis
  2. 3.
  3. 6.
  4. 7.
  5. 8.
  6. 12.

    12 731 MILLION CHINA TOP 10 COUNTRIES WITH THE HIGHEST

    NUMBER OF INTERNET USERS 462 MILLION INDIA 286 MILLION USA 139 MILLION BRAZIL 132 MILLION INDONESIA 118 MILLION JAPAN 104 MILLION RUSSIA 93 MILLION NIGERIA 71 MILLION GERMANY 69 MILLION MEXICO www.internetworldstats.com/top20.htm
  7. 13.

    Finnland France Denmark Great Britian Ireland Italy Spain Germany USA

    Hungary 0GB 25GB 50GB 75GB 100GB de.statista.com/infografik/6188/4g-im-laendervergleich/ data with 4g for 35€ unlimited 50 40 20 10 8 7 4 2 1
  8. 26.
  9. 28.
  10. 31.

    Pinterest increased 15% SEO traffic and 15% conversion rate to

    signup. BBC loses 10% of users for every additional second in load time. The Trainline reduced latency and customers spent an extra ~$11.5 million a year.
  11. 35.

    - getting started - git clone git@github.com:stefanjudis/webperf-101-workshop.git npm i npm

    run dev git clone git@github.com:stefanjudis/webperf-101-workshop-final.git npm i npm run dev
  12. 36.
  13. 50.

    TOPICS 01 02 03 04 05 06 07 10 11

    12 13 14 15 SCRIPT LOADING LAZY LOADING IMAGE FORMATS IMAGE COMPRESSION VIDEO TEXT COMPRESSION RESPONSIVE IMAGES FONT LOADING CONCAT & MINIFY CRITICAL PATH NEW METRICS PRE LOADING HTTP/2 08 ICONS 16 BUDGETING 09 CACHING 17 UNUSED CODE
  14. 58.

    Need animation? GIF Need to preserve fine detail, with highest

    resolution or transparency? YES NO YES NO YES NO developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization
  15. 59.

    Need animation? GIF Need to preserve fine detail, with highest

    resolution or transparency? Need a large color palette? (+256 colors) YES NO YES NO YES NO developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization
  16. 60.

    Need animation? GIF Need to preserve fine detail, with highest

    resolution or transparency? Need a large color palette? (+256 colors) PNG-8 PNG-24 YES NO YES NO YES NO developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization
  17. 61.

    Need animation? GIF Need to preserve fine detail, with highest

    resolution or transparency? Need a large color palette? (+256 colors) PNG-8 PNG-24 JPEG YES NO YES NO YES NO developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/image-optimization
  18. 71.

    progressive enhancement <picture> <source srcset="https://.../some-image.jxr2" type="image/vnd.ms-photo"> <source srcset="https://.../some-image.jp2" type="image/jp2"> <source

    srcset="https://.../some-image.webp" type="image/webp"> <img src="https://.../some-image.png" alt="Some image"> </picture> www.useragentman.com/blog/2015/01/14/using-webp-jpeg2000-jpegxr-apng-now-with-picturefill-and-modernizr/
  19. 72.

    server side detection <IfModule mod_rewrite.c> RewriteEngine On # Check if

    browser support WebP images RewriteCond %{HTTP_ACCEPT} image/webp # Check if WebP replacement image exists RewriteCond %{DOCUMENT_ROOT}/$1.webp -f # Serve WebP image instead RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=accept:1] </IfModule> <IfModule mod_headers.c> Header append Vary Accept env=REDIRECT_accept </IfModule> AddType image/webp .webp images.guide/#-a-id-how-do-i-serve-webp-href-how-do-i-serve-webp-how-do-i-serve-webp-a- .htaccess
  20. 86.
  21. 91.

    load images responsively How I learned to love JavaScript Or

    how I saved some bytes... How I learned to love JavaScript Desktop 21MB (0.21€) Mobile 30MB (0.30€)
  22. 92.
  23. 97.
  24. 99.
  25. 100.

    # $ compress_video caniuse.mp4 caniuse_compressed.mp4 function compress_video() { if !

    [ $# -eq 2 ]; then echo "Wrong parameter usage: \n $ compress_video <inputFile> <outputFile>" return 1 fi ffmpeg -i $1 -vcodec h264 -b:v 1000k -acodec mp2 $2 } compress video on the fly
  26. 102.

    <video autoplay muted loop playsinline preload="metadata"> <source src="https://.../some-video.webm" type="video/webm"> <source

    src="https://.../some-video.mp4" type="video/mp4"> </video> progressive enhancement
  27. 104.

    # $ prepare_video caniuse.mp4 caniuse_compressed function prepare_video() { if !

    [ $# -eq 1 ]; then echo "Wrong parameter usage: \n $ compress_video <inputFile> <outputFileBase>" return 1 fi ffmpeg -i $1 -vcodec h264 -b:v 1000k -acodec mp2 $2.mp4 ffmpeg -i $2.mp4 -strict -2 $2.webm } compress video on the fly github.com/stefanjudis/.dotfiles/blob/master/functions.zsh#L61-L69
  28. 109.

    gzip <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE application/xml AddOutputFilterByType

    DEFLATE image/svg+xml AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE text/html AddOutputFilterByType DEFLATE text/javascript AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/xml ... ... ... </IfModule> .htaccess
  29. 112.

    brotli 2.4M application-97681b5991.min.js (0.02€) 708K application-97681b5991.min.js.gz (0.007€) 526K application-97681b5991.min.js.br (0.005€)

    samsaffron.com/archive/2016/06/15/the-current-state-of-brotli-compression brotli css-tricks.com/brotli-static-compression/
  30. 114.
  31. 121.

    caching of static assets “When you reload, browsers revalidate the

    page that you are currently on, even if that page hasn't expired yet. However, they also go a step further and revalidate all sub-resources on the page — things like images and JavaScript files.
  32. 130.
  33. 131.
  34. 133.

    Using SVGs <img src="image.svg" alt="image"> load as img <svg version="1.1"

    id="Layer_1" xmlns="http://www.w3.org/2000/svg" class="shopping-cart" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="5 24.366 90 61.125" xml:space="preserve"> <g>...</g> </svg> inline SVG .element { background-image: url(/images/image.svg); } load SVG in CSS
  35. 134.

    Using SVGs <svg style="display: none;"> <defs> <symbol id="basketball" viewBox="0 0

    100 100"> <title>Basketball</title> <path d="M28.1,3 ... "/> </symbol> </defs> </svg> <svg class="icon-basketball"> <use xlink:href="#basketball"></use> </svg> load as an icon system
  36. 147.
  37. 148.

    script loading HTML parsing fetch async vs. defer async defer

    fetch execution execution HTML parsing fetch fetch execution execution 1 2
  38. 152.
  39. 154.
  40. 155.
  41. 159.
  42. 172.

    latency on mobile (3g connection) de.slideshare.net/metrofun/reduce-mobile-latency Control plane latency Internet

    routing latency Internal latency (CDNs, ISPs, Caches, Proxies) (Firewalls, Load Balancers, Servers) ~600ms ~200ms
  43. 180.

    inline critical CSS <head> <style> // critical styles </style> <link

    rel="preload" href="path/to/mystylesheet.css" as="style" onload="this.rel='stylesheet'"> <noscript><link rel="stylesheet" href="path/to/mystylesheet.css"></noscript> <script> /*! loadCSS. [c]2017 Filament Group, Inc. MIT License */ (function(){ ... }()); /*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */ (function(){ ... }()); </script> </head>
  44. 184.
  45. 187.
  46. 192.

    function isElementInViewport (el) { var rect = el.getBoundingClientRect(); return (

    rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } How to tell if a DOM element is visible in the current viewport? stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
  47. 193.

    function isElementInViewport (el) { var rect = el.getBoundingClientRect(); // can

    trigger force layout/reflow return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } How to tell if a DOM element is visible in the current viewport? stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
  48. 195.

    Implementation with 'scroll' const speakers = [...document.querySelectorAll('.lazy')]; window.addEventListener('scroll', () =>

    { speakers.forEach(elem => { if (isElementInViewport(elem)) { // load image } }); });
  49. 196.

    Implementation with 'scroll' const speakers = [...document.querySelectorAll('.lazy')]; window.addEventListener('scroll', () =>

    { // really expensive speakers.forEach(elem => { if (isElementInViewport(elem)) { // load image } }); });
  50. 199.
  51. 200.
  52. 201.
  53. 202.
  54. 204.

    - Intersection Observer - (function() { const options = {

    threshold: 1.0 }; const intersectionObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // load image } }); }, options); [...document.querySelectorAll('.lazy')] .forEach(elem => intersectionObserver.observe(elem)); })();
  55. 205.

    - Intersection Observer - (function() { const options = {

    threshold: 1.0 }; const intersectionObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // load image } }); }, options); [...document.querySelectorAll('.lazy')] .forEach(elem => intersectionObserver.observe(elem)); })();
  56. 206.

    - Intersection Observer - (function() { const options = {

    threshold: 1.0 }; const intersectionObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // load image } }); }, options); [...document.querySelectorAll('.lazy')] .forEach(elem => intersectionObserver.observe(elem)); })();
  57. 207.

    (function() { const options = { threshold: 1.0 }; const

    intersectionObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // load image } }); }, options); [...document.querySelectorAll('.lazy')] .forEach(elem => intersectionObserver.observe(elem)); })(); - Intersection Observer -
  58. 214.

    Preload resources you have high- confidence will be used in

    the current page. Prefetch resources likely to be used for future navigations across multiple navigation boundaries. “
  59. 222.

    Link: <https://widget.com>; rel=dns-prefetch Link: <https://example.com>; rel=preconnect Link: <https://example.com/next-page.html>; rel=prefetch; Link:

    <https://example.com/logo-hires.jpg>; rel=preload; as=image; Usage <link rel="dns-prefetch" href="//widget.com"> <link rel="preconnect" href="//cdn.example.com"> <link rel="prefetch" href="//example.com/next-page.html"> <link rel="preload" href="//example.com/logo-hires.jpg" as="image"> link element header
  60. 230.

    - font loading behavior - FOUT FOIT FOIT FOIT FOIT

    3s 3s 3s 3s - www.zachleat.com/web/fout-foit-history/
  61. 231.

    body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,

    sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } github.com
  62. 234.

    @media screen and (min-width: 52em) { body { font-family: 'Droid

    Sans', sans-serif; } } no fonts for mobile?
  63. 235.

    - font rendering controls - font-display: auto; font-display: block; font-display:

    swap; font-display: fallback; font-display: optional; developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
  64. 236.

    - font rendering controls - font-display: auto; font-display: block; font-display:

    swap; font-display: fallback; font-display: optional; developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display
  65. 244.

    - Navigation Timing API - { "navigationStart": 1494722965671, "unloadEventStart": 0,

    "unloadEventEnd": 0, "redirectStart": 0, "redirectEnd": 0, "fetchStart": 1494722965838, "domainLookupStart": 1494722965841, "domainLookupEnd": 1494722972627, "connectStart": 1494722972627, "connectEnd": 1494722973191, "secureConnectionStart": 1494722972815, "requestStart": 1494722973191, "responseStart": 1494722973667, "responseEnd": 1494722973681, "domLoading": 1494722973681, "domInteractive": 1494722974288, "domContentLoadedEventStart": 1494722974288, "domContentLoadedEventEnd": 1494722974320, "domComplete": 1494722974571, "loadEventStart": 1494722974571, "loadEventEnd": 1494722974574 } w3c.github.io/navigation-timing/ window.performance.timing
  66. 247.

    - Resource Timing API - [ ..., { connectEnd: 117.69500000000001,

    connectStart: 117.69500000000001, decodedBodySize: 20133, domainLookupEnd: 117.69500000000001, domainLookupStart: 117.69500000000001, duration: 846.3100000000001, encodedBodySize: 20133, entryType: 'resource', fetchStart: 117.69500000000001, initiatorType: 'img', name: 'http://127.0.0.1:8080/image.png', redirectEnd: 0, redirectStart: 0, requestStart: 962.6750000000001, responseEnd: 964.0050000000001, responseStart: 963.45, secureConnectionStart: 0, startTime: 117.69500000000001, transferSize: 20391, workerStart: 0 } ] www.w3.org/TR/resource-timing-1/ window.performance.getEntriesByType('resource')
  67. 249.

    - User Timing API - w3c.github.io/user-timing/ if (talksSeen === nrOfTalks)

    { // measure how long this takes performance.mark('cornify_start'); [...Array(20)].forEach(cornify_add); performance.mark('cornify_end'); performance.measure( 'cornify_processing_time', 'cornify_start', 'cornify_end' ); }
  68. 250.

    - User Timing API - w3c.github.io/user-timing/ window.performance.getEntriesByType('mark') [ { duration:

    0 entryType: 'mark' name: 'cornify_start' startTime: 39613.885 }, ... ]
  69. 251.

    - User Timing API - w3c.github.io/user-timing/ window.performance.getEntriesByType('measure') [ { duration:

    5.9900000000016 entryType: 'measure' name: 'cornify_processing_time' startTime: 46002.34500000001 }, ... ]
  70. 257.

    - paint timing - www.w3.org/TR/paint-timing/ const perfObserver = new PerformanceObserver(list

    => { list.getEntries().forEach((entry) => { // Process entries // report back for analytics and monitoring // entry.name -> 'first-paint' // entry.name -> 'first-contentful-paint' } ); }); perfObserver.observe({entryTypes: ['paint']}); window.performance.getEntriesByType('paint')
  71. 258.

    github.com/w3c/longtasks var perfObserver = new PerformanceObserver(function(list) { list.getEntries().forEach((entry) => {

    // Process entries // report back for analytics and monitoring // ... }); }); perfObserver.observe({entryTypes: ['longtask']}); - longtask - window.performance.getEntriesByType('longtask')
  72. 260.
  73. 263.
  74. 269.
  75. 272.

    The perfect preload will always be slightly slower than the

    perfect HTTP/2 push, since it doesn't need to wait for the browser to make the request. However, preloading is drastically simpler and easier to debug. I recommend using it today, as browser support is only going to get better – but do keep an eye on devtools to ensure your pushed items are being used. “
  76. 277.

    HTTP/1.x vs HTTP/2 HTTP/1.x HTTP/2 DOMAIN SHARDING CONCAT FILES INLINE

    RESOURCES DOMAIN SHARDING CONCAT FILES INLINE RESOURCES * * * always measure
  77. 280.
  78. 289.

    - performance budget - 600 KB total page weight 01

    20 requests 02 03 1000 Speed Index 04 1s start render time 05 Start render 3-4s on 3g ...
  79. 291.

    What happens if something does not fit within the budget?

    Steve Souders Speedcurve Optimize? Remove? Don't do it?