All files / src/reports transfer.js

100% Statements 38/38
76.92% Branches 10/13
100% Functions 14/14
100% Lines 38/38

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142                  1x 1x   1x 1x     6x                 6x 48x   48x 48x   6x 6x         12x 6x   6x   12x   6x 6x   6x 6x         12x 6x           12x           6x 6x       6x     1x                                   1x     1x           1x     1x         1x     1x           1x     1x         1x     1x         1x     1x        
'use strict';
/**
 * Report with transfer sizes from the Resource Timing API.
 *
 * @module reports/transfer
 * @see {@link Report}
 * @see <https://www.w3.org/TR/resource-timing-2/>
 */
 
const URL = require( 'url' ).URL;
const compute = require( '../compute' );
 
const rImg = /\.(?:jpeg|jpg|gif|png|svg)$/;
const rFont = /\.(?:woff2|woff:ttf)$/;
 
function getSizesFromEntries( category, entries ) {
	const sizes = {
		html: 0,
		css: 0,
		js: 0,
		img: 0,
		other: 0,
		total: 0
	};
 
	entries.forEach( ( entry ) => {
		sizes.total += entry.transferSize;
 
		const path = ( new URL( entry.name ) ).pathname;
		switch ( entry.initiatorType ) {
			case 'navigation':
				sizes.html += entry.transferSize;
				break;
			case 'link':
				// From <link> element or Link header.
				// e.g. a stylesheet, or preloaded image.
				// Upstream issue: https://github.com/w3c/resource-timing/issues/132
				if ( rImg.test( path ) ) {
					sizes.img += entry.transferSize;
				} else {
					sizes.css += entry.transferSize;
				}
				break;
			case 'script':
				sizes.js += entry.transferSize;
				break;
			case 'img':
				sizes.img += entry.transferSize;
				break;
			// Resource requested by CSS.
			// e.g. an imported stylesheet, background image, or font file.
			case 'css':
				/* istanbul ignore else */
				if ( rImg.test( path ) ) {
					sizes.img += entry.transferSize;
				} else if ( rFont.test( path ) ) {
					sizes.other += entry.transferSize;
				} else {
					sizes.css += entry.transferSize;
				}
				break;
			case 'xmlhttprequest':
			case 'fetch':
			case 'beacon':
			case 'other':
			default:
				sizes.other += entry.transferSize;
				break;
		}
	} );
 
	return sizes[ category ];
}
 
module.exports = {
	probes: [ 'transfer' ],
 
	// TODO: Improve the transfer-based metrics to have a concept of
	// of 'interaction-blocking', which measures only the startup module
	// (and any other requests that don't contain page-specific leaf modules).
	//
	// To do this cleanly, we may want to separate "probes" (which gather data)
	// from "metrics" (which interpret data), so that we can have the 'transfer'
	// probe collect the data with only minimal aggregation (e.g. by url, and by type),
	// with maybe "render-blocking" which is fairly universal. And then, the "mediawiki"
	// metrics interprets that data also, and defines a "interaction-blocking-bytes"
	// metric based on load.php urls and other MediaWiki-specific things.
 
	metrics: {
		pageWeight: {
			caption: 'Total size of transfers during page load',
			unit: 'B',
			analyse: ( series ) => compute.stats(
				series.transfer.entries.map( getSizesFromEntries.bind( null, 'total' ) )
			),
			compare: ( a, b ) => compute.diffStdev( a, b ),
			threshold: 5
		},
		html: {
			caption: 'Transfer size of HTML document',
			unit: 'B',
			analyse: ( series ) => compute.stats(
				series.transfer.entries.map( getSizesFromEntries.bind( null, 'html' ) )
			),
			compare: ( a, b ) => compute.diffStdev( a, b )
		},
		css: {
			caption: 'Transfer size of CSS resources',
			unit: 'B',
			analyse: ( series ) => compute.stats(
				series.transfer.entries.map( getSizesFromEntries.bind( null, 'css' ) )
			),
			compare: ( a, b ) => compute.diffStdev( a, b ),
			threshold: 3
		},
		js: {
			caption: 'Transfer size of JavaScript resources',
			unit: 'B',
			analyse: ( series ) => compute.stats(
				series.transfer.entries.map( getSizesFromEntries.bind( null, 'js' ) )
			),
			compare: ( a, b ) => compute.diffStdev( a, b )
		},
		img: {
			caption: 'Transfer size of Image document',
			unit: 'B',
			analyse: ( series ) => compute.stats(
				series.transfer.entries.map( getSizesFromEntries.bind( null, 'img' ) )
			),
			compare: ( a, b ) => compute.diffStdev( a, b )
		},
		other: {
			caption: 'Transfer size of other resources',
			unit: 'B',
			analyse: ( series ) => compute.stats(
				series.transfer.entries.map( getSizesFromEntries.bind( null, 'other' ) )
			),
			compare: ( a, b ) => compute.diffStdev( a, b )
		}
	}
};