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. Automating WEB Performance

  2. Robin Osborne @rposbo

  3. Dean Hume @deanohume

  4. None
  5. ASP.Net is Open Source!

  6. Does performance really matter?

  7. 2.2 SECONDS

  8. 2.2 SECONDS Increased downloads by 15.4%

  9. 2.2 SECONDS Increased downloads by 15.4% 60 million more downloads

  10. People != Patience

  11. whatdoesmysitecost.com

  12. None
  13. None
  14. It all started with...

  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
  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
  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
  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
  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
  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
  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
  22. Web Performance ain’t easy!

  23. None
  24. Manual Process

  25. None
  26. None
  27. HTML

  28. HTML CSS

  29. HTML CSS Images

  30. HTML CSS JS Images

  31. Steps 1. Images

  32. Steps 1. Images 2. CSS

  33. Steps 1. Images 2. CSS 3. JavaScript

  34. Steps 1. Images 2. CSS 3. JavaScript 4. HTML

  35. Steps 1. Images 2. CSS 3. JavaScript 4. HTML 5.

    Testing
  36. HTTP2

  37. HTTP2 HTTP2 Approved

  38. None
  39. TAKE A BREAK

  40. tinyurl.com/bulkybricks

  41. None
  42. index

  43. index products

  44. index products product

  45. index products product contact

  46. index products product contact about

  47. None
  48. None
  49. Material Design Lite

  50. getmdl.io/started

  51. whatdoesmysitecost.com

  52. Amsterdam

  53. Amsterdam Home page

  54. Amsterdam Home page - 21 seconds

  55. Amsterdam Home page - 21 seconds & cost 5c

  56. Amsterdam Home page - 21 seconds & cost 5c Product

    page
  57. Amsterdam Home page - 21 seconds & cost 5c Product

    page - 22 seconds
  58. Amsterdam Home page - 21 seconds & cost 5c Product

    page - 22 seconds & cost 35c
  59. webpagetest.org

  60. developers.google.com/speed/pagespeed/insights/

  61. 53

  62. Automate Measuring

  63. Automate all the things

  64. Begin the journey tinyurl.com/bulkybricks

  65. optimise before deploying

  66. Grunt Gulp

  67. Grunt Gulp

  68. Gulp Grunt

  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
  70. mod_pagespeed optimise after deploying

  71. Gulp Grunt

  72. GruntFile

  73. 1. NodeJS GruntFile

  74. 1. NodeJS GruntFile 2. Grunt

  75. 1. NodeJS GruntFile 2. Grunt 3. GruntFile

  76. nodejs.org

  77. NPM

  78. npm install grunt

  79. npm install -g grunt-cli

  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']); };
  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']); };
  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']); };
  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']); };
  84. Stack ‘em up

  85. None
  86. 1. Images

  87. Images are the biggest part of a web page Stylesheets,

    HTML, etc Images 60% JavaScript source: httparchive.org
  88. 1. Images Compression

  89. 160 KB 110 KB Before After

  90. 50KB

  91. Manual

  92. jpegmini mozjpeg kraken.io jpeg png gif svg

  93. jpegmini mozjpeg kraken.io optipng pngcrush zopflipng pngmini kraken.io jpeg png

    gif svg
  94. jpegmini mozjpeg kraken.io gifsicle kraken.io optipng pngcrush zopflipng pngmini kraken.io

    jpeg png gif svg
  95. jpegmini mozjpeg kraken.io gifsicle kraken.io SVGO kraken.io optipng pngcrush zopflipng

    pngmini kraken.io jpeg png gif svg
  96. Automated

  97. jpeg png gif svg grunt-contrib-imagemin

  98. npm install grunt-contrib-imagemin

  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']); };
  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']); };
  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']); };
  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']); };
  103. imagemin plugins

  104. grunt-contrib-imagemin 1. npm install <imagemin plugin>

  105. grunt-contrib-imagemin 1. npm install <imagemin plugin> 2. var plugin =

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

    plugin>'); 3. options: {use: [plugin( {config} )]} grunt-contrib-imagemin
  107. Squeezing the JPEG

  108. Before: 1.02 MB

  109. Before: 1.02 MB Default: 905 KB

  110. Before: 1.02 MB Default: 905 KB JPEG ReCompress: 616 KB

  111. Before: 1.02 MB Default: 905 KB JPEG ReCompress: 616 KB

    MozJPEG: 217 KB
  112. ~80%

  113. None
  114. 1. npm install imagemin-mozjpeg grunt-contrib-imagemin

  115. 1. npm install imagemin-mozjpeg 2. var moz= require('imagemin-mozjpeg'); grunt-contrib-imagemin

  116. 1. npm install imagemin-mozjpeg 2. var moz= require('imagemin-mozjpeg'); 3. options:

    {use: [moz( {quality:80} )]} grunt-contrib-imagemin
  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
  118. 109 KB 34.9 KB Before After

  119. Bashing the PNG

  120. Before: 5.23 MB

  121. Before: 5.23 MB Default: 906 KB

  122. Before: 5.23 MB Default: 906 KB Default + Level 7:

    905 KB
  123. Before: 5.23 MB Default: 906 KB Default + Level 7:

    905 KB Zopfli: 851 KB
  124. ~85%

  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
  126. 3.5 MB 68 KB Before After

  127. HOWEVER Default: 15s

  128. HOWEVER Default: 15s Default + Level 7: 1m23s

  129. HOWEVER Default: 15s Default + Level 7: 1m23s Zopfli: 8

    minutes!!
  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
  131. Slashing the SVG

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

    true }] }, files: [{ expand: true, cwd: 'images/', src: ['*.{png,jpg,gif,svg}'], dest: 'images/dist/' }] } } }) Gruntfile.js
  133. 14KB 4KB Before After

  134. 70%

  135. We kinda cheated!

  136. 6.25 MB JPEG PNG SVG

  137. 6.25 MB

  138. 6.25 MB 1.09 MB

  139. 5.16 MB

  140. ~83%

  141. Alternatives

  142. bit.ly/azure-jobs

  143. bit.ly/azure-jobs bit.ly/amazon-smush

  144. ?

  145. 86

  146. 1. Images Compression

  147. 1. Images Compression HTTP2 Approved

  148. Gruntfile • grunt-contrib-imagemin ◦ imagemin-mozjpeg ◦ imagemin-zopfli

  149. 1. Images Compression Format

  150. Progressive

  151. Baseline Progressive

  152. WebP

  153. Converting the JPEG

  154. Before: 1.02 MB MozJPEG: 217 KB

  155. Before: 1.02 MB MozJPEG: 217 KB WebP: 196 KB

  156. Converting the PNG

  157. Before: 5.23 MB Zopfli: 851 KB

  158. Before: 5.23 MB Zopfli: 851 KB WebP: 139 KB

  159. 6.25 MB JPEG PNG SVG

  160. 6.25 MB

  161. 6.25 MB 335 KB

  162. 5.9 MB

  163. ~94%

  164. npm install grunt-webp

  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
  166. 10KB PNG WebP 3.5 MB

  167. 1. Images Compression Format HTTP2 Approved

  168. 1. Images Compression Format Responsive Images

  169. None
  170. 72%

  171. None
  172. <picture> <source media="(min-width: 1024px)" srcset="dest/1024/dog.jpg"> <source media="(min-width: 640px)" srcset="dest/640/dog.jpg"> <source

    srcset="dest/320/dog.jpg"> <img src="dest/640/dog.jpg" alt="The fallback image."> </picture>
  173. <picture> <source media="(min-width: 1024px)" srcset="dest/1024/dog.jpg"> <source media="(min-width: 640px)" srcset="dest/640/dog.jpg"> <source

    srcset="dest/320/dog.jpg"> <img src="dest/640/dog.jpg" alt="The fallback image."> </picture>
  174. <picture> <source media="(min-width: 1024px)" srcset="dest/1024/dog.jpg"> <source media="(min-width: 640px)" srcset="dest/640/dog.jpg"> <source

    srcset="dest/320/dog.jpg"> <img src="dest/640/dog.jpg" alt="The fallback image."> </picture>
  175. npm install grunt-responsive-images

  176. brew install ImageMagick

  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'} } }
  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' } } }
  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' } } }
  180. None
  181. 112 KB

  182. 112 KB 70 KB

  183. 112 KB 70 KB 26 KB

  184. 77%

  185. 1. Images Compression Format Responsive Images HTTP2 Approved

  186. 1. Images Compression HTTP2 Approved

  187. 1. Images Compression Format HTTP2 Approved

  188. 1. Images Compression Format Responsive Images HTTP2 Approved

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

    grunt-responsive-images
  190. Stop!

  191. Stop! Psst! Fancy some more info on image optimisation? Check

    out imageoptimization.info
  192. 2.CSS

  193. Minify 2.CSS

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

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

  196. npm install grunt-contrib-cssmin

  197. cssmin: { dist: { files: [{ src: ['css/material-design.css', 'css/site.css'], dest:

    'css/result.min.css' }] } Gruntfile.js
  198. But wait...

  199. Clean your CSS div { color:red; } a { color:red

    ;}
  200. Clean your CSS div,a{color:red}

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

    .bar { display: block; } .baz { background: url('images/test.jpg'); width: 110px; }
  202. Clean your CSS .baz,.foo{background:url('images/test.jpg')} .foo{width:100px} .bar{display:block} .baz{width:110px}

  203. Browsers don’t care!

  204. Foundation CSS 22 % with whitespace optimizations 29% with all

    optimizations
  205. 155 KB

  206. 118 KB

  207. 25%

  208. 1 HTTP Request

  209. HTTP2 Approved

  210. cssmin: { dist: { files: [{ src: ['css/material-design.css', 'css/site.css'], dest:

    'css/result.min.css' }] } Gruntfile.js
  211. cssmin: { dist: { files: [{ expand: true, cwd: 'release/css',

    src: ['*.css', '!*.min.css'], dest: 'release/css', ext: '.min.css' }] } } Gruntfile.js
  212. 118 KB

  213. Minify 2.CSS HTTP2 Approved

  214. Alternatives

  215. bit.ly/azure-jobs

  216. CSS Minify Unused

  217. Before 107 KB After 30 KB

  218. 91% Unused CSS

  219. 91% Unused CSS

  220. Deliver only the goods that will be used !

  221. Chrome Developer Tools

  222. Chrome Developer Tools

  223. npm install grunt-uncss

  224. Gruntfile.js uncss: { dist: { files: { 'css/result.css': ['index.html'] }

    } }
  225. 118 KB

  226. 24 KB

  227. But wait...

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

    files: { 'css/result.css': ['index.html'] } } } Gruntfile.js
  229. uncss: { dist: { options: { ignore: ['#header', 'mdl-layout__header'] },

    files: { 'css/result.css': ['index.html'] } } } Gruntfile.js
  230. CSS Unused HTTP2 Approved

  231. CSS Minify Unused Critical

  232. HTML

  233. HTML CSS

  234. HTML CSS JS

  235. HTML CSS JS Blocking Blocking

  236. None
  237. “More Weight Doesn't Mean More Wait” Scott Jehl

  238. Finding the Critical Path

  239. None
  240. 14 KB

  241. None
  242. <!doctype html> <head> <style> /* inlined critical CSS */ </style>

    <script> loadCSS('deferred.css'); </script> </head> <body> ...body goes here </body> </html>
  243. <!doctype html> <head> <style> /* inlined critical CSS */ </style>

    <script> loadCSS('deferred.css'); </script> </head> <body> ...body goes here </body> </html>
  244. 1 Roundtrip 14 KB

  245. npm install grunt-critical

  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
  247. HTTP2 Approved

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

    grunt-responsive-images • grunt-contrib-cssmin • grunt-critical
  249. CSS Summary Minify

  250. CSS Summary Minify Clean

  251. CSS Summary Minify Clean Unused

  252. CSS Summary Minify Clean Unused Critical

  253. Stop!

  254. ?

  255. 94

  256. JavaScript

  257. JavaScript Minify

  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
  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)}}
  260. npm install grunt-contrib-uglify

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

    src: ['*.js'], dest: 'dest', ext: '.min.js' }] } } Gruntfile.js
  262. 140 KB

  263. 60 KB

  264. 40%

  265. Alternatives

  266. bit.ly/azure-jobs

  267. JavaScript Minify HTTP2 Approved

  268. HTML

  269. HTML Minify

  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
  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
  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>
  273. npm install grunt-contrib-htmlmin

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

    collapseBooleanAttributes: true, removeAttributeQuotes: true }, files: { dest: 'index.min.html', src: 'index.html' } } } Gruntfile.js
  275. 28.2 KB

  276. 19.3 KB

  277. 30%

  278. HTML Minify HTTP2 Approved

  279. 95

  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
  281. All In One

  282. mod_pagespeed

  283. ION

  284. bit.ly/akamai-bulky

  285. bit.ly/akamai-ion

  286. developers.google.com/web/tools/starter-kit

  287. ampproject.org Accelerated Mobile Pages

  288. Amsterdam

  289. Amsterdam Home page - 2 seconds & cost $0.01

  290. Amsterdam Home page - 2 seconds & cost $0.01 Product

    page - 1 second & cost $0.01
  291. Testing

  292. None
  293. Regression

  294. Automated

  295. npm install grunt-pagespeed

  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
  297. Fail a Build

  298. None
  299. None
  300. language: node_js node_js: - "0.12" before_install: npm install -g grunt-cli

    install: npm install script: grunt test travis.yml
  301. { "dependencies": { "grunt": "*", "grunt-pagespeed": "*" } } package.json

  302. Check every push

  303. None
  304. Alternatives

  305. None
  306. Summary

  307. Summary 1. Images

  308. Summary 1. Images 2. CSS

  309. Summary 1. Images 2. CSS 3. JavaScript

  310. Summary 1. Images 2. CSS 3. JavaScript 4. HTML

  311. Summary 1. Images 2. CSS 3. JavaScript 4. HTML 5.

    Testing
  312. Dean Hume @deanohume Robin Osborne @rposbo tinyurl.com/bulkybricks Thank you!