width
/height
dimensions.
30 | ---
31 |
--------------------------------------------------------------------------------
/_audit/91-activity-audit-services.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Auditing Services
3 | type: activity
4 |
5 | tasks:
6 | -
7 | id: services-psi
8 | text: Use Google PageSpeed Insights to analyze a web page. Use the information to make a plan for fixing the issues.
9 | -
10 | id: services-wpt-mobile
11 | text: Use WebPageTest to determine how long it takes a web page to start rendering using a Moto E on 3G mobile connection.
12 | -
13 | id: services-wpt-repeat
14 | text: Use WebPageTest to determine the differences between a "cold" load (first-time visit) versus a repeat visit with browser cache primed.
15 | -
16 | id: services-wpt-film-strip
17 | text: Use WebPageTest to generate a film-strip of a web page render.
18 | -
19 | id: services-wpt-spof
20 | text: Use WebPageTest to simulate a network failure in order to test a site's resilience to network problems.
21 | -
22 | id: services-wpt-cost
23 | text: Use WhatDoesMySiteCost to roughly determine the cost of a web page in various countries.
24 | -
25 | id: services-yslow-worst
26 | text: Use YSlow to identify the biggest performance problems with a site.
27 | -
28 | id: services-yslow-js
29 | text: Use YSlow to identify all JavaScript-related scores and determine how to fix them (if needed).
30 | -
31 | id: services-yslow-ruleset
32 | text: Use YSlow with the ruleset designed for small sites and blogs. Compare the score to YSlow v2.
33 | ---
34 |
--------------------------------------------------------------------------------
/_automation/1-npm.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: npm
3 | type: tool
4 |
5 | links:
6 | -
7 | text: npm
8 | type: official
9 | url: https://www.npmjs.com/package/npm
10 | ---
11 |
12 | Included with your installation of Node.js is npm, the package manager for JavaScript. Many powerful frontend tools are hosted as packages on npm. Npm also supports the "scripts" property in package.json, which allows you to hook into and run scripts at various stages of packages lifecycle.
13 |
--------------------------------------------------------------------------------
/_automation/2-gulp.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Gulp
3 | type: tool
4 |
5 | links:
6 | -
7 | text: Gulp
8 | type: official
9 | url: https://www.npmjs.com/package/gulp
10 | ---
11 |
12 | A task manager specifically designed around node.js' strengths. You have to code a little bit compared to Grunt, but it is noticeably faster in almost all cases.
13 |
--------------------------------------------------------------------------------
/_automation/3-grunt.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Grunt
3 | type: tool
4 |
5 | links:
6 | -
7 | text: Grunt
8 | type: official
9 | url: https://www.npmjs.com/package/grunt-cli
10 | -
11 | text: using Grunt to generate critical CSS
12 | type: blog
13 | url: https://fourword.fourkitchens.com/article/use-grunt-and-advagg-inline-critical-css-drupal-7-theme
14 | ---
15 |
16 | Grunt is Gulp's predecessor. While simpler to understand and configure, Grunt is often much slower due to its internal arcitecture decisions. Sometimes that makes it less useful as a task runner since files might get saved quickly during development.
17 |
--------------------------------------------------------------------------------
/_automation/4-ghooks.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: git hooks
3 | type: tool
4 |
5 | links:
6 | -
7 | text: ghooks
8 | type: official
9 | url: https://www.npmjs.com/package/ghooks
10 | ---
11 |
12 | Provides a way to run certain tests or operations during git events. For instance, you can lint your code before committing, or run tests before pushing.
13 |
--------------------------------------------------------------------------------
/_budgets/1-grunt-perfbudget.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: grunt-perfbudget
3 | type: tool
4 |
5 | links:
6 | -
7 | text: grunt-perfbudget
8 | type: official
9 | url: https://www.npmjs.com/package/grunt-perfbudget
10 | -
11 | text: using grunt-perfbudget
12 | type: blog
13 | url: http://timkadlec.com/2014/05/performance-budgeting-with-grunt/
14 | -
15 | text: See how much your site costs
16 | url: http://whatdoesmysitecost.com/
17 | ---
18 |
19 | Uses the WebPageTest API to measure the _Speed Index_ of your site. Could be combined with git hooks to ensure a performance budget is passing before pushing code.
20 |
--------------------------------------------------------------------------------
/_budgets/2-grunt-phantomas.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: grunt-phantomas
3 | type: tool
4 |
5 | links:
6 | -
7 | text: grunt-phantomas
8 | type: official
9 | url: https://www.npmjs.com/package/grunt-phantomas
10 | ---
11 |
12 | A tool that allows you to collect trends of performance data. Unlike WPT _Speed Index_, which combined many factors into a single, digestible number, `grunt-phantomas` exposes the raw data and graphs its progress over time. It has built-in support for tests, allowing you to set a limit on any number of factors and easily spot the failing tests.
13 |
--------------------------------------------------------------------------------
/_budgets/90-activity-testing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Testing
3 | type: activity
4 |
5 | tasks:
6 | -
7 | id: budgets-test-phantomas
8 | text: Change this site (or the test!) so that it passes the phantomas
test.
9 | -
10 | id: budgets-test-critical
11 | text: Change this site (or the test!) so that it passes the critical-test
test.
12 | -
13 | id: budgets-test-psi
14 | text: Change this site (or the test!) so that it passes the psi
test.
15 | -
16 | id: budgets-test-grunt-perfbudget
17 | text: Use the grunt-perfbudget
test in the examples folder.
18 | ---
19 |
--------------------------------------------------------------------------------
/_budgets/91-activity-ci.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: GitHub + Travis
3 | type: activity
4 |
5 | tasks:
6 | -
7 | id: ci-fork
8 | text: Fork the training repo on GitHub and clone it locally.
9 | -
10 | id: ci-push
11 | text: Successfully push your changes to your own fork after passing the local performance tests.
12 | -
13 | id: ci-pr
14 | text: Create a PR to the main repo.
15 | -
16 | id: ci-
17 | text: Make changes until the Travis build passes and you see the beautiful green merge button
18 | ---
19 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | name: Frontend Performance Training
2 | permalink: pretty
3 | debug: true
4 | development: true
5 |
6 | markdown: redcarpet
7 | redcarpet:
8 | extensions: ["smart"]
9 |
10 | highlighter: true
11 | exclude: [CONTRIBUTING.md, README.md, SCHEDULE.md, TODO.md config.rb, package.json, gulpfile.js, Gemfile, Gemfile.lock, node_modules, "vendor", bower.json]
12 |
13 | collections:
14 | foundation:
15 | output: true
16 | audit:
17 | output: true
18 | drupal:
19 | output: true
20 | automation:
21 | output: true
22 | workflow:
23 | output: true
24 | budgets:
25 | output: true
26 |
27 | defaults:
28 | -
29 | scope:
30 | path: ""
31 | values:
32 | layout: "page"
33 | -
34 | scope:
35 | path: "tools"
36 | values:
37 | layout: "tool"
38 |
39 | navigation:
40 | -
41 | text: About
42 | title: Basic info about this training
43 | url: /about/
44 | accesskey: q
45 | -
46 | text: Foundation
47 | title: Learn about the fundamentals of frontend performance
48 | url: /foundation/
49 | accesskey: w
50 | -
51 | text: Audit
52 | title: Explore performance-related auditing tools
53 | url: /audit/
54 | accesskey: e
55 | -
56 | text: Drupal
57 | title: Get the best frontend performance out of Drupal 7
58 | url: /drupal/
59 | accesskey: r
60 | -
61 | text: Workflow
62 | title: Automate your workflow and deployment
63 | url: /workflow/
64 | accesskey: t
65 | -
66 | text: Budgets
67 | title: Set and maintain a performance budget
68 | url: /budgets/
69 | accesskey: y
70 |
--------------------------------------------------------------------------------
/_drupal/1-magic.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Magic
3 | type: tool
4 |
5 | links:
6 | -
7 | text: the Magic module
8 | type: official
9 | url: https://www.drupal.org/project/magic
10 | -
11 | text: Magic
12 | type: more-info
13 | url: http://fourword.fourkitchens.com/article/magic-frontend-performance-all-themes
14 | ---
15 |
16 | Magic is a lightweight module that implements many frontend performance goodies. Many popular base themes were individually implementing these settings. Using Magic, you can:
17 |
18 | * Move scripts to the footer
19 | * Utilize the `async` attribute on CSS/JS
20 | * Use additional config options for CSS aggregation.
21 |
--------------------------------------------------------------------------------
/_drupal/2-advagg.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Advanced CSS/JS Aggregation
3 | type: tool
4 |
5 | links:
6 | -
7 | text: AdvAgg
8 | type: official
9 | url: https://www.drupal.org/project/advagg
10 | -
11 | text: using AdvAgg to inline critical CSS
12 | type: blog
13 | url: https://fourword.fourkitchens.com/article/use-grunt-and-advagg-inline-critical-css-drupal-7-theme
14 | -
15 | text: using AdvAgg to inspect aggregates
16 | type: blog
17 | url: https://fourword.fourkitchens.com/article/use-advagg-information-tab-debug-or-optimize-css-and-js-aggregates-drupal-7
18 | -
19 | text: using prerender in Chrome/IE
20 | type: blog
21 | url: https://fourword.fourkitchens.com/article/prerender-chrome-instant-page-loads
22 | -
23 | text: Bookmarklet to generate DNS prefect link tags
24 | url: https://paul.kinlan.me/quick-script-for-prefetch/
25 | ---
26 |
27 | AdvAgg is another more advanced option, when your needs have outgrown Magic's functionality.
28 |
29 | * All of Magic's features, plus...
30 | * Stampede protection protects your Drupal site when aggregates are re-generated.
31 | * Prevent more than 4095 CSS selectors in an aggregate. (old IE bug)
32 | * Pre-compress aggregates. Faster than on-demand compression.
33 | * Configure the number of aggregates created.
34 | * Minification of JS (not available in Drupal 7 core).
35 | * Minify and/or inline CSS and JS.
36 | * Asynchronously load CSS/JS/fonts.
37 | * DNS Prefetch hostnames automatically.
38 |
--------------------------------------------------------------------------------
/_drupal/3-modernizr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Modernizr
3 | type: tool
4 |
5 | links:
6 | -
7 | text: the Modernizr module
8 | type: official
9 | url: https://www.drupal.org/project/modernizr
10 | -
11 | text: using Modernizr.load()
12 | type: blog
13 | url: http://fourword.fourkitchens.com/article/one-less-jpg
14 | ---
15 |
16 | Modernizr provides integration for the Modernizr JS library. Its primary performance-oriented feature is the ability to use `Modernizr.load()` to asynchronously request assets based on the feature detection it performs on each individual browser.
17 |
18 | It also contains a Drupal API that lets you build the smallest-possible Modernizr file, so you serve less unused feature tests.
19 |
--------------------------------------------------------------------------------
/_drupal/4-cdn.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Content Delivery Network (CDN)
3 | type: tool
4 |
5 | links:
6 | -
7 | text: the CDN module
8 | type: official
9 | url: https://www.drupal.org/project/CDN
10 | ---
11 |
12 | CDN provides integration with Content Delivery Networks, or CDNs. It means that you can host multiple copies of your assets in different geographical locations. Your visitors then receive copies of your assets from the location nearest to them, decreasing latency and speeding up their page loads.
13 |
--------------------------------------------------------------------------------
/_drupal/6-lazyloader.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Image Lazyloader
3 | type: tool
4 |
5 | links:
6 | -
7 | text: the Image Lazyloader module
8 | type: official
9 | url: https://www.drupal.org/project/lazyloader
10 | ---
11 |
12 | Lazyloading means that the browser doesn't download images until they are in or near the viewport window. By deferring the loading of images until they are actually viewed, you can save bandwidth.
13 |
--------------------------------------------------------------------------------
/_drupal/7-picture.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Picture
3 | type: tool
4 |
5 | links:
6 | -
7 | text: the Picture module
8 | type: official
9 | url: https://www.drupal.org/project/picture
10 | ---
11 |
12 | The Picture module implements `<head>
.
12 | -
13 | id: drupal-cssjs-advagg-compressed
14 | text: Use the AdvAgg module to generate compressed assets.
15 | -
16 | id: drupal-cssjs-advagg-minify
17 | text: Use the AdvAgg module to minify JavaScript.
18 | -
19 | id: drupal-cssjs-advagg-cdn
20 | text: Use the AdvAgg module to reference CDN-hosted jQuery instead of the built-in copy from Drupal core.
21 | -
22 | id: drupal-cssjs-advagg-bundler
23 | text: Use the AdvAgg module to control the number of CSS/JS aggregates on the Drupal site.
24 | -
25 | id: drupal-cssjs-advagg-load
26 | text: Use the AdvAgg module to load CSS and JS asynchronously.
27 | ---
28 |
--------------------------------------------------------------------------------
/_drupal/91-activity-theming.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Theming optimization
3 | type: activity
4 |
5 | tasks:
6 | -
7 | id: drupal-grunt-criticalcss
8 | text: Use Grunt or Gulp to generate critical CSS for a theme
9 | -
10 | id: drupal-theming-prefetch
11 | text: Add a DNS pre-fetch to your theme's page template
12 | -
13 | id: drupal-theming-prerender
14 | text: Add a prerender to your theme's page template (must use Chrome or IE11 to test).
15 | ---
16 |
--------------------------------------------------------------------------------
/_drupal/92-activity-media.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Media optimization
3 | type: activity
4 |
5 | tasks:
6 | -
7 | id: drupal-media-picture
8 | text: Use the Picture module to add responsive images to your site.
9 | -
10 | id: drupal-media-lazyload
11 | text: Use the Image Lazyloader module to defer images until they are actually seen.
12 | ---
13 |
--------------------------------------------------------------------------------
/_foundation/1-concatenation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Concatenation
3 | ---
4 |
5 | Concatenation means to combine several assets into one asset. When Drupal combines multiple stylesheets into an aggregate, that is concatenation. Another common concatenation is inlining assets into another asset (e.g. inline an image into CSS, or inlining critical CSS into HTML)
6 |
--------------------------------------------------------------------------------
/_foundation/2-minification.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Minification
3 | ---
4 |
5 | Minification is when all unimportant information is removed from a file. This typically means all whitespace and line-breaks are removed, but can also mean that JS variables are renamed to be as short as possible. Generous use of whitespace is required to maintain organized files, but the typical user never looks at your files so the whitespace is unnecessary bulk.
6 |
--------------------------------------------------------------------------------
/_foundation/3-compression.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Compression
3 | ---
4 |
5 | Compression is when your server zips up an asset before sending it to the browser. In most cases it is more efficient to zip the asset, send the smaller version, and unzip it in the browser, rather than transferring the uncompressed asset over the internet.
6 |
--------------------------------------------------------------------------------
/_foundation/4-sharding.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Domain sharding
3 | ---
4 |
5 | Domain sharding is when you set up multiple subdomains in order to "trick" the browser into opening more HTTP connections to a single website. Typically you'll see a domain that has several subdomains: css.example.com, js.example.com etc. Since a browser will only open a certain number of HTTP connections to a single host, domain sharding allows for more simultaneous connections to your server.
6 |
--------------------------------------------------------------------------------
/_img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
52 |
--------------------------------------------------------------------------------
/_img/maps-google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fourkitchens/frontend-perf/11d63b7dfcd98955c8048826d91cf162d02ccc9f/_img/maps-google.png
--------------------------------------------------------------------------------
/_includes/critical.css:
--------------------------------------------------------------------------------
1 | /* This file intentionally left blank. See gulp task: critical */
2 |
--------------------------------------------------------------------------------
/_includes/goals-codebase.html:
--------------------------------------------------------------------------------
1 | How many of these goals can you complete? Your progress will be automatically saved. Use this site's codebase as a starting point.
2 | -------------------------------------------------------------------------------- /_includes/goals.html: -------------------------------------------------------------------------------- 1 |How many of these goals can you complete? Your progress will be automatically saved as you check the boxes.
2 | -------------------------------------------------------------------------------- /_includes/list-tasks.html: -------------------------------------------------------------------------------- 1 |async
attribute to load JS asynchronously.
12 | ---
13 |
--------------------------------------------------------------------------------
/about.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: About this Training
3 | ---
4 |
5 | We will walk through the process of optimizing an existing site. Learn how to identify and measure performance bottlenecks, and respond with solutions to these problems.
18 |Tools can tirelessly check every change you make to a site, ensuring that major changes in performance do not go unnoticed during the development cycle.
24 |Use automation to always deploy an optimized build, using an asset pipeline that ensures your HTML, CSS, JS, and images are all as lean as possible.
25 |Build sites from scratch while balancing other priorities like feature backlogs and deadlines through the use of performance budgets.
31 |8:00 - 9:00 | 43 |Breakfast / Registration | 44 |
9:00 - 9:45 | 47 |Foundation of Performance | 48 |
9:45 - 10:15 | 51 |Performance Auditing | 52 |
10:15 - 10:45 | 55 |Coffee break | 56 |
10:45 - 11:15 | 59 |Performance Auditing (cont.) | 60 |
10:45 - 11:45 | 63 |Drupal Modules | 64 |
11:45 - 1:00 | 67 |Lunch | 68 |
1:00 - 1:45 | 71 |Workflow Automation | 72 |
1:45 - 3:15 | 75 |Continuous Integration | 76 |
3:15 - 3:45 | 79 |Coffee break | 80 |
3:45 - 4:30 | 83 |Performance Budgeting | 84 |
4:30 - 5:00 | 87 |Wrap up / Evaluations | 88 |
5:00 - 6:30 | 91 |Opening reception in the Exhibit Hall | 92 |
We are most familiar with Chrome and so all of the browser examples will follow suit. However, most of the tools demoed are available in all browsers.
13 |Every browser has a powerful set of development tools (DevTools) lurking inside, waiting for you to unlock its full potential. Every type of browser performance problem, from page loads to animation jank, can be debugged directly in the browser. We will cover the most oft-used tools.
16 |These tools are hosted services, meaning you don't have to rely on your personal internet connection to perform an audit. Some have the ability to throttle the connection, or choose where in the world the test is run. They all have APIs and tests can be run as part of a workflow.
29 | 30 |This is just as much a shift in workplace culture as it is process. Sometimes your client or product owner cannot or will not prioritize performance, so even with a warning system in place, you might not be afforded the time to fix it.
13 | Watch video about Performance Budgets 14 |The main difficulty most teams overcome is actually putting performance front and center during a normal workflow. Many problems are much harder to solve after weeks of work have been built on top of them. One way of finding problems early and often is the use of a performance budget.
18 |Just like a monetary budget, a performance budget places limits on your site, and as you develop features the budget keeps the site in check.
19 |20 | Performance budget metrics 21 | How to make a performance budget 22 |
23 | 24 | 25 |Time limits are a simple but effective metric that can be measured. Most any event can be listened for in the browser, and there are even whole Web APIs dedicated to performance timing.
30 |Requests are a simple thing to measure but might not always tell the whole story. For instance, you could place a limit of four CSS requests, but if one of them has 250K of inline base64 data added during development, it will not trigger a warning as long as you still only have four CSS files.
31 |File size of your page as a whole is always something good to keep in mind, especially since most mobile data plans have download limits. A heavy site costs your users money! WebPageTest includes a $ indicator on every results page letting you see an estimate of how much a single page load of your site costs in various countries.
32 |Milestones are specific to your website or app. For example, Twitter has a "time to first tweet" milestone, meaning the measure performance based on how soon the user can read content.
33 |
34 | Speed Index is a metric invented by WebPageTest.org, and combines many factors together which measure "visual completeness" and are able to factor in things like FOUT, giving better scores to sites that are progressively enhanced to be flexible and fault tolerant.
35 | Checking the Speed Index of various devices gives one of the most reliable indicators of real-world performance. It is also quick to reveal the performance impact of common sources of bloat, such as a slideshow at the top of a homepage. For that reason it can be a very effective bargaining tool during design or product discussions.
36 |
You can see a large collection of curated tools, articles, and tutorials at perf-tooling.today
Drupal offers a number of modules specifically designed to improve frontend performance. Combined with a well-made theme, these modules can help your Drupal sites fly!
8 | 9 |Many facets of performance are improving over time. One such change is HTTP/2, and it will cause sweeping changes to how sites download and arrive onto your computer. Some of the current optimizations that are a good idea with HTTP/1.1 will actually hurt your site's performance using HTTP/2. We will mark any potential regressions so you can keep the future in mind.
26 |Concatenate, minify, compress, shard.33 | 34 |
Ignore all the above advice.45 | 46 |
While HTTP/2 is on the horizon, HTTP/1.1 will not be going away soon.
52 |Therefore, today's training covers HTTP/1.1 techniques. These techniques will help all sites today, while HTTP/2 requires server knowledge to properly handle both types of traffic simultaneously.
53 |HTTP/2 was created to address all of the performance workarounds described in the previous section about HTTP/1.1. So if your server is using the newer protocol, none of those workarounds are necessary.
57 |For exmple, with HTTP/2 the server and browser can more efficiently use the connection they create between each other, and multiple files can be sent using one single connection. Thus, the gains achieved by concatenating when using HTTP1.1 is now a restriction that the server has no choice but to obey when using HTTP2.
58 |If you're interested in reading about HTTP/2 in order to be prepared for the future, feel free to browse the official community site.
59 | 60 | Read about HTTP/2 61 | Watch a video about HTTP/2 62 | See a demo of HTTP/2 63 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // Gulp 2 | // 3 | // This is not a normal require, because our gulp-help tool (which provides the 4 | // nice task descriptions on the command-line) requires changing the function 5 | // signature of gulp tasks to include the task description. 6 | var gulp = require('gulp-help')(require('gulp')); 7 | 8 | // Gulp / Node utilities 9 | var u = require('gulp-util'); 10 | var log = u.log; 11 | var c = u.colors; 12 | var del = require('del'); 13 | var spawn = require('child_process').spawn; 14 | var sequence = require('run-sequence'); 15 | 16 | // Basic workflow plugins 17 | var prefix = require('gulp-autoprefixer'); 18 | var sass = require('gulp-sass'); 19 | var bs = require('browser-sync'); 20 | var reload = bs.reload; 21 | 22 | // Performance workflow plugins 23 | var concat = require('gulp-concat'); 24 | var mincss = require('gulp-minify-css'); 25 | var imagemin = require('gulp-imagemin'); 26 | var uncss = require('gulp-uncss'); 27 | var uglify = require('gulp-uglify'); 28 | var critical = require('critical').stream; 29 | 30 | // Performance testing plugins 31 | var psi = require('psi'); 32 | var wpt = require('webpagetest'); 33 | var ngrok = require('ngrok'); 34 | 35 | // ----------------------------------------------------------------------------- 36 | // Remove old CSS 37 | // 38 | // This task only deletes the files generated by the 'sass' and 'css' tasks. 39 | // The 'uncss' task is slow to run and less frequently needed, so we keep the 40 | // build process fast by preserving the results of uncss. 41 | // ----------------------------------------------------------------------------- 42 | gulp.task('clean-css', false, function() { 43 | return del(['css/{all,main}*'], function (err) { 44 | if (err) { log(c.red('clean-css'), err); } 45 | else { 46 | log( 47 | c.green('clean-css'), 48 | 'deleted old stylesheets' 49 | ); 50 | } 51 | }); 52 | }); 53 | 54 | // ----------------------------------------------------------------------------- 55 | // Sass Task 56 | // 57 | // Compiles Sass and runs the CSS through autoprefixer. A separate task will 58 | // combine the compiled CSS with vendor files and minify the aggregate. 59 | // ----------------------------------------------------------------------------- 60 | gulp.task('sass', 'Compiles Sass and uses autoprefixer', function() { 61 | return gulp.src('_sass/**/*.scss') 62 | .pipe(sass({ 63 | outputStyle: 'nested', 64 | onSuccess: function(css) { 65 | var dest = css.stats.entry.split('/'); 66 | log(c.green('sass'), 'compiled to', dest[dest.length - 1]); 67 | }, 68 | onError: function(err, res) { 69 | log(c.red('Sass failed to compile')); 70 | log(c.red('> ') + err.file.split('/')[err.file.split('/').length - 1] + ' ' + c.underline('line ' + err.line) + ': ' + err.message); 71 | } 72 | })) 73 | .pipe(prefix("last 2 versions", "> 1%")) 74 | .pipe(gulp.dest('css')); 75 | }); 76 | 77 | // ----------------------------------------------------------------------------- 78 | // Combine and Minify CSS 79 | // 80 | // This task minifies all the CSS found in the css/ directory, including the 81 | // uncss-ed copies of bootstrap. The end result is a minified aggregate, ready 82 | // to be served. 83 | // ----------------------------------------------------------------------------- 84 | gulp.task('css', 'Removes old CSS, compiles Sass, combines CSS, minifies CSS', ['clean-css', 'sass'], function() { 85 | bs.notify('Running: CSS task'); 86 | 87 | return gulp.src('css/*.css') 88 | .pipe(concat('all.min.css')) 89 | .pipe(mincss()) 90 | .pipe(gulp.dest('css')) 91 | .pipe(gulp.dest('_site/css')) 92 | .pipe(reload({stream: true})); 93 | }); 94 | 95 | 96 | // ----------------------------------------------------------------------------- 97 | // UnCSS Task 98 | // 99 | // Checks the site's usage of Bootstrap and strips unused styles out. Outputs 100 | // the resulting files in the css/ directory where they will be combined and 101 | // minified by a separate task. 102 | // 103 | // Note: this task requires a local server to be running because it references 104 | // the actual compiled site to calculate the unused styles. 105 | // ----------------------------------------------------------------------------- 106 | gulp.task('uncss', 'Removes unused CSS from frameworks', function() { 107 | return gulp.src([ 108 | 'node_modules/bootstrap/dist/css/bootstrap.css', 109 | 'node_modules/bootstrap/dist/css/bootstrap-theme.css' 110 | ]) 111 | .pipe(uncss({ 112 | html: [ 113 | 'http://localhost:3000/', 114 | 'http://localhost:3000/audit/', 115 | 'http://localhost:3000/foundation/', 116 | 'http://localhost:3000/budgets/' 117 | ] 118 | })) 119 | .pipe(gulp.dest('css/')); 120 | }); 121 | 122 | // ----------------------------------------------------------------------------- 123 | // Combine and Minify JS 124 | // 125 | // Just like the CSS task, the end result of this task is a minified aggregate 126 | // of JS ready to be served. 127 | // ----------------------------------------------------------------------------- 128 | gulp.task('js', 'Combines and minifies JS', function() { 129 | return gulp.src('_js/**/*.js') 130 | .pipe(concat('all.min.js')) 131 | .pipe(uglify()) 132 | .pipe(gulp.dest('js')) 133 | .pipe(gulp.dest('_site/js')) 134 | .pipe(reload({stream: true})); 135 | }); 136 | 137 | // ----------------------------------------------------------------------------- 138 | // Generate critical-path CSS 139 | // 140 | // This task generates a small, minimal amount of your CSS based on which rules 141 | // are visible (aka "above the fold") during a page load. We will use a Jekyll 142 | // template command to inline the CSS when the site is generated. 143 | // 144 | // All styles should be directly applying to an element visible when your 145 | // website renders. If the user has to scroll even a small amount, it's not 146 | // critical CSS. 147 | // ----------------------------------------------------------------------------- 148 | gulp.task('critical', 'Generates critical CSS for the example site', function () { 149 | return gulp.src('_site/index.html') 150 | .pipe(critical({ 151 | dest: '_includes/critical.css', 152 | css: ['css/all.min.css'], 153 | dimensions: [{ 154 | width: 320, 155 | height: 480 156 | },{ 157 | width: 768, 158 | height: 1024 159 | },{ 160 | width: 1280, 161 | height: 960 162 | }], 163 | minify: true, 164 | extract: false 165 | })); 166 | }); 167 | 168 | // ----------------------------------------------------------------------------- 169 | // Minify SVGs and compress images 170 | // 171 | // It's good to maintain high-quality, uncompressed assets in your codebase. 172 | // However, it's not always appropriate to serve such high-bandwidth assets to 173 | // users, in order to reduce mobile data plan usage. 174 | // ----------------------------------------------------------------------------- 175 | gulp.task('imagemin', 'Minifies and compresses images on the example site', function() { 176 | return gulp.src('_img/**/*') 177 | .pipe(imagemin({ 178 | progressive: true, 179 | svgoPlugins: [{removeViewBox: false}] 180 | })) 181 | .pipe(gulp.dest('img')); 182 | }); 183 | 184 | // ----------------------------------------------------------------------------- 185 | // Jekyll 186 | // 187 | // Regenerate the Jekyll site when files are touched. The --watch flag is not 188 | // used because the bs/watch tasks will handle the "watching" of the files. 189 | // ----------------------------------------------------------------------------- 190 | gulp.task('jekyll', 'Development task: generate the Jekyll server', function() { 191 | bs.notify('Running: Jekyll task'); 192 | 193 | return spawn('bundle', ['exec', 'jekyll', 'build', '--config=_config.yml'], {stdio: 'inherit'}) 194 | .on('close', reload); 195 | }); 196 | 197 | // ----------------------------------------------------------------------------- 198 | // Jekyll Serve 199 | // 200 | // This command is used exclusively by Travis to start Jekyll in the background 201 | // then run tests against it. 202 | // ----------------------------------------------------------------------------- 203 | gulp.task('jekyll-serve', false, function(callback) { 204 | spawn('bundle', ['exec', 'jekyll', 'serve', '--detach', '--no-watch', '--config=_config.yml'], {stdio: 'inherit'}) 205 | .on('close', callback) 206 | }); 207 | 208 | // ----------------------------------------------------------------------------- 209 | // Browser Sync 210 | // 211 | // Makes web development better by eliminating the need to refresh. Essential 212 | // for CSS development and multi-device testing. 213 | // ----------------------------------------------------------------------------- 214 | gulp.task('browser-sync', false, function() { 215 | bs({ 216 | server: './_site/' 217 | }); 218 | }); 219 | 220 | // ----------------------------------------------------------------------------- 221 | // Browser Sync using Proxy server 222 | // 223 | // This is a second (unused) example to demonstrate how you'd connect to a local 224 | // server that runs itself. Examples would be a PHP site such as Wordpress or a 225 | // Drupal site, or a node.js site like Express. 226 | // 227 | // Usage: gulp browser-sync-proxy --port 8080 228 | // ----------------------------------------------------------------------------- 229 | gulp.task('browser-sync-proxy', false, function () { 230 | bs({ 231 | // Point this to your pre-existing server. 232 | proxy: 'my-local-site.dev' + (u.env.port ? ':' + u.env.port : ''), 233 | // This tells BrowserSync to auto-open a tab once it boots. 234 | open: true 235 | }, function(err, bs) { 236 | if (err) { 237 | console.log(bs.options); 238 | } 239 | }); 240 | }); 241 | 242 | 243 | // ----------------------------------------------------------------------------- 244 | // Watch tasks 245 | // 246 | // These tasks are run whenever a file is saved. Don't confuse the files being 247 | // watched (gulp.watch blobs in this task) with the files actually operated on 248 | // by the gulp.src blobs in each individual task. 249 | // 250 | // A few of the performance-related tasks are excluded because they can take a 251 | // bit of time to run and don't need to happen on every file change. If you want 252 | // to run those tasks more frequently, set up a new watch task here. 253 | // ----------------------------------------------------------------------------- 254 | gulp.task('watch', 'Watch for changes to various files and process them', function() { 255 | gulp.watch('_sass/**/*.scss', ['css']); 256 | gulp.watch('_js/**/*.js', ['js']); 257 | gulp.watch('_img/**/*', ['imagemin']); 258 | gulp.watch(['./**/*.{md,html}', '!./_site/**/*.*', '!./node_modules/**/*.*'], ['jekyll']); 259 | }); 260 | 261 | // ----------------------------------------------------------------------------- 262 | // Convenience task for development. 263 | // 264 | // This is the command you run to warm the site up for development. It will do 265 | // a full build, open BrowserSync, and start listening for changes. 266 | // ----------------------------------------------------------------------------- 267 | gulp.task('bs', 'Main development task:', ['css', 'js', /*'imagemin',*/ 'jekyll', 'browser-sync', 'watch']); 268 | 269 | // ----------------------------------------------------------------------------- 270 | // Performance test: Phantomas 271 | // 272 | // Phantomas can be used to test granular performance metrics. This example 273 | // ensures that the site never exceeds a specific number of HTTP requests. 274 | // ----------------------------------------------------------------------------- 275 | gulp.task('phantomas', 'Performance: measure a URL using Phantomas', function() { 276 | var limit = 5; 277 | var phantomas = spawn('./node_modules/.bin/phantomas', ['--url', 'http://localhost:4000', '--assert-requests=' + limit]); 278 | 279 | // Uncomment this block to see the full Phantomas output. 280 | // phantomas.stdout.on('data', function (data) { 281 | // data = data.toString().slice(0, -1); 282 | // log('Phantomas:', data); 283 | // }); 284 | 285 | // Catch any errors. 286 | phantomas.on('error', function (err) { 287 | log(err); 288 | }); 289 | 290 | // Log results to console. 291 | phantomas.on('close', function (code) { 292 | // Exit status of 0 means success! 293 | if (code === 0) { 294 | log('Phantomas:', c.green('✔︎ Yay! The site makes ' + limit + ' or fewer HTTP requests.')); 295 | } 296 | 297 | // Exit status of 1 means the site failed the test. 298 | else if (code === 1) { 299 | log('Phantomas:', c.red('✘ Rats! The site makes more than ' + limit + ' HTTP requests.')); 300 | process.exit(code); 301 | } 302 | 303 | // Other exit codes indicate problems with the test itself, not a failed test. 304 | else { 305 | log('Phantomas:', c.bgRed('', c.black('Something went wrong. Exit code'), code, '')); 306 | process.exit(code); 307 | } 308 | }); 309 | }); 310 | 311 | // ----------------------------------------------------------------------------- 312 | // Performance test: PageSpeed Insights 313 | // 314 | // Initializes a public tunnel so the PageSpeed service can access your local 315 | // site, then it tests the site. This task outputs the standard PageSpeed results. 316 | // 317 | // The task will output a standard exit code based on the result of the PSI test 318 | // results. 0 is success and any other number is a failure. To learn more about 319 | // bash-compatible exit status codes read this page: 320 | // 321 | // http://tldp.org/LDP/abs/html/exit-status.html 322 | // ----------------------------------------------------------------------------- 323 | gulp.task('psi', 'Performance: PageSpeed Insights', function() { 324 | // Set up a public tunnel so PageSpeed can see the local site. 325 | return ngrok.connect(4000, function (err_ngrok, url) { 326 | log(c.cyan('ngrok'), '- serving your site from', c.yellow(url)); 327 | 328 | // Run PageSpeed once the tunnel is up. 329 | psi.output(url, { 330 | strategy: 'mobile', 331 | threshold: 80 332 | }, function (err_psi, data) { 333 | // Log any potential errors and return a FAILURE. 334 | if (err_psi) { 335 | log(err_psi); 336 | process.exit(1); 337 | } 338 | 339 | // Kill the ngrok tunnel and return SUCCESS. 340 | process.exit(0); 341 | }); 342 | }); 343 | }); 344 | 345 | // ----------------------------------------------------------------------------- 346 | // Performance test: Critical CSS 347 | // 348 | // This test checks our generated critical CSS to make sure there are no external 349 | // requests which would block rendering. Having external calls defeats the entire 350 | // purpose of inlining the CSS, since the external call blocks rendering. 351 | // ----------------------------------------------------------------------------- 352 | gulp.task('critical-test', 'Performance: check the contents of critical CSS', function () { 353 | // Spawn our critical CSS test and suppress all output. 354 | var critical = spawn('./examples/critical/critical.sh', ['>/dev/null']); 355 | 356 | // Catch any errors. 357 | critical.on('error', function (err) { 358 | log(err); 359 | }); 360 | 361 | // Log results to console. 362 | critical.on('close', function (code) { 363 | // Exit status of 0 means success! 364 | if (code === 0) { 365 | log('Critical:', c.green('✔︎ Yay! The generated CSS makes zero external requests.')); 366 | } 367 | 368 | // Exit status of anything else means the test failed. 369 | else { 370 | log('Critical:', c.red('✘ Rats! The generated CSS makes ' + code + ' external requests.')); 371 | process.exit(code); 372 | } 373 | }); 374 | }); 375 | 376 | // ----------------------------------------------------------------------------- 377 | // Performance test: WebPageTest.org 378 | // 379 | // Initializes a public tunnel so the PageSpeed service can access your local 380 | // site, then it tests the site. This task outputs the standard PageSpeed results. 381 | // ----------------------------------------------------------------------------- 382 | gulp.task('wpt', 'Performance: WebPageTest.org', function () { 383 | var wpt_test = wpt('www.webpagetest.org', process.env.wptkey); 384 | 385 | // Set up a public tunnel so WebPageTest can see the local site. 386 | return ngrok.connect(4000, function (err_ngrok, url) { 387 | log(c.cyan('ngrok'), '- serving your site from', c.yellow(url)); 388 | 389 | // The `url` variable was supplied by ngrok. 390 | wpt_test.runTest(url, function(err_wpt, data_wpt) { 391 | // Log any potential errors and return a FAILURE. 392 | if (err_wpt) { 393 | log(err_wpt); 394 | process.exit(1); 395 | } 396 | 397 | // Open window to results. 398 | var wpt_results = 'http://www.webpagetest.org/result/' + data_wpt.data.testId; 399 | log(c.green('✔︎ Opening results page:', wpt_results)); 400 | spawn('open', [wpt_results]); 401 | 402 | // Note to developer. 403 | log(c.yellow('⚠️ Please keep this process running until WPT is finished.')); 404 | log(c.yellow('⚠️ Once the results load, hit Control + C to kill this process.')); 405 | }); 406 | }); 407 | }); 408 | 409 | // ----------------------------------------------------------------------------- 410 | // Performance test: run everything at once 411 | // 412 | // Having a task that simply runs other tasks is nice for Travis or other CI 413 | // solutions, because you only have to specify one command, and gulp handles 414 | // the rest. 415 | // ----------------------------------------------------------------------------- 416 | gulp.task('test', 'Run all tests', function (callback) { 417 | sequence( 418 | 'jekyll-serve', 419 | 'critical-test', 420 | 'phantomas', 421 | 'psi', 422 | 'wpt', 423 | callback 424 | ); 425 | }); 426 | 427 | // ----------------------------------------------------------------------------- 428 | // Default: load task listing 429 | // 430 | // Instead of launching some unspecified build process when someone innocently 431 | // types `gulp` into the command line, we provide a task listing so they know 432 | // what options they have without digging into the file. 433 | // ----------------------------------------------------------------------------- 434 | gulp.task('default', false, ['help']); 435 | -------------------------------------------------------------------------------- /img/.gitkeep: -------------------------------------------------------------------------------- 1 | # Gulp will generate minified/compressed images in this directory. 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Welcome 3 | --- 4 | 5 |Wifi: Drupal
Pass: nola2016
GitHub: https://github.com/fourkitchens/frontend-perf
Many people place these tools in the primary theme folder of their site. However, it is often more useful to put them in the root of your repo, so that both custom modules and themes can share the same workflow, coding standards, and so forth.
13 |Sometimes installing a few modules won't cut it, and you'll need to rely on additional tools to improve your theme's output. A theme is often the source of many images, scripts, and other things that slow the frontend down.
17 |Using a consistent process to make sure all of your assets are always lean is a good way to control the page weight of a site.
18 | 19 |With these tools you will be able to capture repetitive tasks and run them automatically whenever you need them. It might be as frequent as a file save, or only once per deploy. But either way, having an automated workflow is less error-prone and more reliable than manually remembering and executing the tasks.
22 | 23 |Here is a list of tools we frequently use in our frontend workflow:
32 | 33 |