Automating Web Performance

C620790ae5bf5b50c245b2e0ef95f338?s=47 Dean Hume
October 28, 2015

Automating Web Performance

This workshop was first presented at OSCON Conference on the 28th October 2015. It was a joint workshop with Dean Hume (http://deanhume.com/) and Robin Osborne (http://robinosborne.co.uk/)

http://conferences.oreilly.com/oscon/open-source-eu-2015

Presentation Details
----------------------------------

According to the HTTP Archive, the average web page consumes about 2 MB of total transfer size. This worrying trend is growing year on year, and as developers we need to ensure that our web pages are as mean and lean as possible. The easiest way to do this and ensure that our web pages forever stay lean is to automate our web performance workflow. As the famous meme goes – Automate all the things!

If you are lazy developers like us, then you will understand the need to automate as much of your development workflow as possible. Automation makes life easier and reduces the need to manually recreate your steps every time. However, when it comes to the different automation options out there – where do you even begin?

There are amazing open source tools that can make life a lot easier. In this talk we will run through various open source tools and libraries, and a step-by-step automation example covering web techniques, such as:

- Image compression and optimization
- Responsive images
- WebP images
- Removing unused CSS
- Critical path CSS
- Testing and benchmarking
- How to integrate this all in a continuous integration process
- Build, deployment, hosting, scaling

By the end of the session, we hope that developers will have the knowledge required to set up and automate the performance workflow of their websites. Engineers will look to attend this presentation because they can learn how to automate the performance of their websites; simple improvements can really go a long way toward improving performance.

For more information on the code repository used in this workshop, please checkout - https://github.com/rposbo/bulky-bricks-inc

C620790ae5bf5b50c245b2e0ef95f338?s=128

Dean Hume

October 28, 2015
Tweet

Transcript

  1. 4.
  2. 12.
  3. 13.
  4. 15.

    RULES Make Fewer HTTP Requests Use a Content Delivery Network

    Add an Expires Header Gzip Components Put Stylesheets at the Top Put Scripts at the Bottom
  5. 16.

    RULES Make Fewer HTTP Requests Use a Content Delivery Network

    Add an Expires Header Gzip Components Put Stylesheets at the Top Put Scripts at the Bottom
  6. 17.

    RULES Make Fewer HTTP Requests Use a Content Delivery Network

    Add an Expires Header Gzip Components Put Stylesheets at the Top Put Scripts at the Bottom
  7. 18.

    RULES Make Fewer HTTP Requests Use a Content Delivery Network

    Add an Expires Header Gzip Components Put Stylesheets at the Top Put Scripts at the Bottom
  8. 19.

    RULES Make Fewer HTTP Requests Use a Content Delivery Network

    Add an Expires Header Gzip Components Put Stylesheets at the Top Put Scripts at the Bottom
  9. 20.

    RULES Make Fewer HTTP Requests Use a Content Delivery Network

    Add an Expires Header Gzip Components Put Stylesheets at the Top Put Scripts at the Bottom
  10. 21.

    RULES Make Fewer HTTP Requests Use a Content Delivery Network

    Add an Expires Header Gzip Components Put Stylesheets at the Top Put Scripts at the Bottom
  11. 23.
  12. 25.
  13. 26.
  14. 27.
  15. 28.
  16. 36.
  17. 38.
  18. 41.
  19. 42.
  20. 47.
  21. 48.
  22. 52.
  23. 58.
  24. 61.

    53

  25. 69.

    764 15,758 145 1,283,067 1,691 Grunt Gulp 601 9,883 55

    1,356,374 4,403 Watchers Stars Contributors Downloads Plugins August 2015
  26. 72.
  27. 77.

    NPM

  28. 80.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ cssmin: { dist:

    { files: [ { src: 'stylesheets/about.css', dest: 'stylesheets/about.min.css' }, { src: 'stylesheets/articles.css', dest: 'stylesheets/articles.min.css' }, ] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-cssmin'); // Default tasks. grunt.registerTask('default', ['cssmin']); };
  29. 81.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ cssmin: { dist:

    { files: [ { src: 'stylesheets/about.css', dest: 'stylesheets/about.min.css' }, { src: 'stylesheets/articles.css', dest: 'stylesheets/articles.min.css' }, ] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-cssmin'); // Default tasks. grunt.registerTask('default', ['cssmin']); };
  30. 82.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ cssmin: { dist:

    { files: [ { src: 'stylesheets/about.css', dest: 'stylesheets/about.min.css' }, { src: 'stylesheets/articles.css', dest: 'stylesheets/articles.min.css' }, ] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-cssmin'); // Default tasks. grunt.registerTask('default', ['cssmin']); };
  31. 83.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ cssmin: { dist:

    { files: [ { src: 'stylesheets/about.css', dest: 'stylesheets/about.min.css' }, { src: 'stylesheets/articles.css', dest: 'stylesheets/articles.min.css' }, ] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-cssmin'); // Default tasks. grunt.registerTask('default', ['cssmin']); };
  32. 85.
  33. 86.
  34. 87.

    Images are the biggest part of a web page Stylesheets,

    HTML, etc Images 60% JavaScript source: httparchive.org
  35. 90.
  36. 91.
  37. 96.
  38. 99.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ imagemin: { dist:

    { files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-imagemin'); // Default tasks. grunt.registerTask('default', ['imagemin']); };
  39. 100.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ imagemin: { dist:

    { files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-imagemin'); // Default tasks. grunt.registerTask('default', ['imagemin']); };
  40. 101.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ imagemin: { dist:

    { files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-imagemin'); // Default tasks. grunt.registerTask('default', ['imagemin']); };
  41. 102.

    Gruntfile.js module.exports = function (grunt) { grunt.initConfig({ imagemin: { dist:

    { files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) // Load the plugins grunt.loadNpmTasks('grunt-contrib-imagemin'); // Default tasks. grunt.registerTask('default', ['imagemin']); };
  42. 106.

    1. npm install <imagemin plugin> 2. var plugin = require('<imagemin

    plugin>'); 3. options: {use: [plugin( {config} )]} grunt-contrib-imagemin
  43. 112.
  44. 113.
  45. 116.
  46. 117.

    var moz = require('imagemin-mozjpeg'); grunt.initConfig({ imagemin: { dist: { options:

    { use: [moz( {quality:80, quantTable: 3} )] }, files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) Gruntfile.js
  47. 124.
  48. 125.

    var zopfli = require('imagemin-zopfli'); grunt.initConfig({ imagemin: { dist: { options:

    { use: [zopfli( {more: true} )] }, files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) Gruntfile.js
  49. 130.

    grunt.initConfig({ imagemin: { dist: { options: { optimizationLevel: 3, //

    default values, so is not even necessary }, files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif}'], dest: 'images/dist/' }] } } }) Gruntfile.js
  50. 132.

    grunt.initConfig({ imagemin: { dist: { options: { svgoPlugins: [{ removeViewBox:

    true }] }, files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) Gruntfile.js
  51. 134.

    70%

  52. 137.
  53. 139.
  54. 140.
  55. 144.

    ?

  56. 145.

    86

  57. 152.
  58. 160.
  59. 162.
  60. 163.
  61. 165.

    webp:{ png: { files:[{ expand: true, cwd: "images/", src: "*.png",

    dest: "images/dist/" }], options: { binpath: "tools/cwebp.exe", preset: 'picture', verbose: true, quality: 80, alphaQuality: 80, compressionMethod: 6, …….. } } Gruntfile.js
  62. 169.
  63. 170.

    72%

  64. 171.
  65. 172.
  66. 173.
  67. 174.
  68. 177.

    Gruntfile.js responsive_images: { images: { options: { engine: 'im', sizes:

    [ { name: 'medium', width: 300 },{ name: 'large', width: 500, }] }, files: { src: '../before/images/tie-fighter-large.jpg' , dest: 'images/tie-fighter.jpg'} } }
  69. 178.

    Gruntfile.js responsive_images: { images: { options: { engine: 'im', sizes:

    [ { name: 'medium', width: 300 },{ name: 'large', width: 500, }] }, files: { 'images/tie-fighter.jpg': '../before/images/tie-fighter-large.jpg' } } }
  70. 179.

    Gruntfile.js responsive_images: { images: { options: { engine: 'im', sizes:

    [ { name: 'medium', width: 300 },{ name: 'large', width: 500, }] }, files: { 'images/tie-fighter.jpg': '../before/images/tie-fighter-large.jpg' } } }
  71. 180.
  72. 181.
  73. 184.

    77%

  74. 190.
  75. 192.
  76. 194.

    /* This is a comment */ h1 { font-size: 30px;

    line-height: 36px; } h1 small { font-size: 18px; /* Another comment */ } CSS
  77. 201.

    Clean your CSS .foo { background: url('images/test.jpg'); width: 100px; }

    .bar { display: block; } .baz { background: url('images/test.jpg'); width: 110px; }
  78. 205.
  79. 206.
  80. 207.

    25%

  81. 211.

    cssmin: { dist: { files: [{ expand: true, cwd: 'release/css',

    src: ['*.css', '!*.min.css'], dest: 'release/css', ext: '.min.css' }] } } Gruntfile.js
  82. 212.
  83. 225.
  84. 226.
  85. 228.

    uncss: { dist: { options: { ignore: ['#header', 'mdl-layout__header'] },

    files: { 'css/result.css': ['index.html'] } } } Gruntfile.js
  86. 229.

    uncss: { dist: { options: { ignore: ['#header', 'mdl-layout__header'] },

    files: { 'css/result.css': ['index.html'] } } } Gruntfile.js
  87. 232.
  88. 233.
  89. 236.
  90. 239.
  91. 240.
  92. 241.
  93. 242.

    <!doctype html> <head> <style> /* inlined critical CSS */ </style>

    <script> loadCSS('deferred.css'); </script> </head> <body> ...body goes here </body> </html>
  94. 243.

    <!doctype html> <head> <style> /* inlined critical CSS */ </style>

    <script> loadCSS('deferred.css'); </script> </head> <body> ...body goes here </body> </html>
  95. 246.

    critical: { dist: { options: { base: './', minify: true,

    dimensions: [ { width: 1300, height: 900 }, { width: 500, height: 900 }] }, files: { src: 'index.html', dest: ['index.html'] } } } Gruntfile.js
  96. 248.

    Gruntfile • grunt-contrib-imagemin ◦ imagemin-mozjpeg ◦ imagemin-zopfli • grunt-webp •

    grunt-responsive-images • grunt-contrib-cssmin • grunt-critical
  97. 253.
  98. 254.

    ?

  99. 255.

    94

  100. 256.
  101. 258.

    /** * Upgrades a specific element rather than all in

    the DOM. * @param {HTMLElement} element The element we wish to upgrade. * @param {string} jsClass The name of the class we want to upgrade * the element to. */ function upgradeElementInternal(element, jsClass) { // Only upgrade elements that have not already been upgraded. var dataUpgraded = element.getAttribute('data-upgraded'); if (dataUpgraded === null || dataUpgraded.indexOf(jsClass) === -1) { // Upgrade element. if (dataUpgraded === null) { dataUpgraded = ''; } element.setAttribute('data-upgraded', dataUpgraded + ',' + jsClass); …. JS
  102. 259.

    function upgradeElementInternal(a,b){var c=a.getAttribute("data- upgraded");if(null===c||-1===c.indexOf(b)){null===c&&(c=""),a. setAttribute("data-upgraded",c+","+b);var d=findRegisteredClass_(b);if(! d)throw"Unable to find a

    registered component for the given class.";var e=new d.classConstructor(a);e[componentConfigProperty_]=d, createdComponents_.push(e),d.callbacks.forEach(function(b){b(a)}),d. widget&&(a[b]=e);var f=document.createEvent("Events");f.initEvent ("mdl-componentupgraded",!0,!0),a.dispatchEvent(f)}}
  103. 261.

    uglify: { target: { files: [{ expand: true, cwd: 'src',

    src: ['*.js'], dest: 'dest', ext: '.min.js' }] } } Gruntfile.js
  104. 262.
  105. 263.
  106. 264.

    40%

  107. 268.
  108. 270.

    <body> <!-- Always shows a header, even in smaller screens.

    --> <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header"> <header class="mdl-layout__header"> <div class="mdl-layout__header-row"> <!-- Title --> <span class="mdl-layout-title">Bulky Bricks</span> <!-- Add spacer, to align navigation to the right --> <div class="mdl-layout-spacer"></div> <!-- Navigation. We hide it in small screens. --> <nav class="mdl-navigation mdl-layout--large-screen-only"> <a class="mdl-navigation__link" href="index.html">Home</a> HTML
  109. 271.

    <body>\n <!-- Always shows a header, even in smaller screens.

    --> <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">\n <header class="mdl-layout__header">\n <div class="mdl-layout__header-row">\n <!-- Title -->\n <span class="mdl-layout-title">Bulky Bricks</span>\n <!-- Add spacer, to align navigation to the right -->\n <div class="mdl-layout-spacer"></div>\n <!-- Navigation. We hide it in small screens. -->\n <nav class="mdl-navigation mdl-layout--large-screen-only">\n <a class="mdl-navigation__link" href="index.html">Home</a>\n HTML
  110. 272.

    <body> <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header"> <header class=mdl-layout__header><div class=mdl-layout__header- row> <span

    class=mdl-layout-title>Bulky Bricks</span> <div class=mdl- layout-spacer></div> <nav class="mdl-navigation mdl-layout--large- screen-only"><a class=mdl-navigation__link href=index.html>Home</a> <a class=mdl-navigation__link href=products.html>Products</a> <a class=mdl-navigation__link href=about.html>About Us</a><a class=mdl-navigation__link href=contact.html>Contact Us</a></nav> </div> </header> <div class=mdl-layout__drawer><span class=mdl- layout-title>Bulky Bricks</span><nav class=mdl-navigation>
  111. 274.

    htmlmin: { target: { options: { removeComments: true, collapseWhitespace: true,

    collapseBooleanAttributes: true, removeAttributeQuotes: true }, files: { dest: 'index.min.html', src: 'index.html' } } } Gruntfile.js
  112. 275.
  113. 276.
  114. 277.

    30%

  115. 279.

    95

  116. 280.

    Gruntfile • grunt-contrib-imagemin ◦ imagemin-mozjpeg ◦ imagemin-zopfli • grunt-webp •

    grunt-responsive-images • grunt-contrib-cssmin • grunt-critical • grunt-contrib-uglify • grunt-contrib-htmlmin
  117. 281.
  118. 283.

    ION

  119. 288.
  120. 290.
  121. 291.
  122. 292.
  123. 293.
  124. 294.
  125. 296.

    pagespeed: { options: { nokey: true }, prod: { options:

    { url: "http://rposbo.github.io/bulky-bricks-inc/after/index.html", locale: "en_GB", strategy: "desktop", threshold: 95 } } } Gruntfile.js
  126. 298.
  127. 299.
  128. 300.

    language: node_js node_js: - "0.12" before_install: npm install -g grunt-cli

    install: npm install script: grunt test travis.yml
  129. 303.
  130. 305.
  131. 306.