├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── gulpfile.js ├── package-lock.json ├── package.json ├── release.sh ├── tasks ├── lib │ └── budget.js └── sitespeed.js └── test └── gulpfile.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | 13 | # JavaScript 14 | [*.js] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | # YAML 19 | [*.yml] 20 | indent_style = space 21 | indent_size = 3 22 | 23 | # JSON 24 | [*.json] 25 | indent_style = space 26 | indent_size = 4 27 | 28 | # SHELL 29 | [*.sh] 30 | indent_style = space 31 | indent_size = 2 32 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/* 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | root: true 2 | 3 | env: 4 | node: true 5 | es6: true 6 | 7 | extends: airbnb-base 8 | 9 | rules: 10 | strict: 0 11 | "no-bitwise": error 12 | "no-console": 0 13 | "no-param-reassign": 0 14 | "comma-dangle": 0 15 | "prefer-template": 0 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | *result -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ankit Singhal & Peter Hedenskog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gulp-sitespeedio 2 | 3 | ## Test your website using sitespeed.io 4 | 5 | gulp-sitespeedio is a [gulp.js](https://github.com/gulpjs/gulp) task for testing your site against web performance best practice rules, fetch timings from a browser, test and enforce [performance budgets](#performance-budget), send performance metrics to [Graphite](http://graphite.wikidot.com/) using [sitespeed.io](https://www.sitespeed.io). 6 | 7 | Check out the [documentation](https://www.sitespeed.io/documentation/) to get a full overview of what you can do and test using [sitespeed.io](https://www.sitespeed.io). 8 | 9 | ## Getting Started 10 | 11 | If you haven't used [gulp](http://gulpjs.com/) before, be sure to check out the [Getting Started](https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md) guide, as it explains how to create a gulpfile.js as well as install and use gulp plugins. Once you're familiar with that process, you may install this plugin with this command: 12 | 13 | ```shell 14 | npm install gulp-sitespeedio --save-dev 15 | ``` 16 | 17 | Once the plugin has been installed, it may be enabled inside your gulpfile with this line of JavaScript: 18 | 19 | ```js 20 | var sitespeedio = require('gulp-sitespeedio'); 21 | ``` 22 | 23 | ## The sitespeedio task 24 | 25 | ### Required configuration properties 26 | 27 | To start testing pages, you must configure either a start URL for your crawl (yep sitespeed.io will crawl your site for a configurable depth) or an array of specific URL's that you want to test. 28 | 29 | Crawl the site with depth 1. 30 | ```javascript 31 | { 32 | urls: ['http://localhost/'], 33 | } 34 | ``` 35 | 36 | ### Testing specific URLs 37 | ```javascript 38 | { 39 | urls: ['https://www.sitespeed.io', 'https://www.sitespeed.io/faq/'] 40 | } 41 | ``` 42 | 43 | With these configuration properties set, you can add `sitespeedio` to your default tasks list. That'll look something like this: 44 | 45 | gulp.task('default', ['jshint', 'sitespeedio']); 46 | 47 | If you run it with custom options you need to run like this: 48 | 49 | ```javascript 50 | gulp.task('default', function (done) { 51 | sitespeedio({ 52 | urls: ["http://localhost:3000/"], 53 | })(done) 54 | }); 55 | ``` 56 | 57 | With this in place, gulp-sitespeedio will now collect performance metrics for your site. 58 | 59 | ## The result files 60 | The result files will automatically be stored in a temporary directory. If you want to change that, use 61 | the *resultBaseDir* property, like this: 62 | 63 | ```javascript 64 | 65 | { 66 | url: 'https://www.sitespeed.io', 67 | outputFolder: '/my/new/dir/' 68 | } 69 | ``` 70 | 71 | ## Use cases 72 | Fetch timings, sending performance metrics to Graphite and performance budgets. 73 | 74 | ### Fetching timing metrics 75 | 76 | You can choose to collect Navigation Timing and User Timing metrics using real browser. You can choose by using Firefox or Chrome. And you can configure the connection speed ([more info](https://www.sitespeed.io/documentation/#connectionspeed) by choosing between mobile3g, mobile3gfast, cable and native. And choose how many times you want to test each URL (default is 3). 77 | 78 | You surely want to combine it with running [Xvfb](https://gist.github.com/nwinkler/f0928740e7ae0e7477dd) to avoid opening the browser. 79 | 80 | ```javascript 81 | { 82 | urls: ['https://www.sitespeed.io', 'https://www.sitespeed.io/faq/'], 83 | browsertime: { 84 | browser: 'firefox' 85 | connectivity: 'cable', 86 | iterations: 5, 87 | } 88 | } 89 | ``` 90 | 91 | ### Performance Budget 92 | Test your site against a [performance budget](http://timkadlec.com/2013/01/setting-a-performance-budget/). You can test your site against almost all data collected by sitespeed.io. 93 | 94 | Checkout the [example Gruntfile]() and budget looks something like this: 95 | 96 | ``` 97 | budget: { 98 | "browsertime.pageSummary": [{ 99 | "metric": "statistics.timings.firstPaint.median", 100 | "max": 1000 101 | }, { 102 | "metric": "statistics.visualMetrics.FirstVisualChange.median", 103 | "max": 1000 104 | }], 105 | "coach.pageSummary": [{ 106 | "metric": "advice.performance.score", 107 | "min": 75 108 | }, { 109 | "metric": "advice.info.domElements", 110 | "max": 200 111 | }, { 112 | "metric": "advice.info.domDepth.max", 113 | "max": 10 114 | }, { 115 | "metric": "advice.info.iframes", 116 | "max": 0 117 | }, { 118 | "metric": "advice.info.pageCookies.max", 119 | "max": 5 120 | }], 121 | "pagexray.pageSummary": [{ 122 | "metric": "transferSize", 123 | "max": 100000 124 | }, { 125 | "metric": "requests", 126 | "max": 20 127 | }, { 128 | "metric": "missingCompression", 129 | "max": 0 130 | }, { 131 | "metric": "contentTypes.css.requests", 132 | "max": 1 133 | }, { 134 | "metric": "contentTypes.image.transferSize", 135 | "max": 100000 136 | }, { 137 | "metric": "documentRedirects", 138 | "max": 0 139 | }] 140 | } 141 | ``` 142 | 143 | If you want to include/exclude tests in the output, you can switch that by a gulp config like: 144 | 145 | ``` 146 | 147 | { 148 | urls: ['https://www.sitespeed.io', 'https://www.sitespeed.io/faq/'], 149 | showFailedOnly: true // or false 150 | } 151 | ``` 152 | 153 | 154 | ### Can't find the configuration 155 | 156 | sitespeed.io is highly configurable. The gulp-sitespeedio plugin will pass every option to sitespeed, you can see each and every configuration [here](). Each option needs to be called with full name (meaning the same as using **--** for the cli. Say for example that don't need the screenshot for each. Using the cli, you add the flag --browsertime.screenshot false 157 | 158 | Doing the same with the gulp plugin: 159 | ```javascript 160 | { 161 | url: 'https://www.sitespeed.io', 162 | browsertime: { 163 | screenshot: false 164 | } 165 | } 166 | ``` 167 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * gulp-sitespeed.io (http://www.sitespeed.io) 3 | */ 4 | 'use strict'; 5 | 6 | const gulp = require('gulp'); 7 | const sitespeedio = require('./tasks/sitespeed.js'); 8 | 9 | gulp.task('build', sitespeedio({ 10 | urls: ['https://www.sitespeed.io', 'https://www.sitespeed.io/faq/'], 11 | depth: 0, 12 | browsertime: { 13 | browser: 'chrome', 14 | connectivity: { 15 | profile: 'cable' 16 | }, 17 | iterations: 1, 18 | } 19 | })); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-sitespeedio", 3 | "description": "Analyze the web performance of your site using Gulp", 4 | "version": "2.0.0", 5 | "homepage": "https://github.com/dreamzmaster/gulp-sitespeedio", 6 | "author": { 7 | "name": "Ankit Singhal", 8 | "email": "singhal.ankit@hotmail.com" 9 | }, 10 | "keywords": [ 11 | "performance", 12 | "web", 13 | "budget", 14 | "webperf", 15 | "perfmatters", 16 | "perfbudget", 17 | "browser", 18 | "gulpplugin" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/dreamzmaster/gulp-sitespeedio.git" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/dreamzmaster/gulp-sitespeedio/issues" 26 | }, 27 | "engines": { 28 | "node": ">= 4.6.0" 29 | }, 30 | "dependencies": { 31 | "gulp": "~3.9.1", 32 | "gulp-util": "^3.0.4", 33 | "object-assign": "~2.0.0", 34 | "lodash.merge": "4.6.0", 35 | "sitespeed.io": "6.1.2", 36 | "temporary": "0.0.8" 37 | }, 38 | "devDependencies": { 39 | "eslint": "^4.13.1", 40 | "eslint-config-airbnb-base": "^7.2.0", 41 | "eslint-plugin-import": "^2.8.0", 42 | "gulp": "~3.9.1" 43 | }, 44 | "main": "./tasks/sitespeed.js", 45 | "scripts": { 46 | "lint": "npm run eslint", 47 | "eslint": "eslint .", 48 | "travis": "npm run lint" 49 | }, 50 | "license": "MIT" 51 | } 52 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # You need np for this to work 4 | # npm install --global np 5 | np $* 6 | -------------------------------------------------------------------------------- /tasks/lib/budget.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.checkBudget = (data, gutil, config) => { 4 | // lets get the budget! 5 | gutil.log(gutil.colors.blue('------------------------------------------------- Check budget')); 6 | 7 | let failing = false; 8 | const showFailedOnly = config.showFailedOnly; 9 | 10 | const noPassedTests = 0; 11 | const noFailingTests = 0; 12 | let noSkippedTests = 0; 13 | 14 | gutil.log(gutil.colors.green((showFailedOnly ? 'Will show only failing test.' : 'Show both failing and passing tests.') + 15 | ' Change this by set gulp config showFailedOnly to true')); 16 | 17 | gutil.log(gutil.colors.cyan('the showFailedOnly is:' + showFailedOnly)); 18 | 19 | data.budget.forEach((result) => { 20 | if (result.skipped) { 21 | noSkippedTests += noSkippedTests; 22 | if (!showFailedOnly) { 23 | gutil.log(gutil.colors.cyan('[SKIPPING]') + result.title + ' ' + result.url + ' value [' + result.value + ']'); 24 | } 25 | } else if (result.isOk) { 26 | noSkippedTests += noSkippedTests; 27 | if (!showFailedOnly) { 28 | gutil.log(gutil.colors.green('[PASSED]') + ' The budget for ' + result.title + ' ' + result.url + '[' + result.value + ']'); 29 | } 30 | } else { 31 | noSkippedTests += noSkippedTests; 32 | failing = true; 33 | gutil.log(gutil.colors.red('[FAILED]') + ' The budget for ' + result.title + ' ' + result.url + ' failed. ' + result.description); 34 | } 35 | }); 36 | 37 | gutil.log('We got ' + noPassedTests + ' passing tests, ' + gutil.colors.red(noFailingTests + ' failing') + (( 38 | noSkippedTests > 0) ? ' ' + noSkippedTests + ' skipped tests' : '.')); 39 | 40 | gutil.log(gutil.colors.blue('------------------------------------------------- Finished checking budget')); 41 | 42 | return failing; 43 | }; 44 | -------------------------------------------------------------------------------- /tasks/sitespeed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Sitespeed = require('sitespeed.io/lib/sitespeed'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const gutil = require('gulp-util'); 7 | const budget = require('./lib/budget'); 8 | const assign = require('object-assign'); 9 | const tmp = require('temporary'); 10 | 11 | const PluginError = gutil.PluginError; 12 | 13 | const PLUGIN_NAME = 'gulp-sitespeedio'; 14 | 15 | const readFile = (options) => { 16 | // absoulute or relative 17 | const joinedPath = path.join(process.cwd(), path.sep, options.file); 18 | const fullPathToFile = (options.file.charAt(0) === path.sep) ? options.file : joinedPath; 19 | 20 | const data = fs.readFileSync(fullPathToFile); 21 | let urls = data.toString().split('\n'); 22 | urls = urls.filter(l => l.length > 0); 23 | 24 | // we clean the file in the config to make 25 | // it look that we are feeding with URL array 26 | options.urls = urls; 27 | options.file = undefined; 28 | }; 29 | 30 | const gulpSitespeedio = (options) => { 31 | if (!options.url && !options.urls) { 32 | throw new PluginError(PLUGIN_NAME, 'Missing url option to Analyse'); 33 | } 34 | 35 | const dir = new tmp.Dir(); 36 | const config = { 37 | resultBaseDir: dir.path, 38 | html: true, 39 | showFailedOnly: false 40 | }; 41 | 42 | assign(config, options); 43 | 44 | // special handling for reading file with urls 45 | // in sitespeed.io we read the file within the cli, 46 | // so we need to do it from outside when we run without the 47 | // cli 48 | if (config.file) { 49 | readFile(config); 50 | } 51 | 52 | return (cb) => { 53 | gutil.log('Analyze your site’s web performance'); 54 | gutil.log(options); 55 | 56 | Sitespeed.run(options, (err, data) => { 57 | if (err) { 58 | cb(new gutil.PluginError(PLUGIN_NAME, err + '\n\n')); 59 | } else if (data && data.budget) { 60 | const isFailing = budget.checkBudget(data, gutil, config); 61 | 62 | if (isFailing) { 63 | cb(new gutil.PluginError(PLUGIN_NAME, 'FAILED BUDGETS')); 64 | } else { 65 | cb(); 66 | } 67 | } else { 68 | cb(); 69 | } 70 | }); 71 | }; 72 | }; 73 | 74 | module.exports = gulpSitespeedio; 75 | -------------------------------------------------------------------------------- /test/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let gulp = require('gulp'), 4 | sitespeedio = require('./tasks/sitespeed.js'), 5 | options = { 6 | urls: ['https://www.sitespeed.io', 'https://www.sitespeed.io/faq/'], 7 | browsertime: { 8 | connectivity: 'native', 9 | browser: 'chrome', 10 | iterations: 1, 11 | }, 12 | budget: { 13 | config: { 14 | 'browsertime.pageSummary': [{ 15 | metric: 'statistics.timings.firstPaint.median', 16 | max: 1000 17 | }, { 18 | metric: 'statistics.visualMetrics.FirstVisualChange.median', 19 | max: 1000 20 | }], 21 | 'coach.pageSummary': [{ 22 | metric: 'advice.performance.score', 23 | min: 75 24 | }, { 25 | metric: 'advice.info.domElements', 26 | max: 200 27 | }, { 28 | metric: 'advice.info.domDepth.max', 29 | max: 10 30 | }, { 31 | metric: 'advice.info.iframes', 32 | max: 0 33 | }, { 34 | metric: 'advice.info.pageCookies.max', 35 | max: 5 36 | }], 37 | 'pagexray.pageSummary': [{ 38 | metric: 'transferSize', 39 | max: 100000 40 | }, { 41 | metric: 'requests', 42 | max: 20 43 | }, { 44 | metric: 'missingCompression', 45 | max: 0 46 | }, { 47 | metric: 'contentTypes.css.requests', 48 | max: 1 49 | }, { 50 | metric: 'contentTypes.image.transferSize', 51 | max: 100000 52 | }, { 53 | metric: 'documentRedirects', 54 | max: 0 55 | }] 56 | } 57 | } 58 | }; 59 | 60 | gulp.task('build', sitespeedio(options)); 61 | --------------------------------------------------------------------------------