core(redirects): use lantern to compute savings (#5081)
This commit is contained in:
Родитель
e8e024b6ca
Коммит
6159de61b6
|
@ -11,7 +11,7 @@ const UnusedBytes = require('./byte-efficiency/byte-efficiency-audit');
|
||||||
|
|
||||||
class Redirects extends Audit {
|
class Redirects extends Audit {
|
||||||
/**
|
/**
|
||||||
* @return {!AuditMeta}
|
* @return {LH.Audit.Meta}
|
||||||
*/
|
*/
|
||||||
static get meta() {
|
static get meta() {
|
||||||
return {
|
return {
|
||||||
|
@ -20,68 +20,92 @@ class Redirects extends Audit {
|
||||||
failureDescription: 'Has multiple page redirects',
|
failureDescription: 'Has multiple page redirects',
|
||||||
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
|
||||||
helpText: 'Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/speed/docs/insights/AvoidRedirects).',
|
helpText: 'Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/speed/docs/insights/AvoidRedirects).',
|
||||||
requiredArtifacts: ['URL', 'devtoolsLogs'],
|
requiredArtifacts: ['URL', 'devtoolsLogs', 'traces'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!Artifacts} artifacts
|
* @param {LH.Artifacts} artifacts
|
||||||
* @return {!AuditResult}
|
* @param {LH.Audit.Context} context
|
||||||
|
* @return {Promise<LH.Audit.Product>}
|
||||||
*/
|
*/
|
||||||
static audit(artifacts) {
|
static async audit(artifacts, context) {
|
||||||
const data = {URL: artifacts.URL, devtoolsLog: artifacts.devtoolsLogs[Audit.DEFAULT_PASS]};
|
const settings = context.settings;
|
||||||
return artifacts.requestMainResource(data)
|
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
||||||
.then(mainResource => {
|
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
||||||
// redirects is only available when redirects happens
|
|
||||||
const redirectRequests = Array.from(mainResource.redirects || []);
|
|
||||||
|
|
||||||
// add main resource to redirectRequests so we can use it to calculate wastedMs
|
const traceOfTab = await artifacts.requestTraceOfTab(trace);
|
||||||
redirectRequests.push(mainResource);
|
const networkRecords = await artifacts.requestNetworkRecords(devtoolsLog);
|
||||||
|
const mainResource = await artifacts.requestMainResource({URL: artifacts.URL, devtoolsLog});
|
||||||
|
|
||||||
let totalWastedMs = 0;
|
const metricComputationData = {trace, devtoolsLog, traceOfTab, networkRecords, settings};
|
||||||
const pageRedirects = [];
|
const metricResult = await artifacts.requestLanternInteractive(metricComputationData);
|
||||||
|
|
||||||
// Kickoff the results table (with the initial request) if there are > 1 redirects
|
/** @type {Map<string, LH.Gatherer.Simulation.NodeTiming>} */
|
||||||
if (redirectRequests.length > 1) {
|
const nodeTimingsByUrl = new Map();
|
||||||
pageRedirects.push({
|
for (const [node, timing] of metricResult.pessimisticEstimate.nodeTimings.entries()) {
|
||||||
url: `(Initial: ${redirectRequests[0].url})`,
|
if (node.type === 'network') {
|
||||||
wastedMs: 0,
|
const networkNode = /** @type {LH.Gatherer.Simulation.GraphNetworkNode} */ (node);
|
||||||
});
|
nodeTimingsByUrl.set(networkNode.record.url, timing);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 1; i < redirectRequests.length; i++) {
|
// redirects is only available when redirects happens
|
||||||
const initialRequest = redirectRequests[i - 1];
|
const redirectRequests = Array.from(mainResource.redirects || []);
|
||||||
const redirectedRequest = redirectRequests[i];
|
|
||||||
|
|
||||||
const wastedMs = (redirectedRequest.startTime - initialRequest.startTime) * 1000;
|
// add main resource to redirectRequests so we can use it to calculate wastedMs
|
||||||
totalWastedMs += wastedMs;
|
redirectRequests.push(mainResource);
|
||||||
|
|
||||||
pageRedirects.push({
|
let totalWastedMs = 0;
|
||||||
url: redirectedRequest.url,
|
const pageRedirects = [];
|
||||||
wastedMs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const headings = [
|
// Kickoff the results table (with the initial request) if there are > 1 redirects
|
||||||
{key: 'url', itemType: 'text', text: 'Redirected URL'},
|
if (redirectRequests.length > 1) {
|
||||||
{key: 'wastedMs', itemType: 'ms', text: 'Time for Redirect', granularity: 1},
|
pageRedirects.push({
|
||||||
];
|
url: `(Initial: ${redirectRequests[0].url})`,
|
||||||
const summary = {wastedMs: totalWastedMs};
|
wastedMs: 0,
|
||||||
const details = Audit.makeTableDetails(headings, pageRedirects, summary);
|
|
||||||
|
|
||||||
return {
|
|
||||||
// We award a passing grade if you only have 1 redirect
|
|
||||||
score: redirectRequests.length <= 2 ? 1 : UnusedBytes.scoreForWastedMs(totalWastedMs),
|
|
||||||
rawValue: totalWastedMs,
|
|
||||||
displayValue: Util.formatMilliseconds(totalWastedMs, 1),
|
|
||||||
extendedInfo: {
|
|
||||||
value: {
|
|
||||||
wastedMs: totalWastedMs,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
details,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < redirectRequests.length; i++) {
|
||||||
|
const initialRequest = redirectRequests[i - 1];
|
||||||
|
const redirectedRequest = redirectRequests[i];
|
||||||
|
|
||||||
|
const initialTiming = nodeTimingsByUrl.get(initialRequest.url);
|
||||||
|
const redirectedTiming = nodeTimingsByUrl.get(redirectedRequest.url);
|
||||||
|
if (!initialTiming || !redirectedTiming) {
|
||||||
|
throw new Error('Could not find redirects in graph');
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore TODO(phulce): split NodeTiming typedef, these are always defined
|
||||||
|
const wastedMs = redirectedTiming.startTime - initialTiming.startTime;
|
||||||
|
totalWastedMs += wastedMs;
|
||||||
|
|
||||||
|
pageRedirects.push({
|
||||||
|
url: redirectedRequest.url,
|
||||||
|
wastedMs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const headings = [
|
||||||
|
{key: 'url', itemType: 'text', text: 'Redirected URL'},
|
||||||
|
{key: 'wastedMs', itemType: 'ms', text: 'Time for Redirect'},
|
||||||
|
];
|
||||||
|
const summary = {wastedMs: totalWastedMs};
|
||||||
|
const details = Audit.makeTableDetails(headings, pageRedirects, summary);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// We award a passing grade if you only have 1 redirect
|
||||||
|
score: redirectRequests.length <= 2 ? 1 : UnusedBytes.scoreForWastedMs(totalWastedMs),
|
||||||
|
rawValue: totalWastedMs,
|
||||||
|
displayValue: Util.formatMilliseconds(totalWastedMs, 1),
|
||||||
|
extendedInfo: {
|
||||||
|
value: {
|
||||||
|
wastedMs: totalWastedMs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
details,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const assert = require('assert');
|
||||||
/* eslint-env mocha */
|
/* eslint-env mocha */
|
||||||
const FAILING_THREE_REDIRECTS = {
|
const FAILING_THREE_REDIRECTS = {
|
||||||
startTime: 17,
|
startTime: 17,
|
||||||
url: 'http://exampel.com/',
|
url: 'https://m.example.com/final',
|
||||||
redirects: [
|
redirects: [
|
||||||
{
|
{
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
|
@ -30,25 +30,25 @@ const FAILING_THREE_REDIRECTS = {
|
||||||
|
|
||||||
const FAILING_TWO_REDIRECTS = {
|
const FAILING_TWO_REDIRECTS = {
|
||||||
startTime: 446.286,
|
startTime: 446.286,
|
||||||
url: 'http://lisairish.com/',
|
url: 'https://www.lisairish.com/',
|
||||||
redirects: [
|
redirects: [
|
||||||
{
|
{
|
||||||
startTime: 445.648,
|
startTime: 445.648,
|
||||||
url: 'https://lisairish.com/',
|
url: 'http://lisairish.com/',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
startTime: 445.757,
|
startTime: 445.757,
|
||||||
url: 'https://www.lisairish.com/',
|
url: 'https://lisairish.com/',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const SUCCESS_ONE_REDIRECT = {
|
const SUCCESS_ONE_REDIRECT = {
|
||||||
startTime: 136.383,
|
startTime: 136.383,
|
||||||
url: 'https://lisairish.com/',
|
url: 'https://www.lisairish.com/',
|
||||||
redirects: [{
|
redirects: [{
|
||||||
startTime: 135.873,
|
startTime: 135.873,
|
||||||
url: 'https://www.lisairish.com/',
|
url: 'https://lisairish.com/',
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,30 +58,45 @@ const SUCCESS_NOREDIRECT = {
|
||||||
redirects: [],
|
redirects: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockArtifacts = (mockChain) => {
|
|
||||||
return {
|
|
||||||
devtoolsLogs: {
|
|
||||||
[Audit.DEFAULT_PASS]: [],
|
|
||||||
},
|
|
||||||
requestNetworkRecords: () => {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
},
|
|
||||||
requestMainResource: function() {
|
|
||||||
return Promise.resolve(mockChain);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Performance: Redirects audit', () => {
|
describe('Performance: Redirects audit', () => {
|
||||||
|
let nodeTimings;
|
||||||
|
|
||||||
|
const mockArtifacts = (mockChain) => {
|
||||||
|
return {
|
||||||
|
traces: {},
|
||||||
|
devtoolsLogs: {},
|
||||||
|
requestLanternInteractive: () => ({pessimisticEstimate: {nodeTimings}}),
|
||||||
|
requestTraceOfTab: () => {},
|
||||||
|
requestNetworkRecords: () => [],
|
||||||
|
requestMainResource: function() {
|
||||||
|
return Promise.resolve(mockChain);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
it('fails when 3 redirects detected', () => {
|
it('fails when 3 redirects detected', () => {
|
||||||
return Audit.audit(mockArtifacts(FAILING_THREE_REDIRECTS)).then(output => {
|
nodeTimings = new Map([
|
||||||
|
[{type: 'network', record: {url: 'http://example.com/'}}, {startTime: 0}],
|
||||||
|
[{type: 'network', record: {url: 'https://example.com/'}}, {startTime: 5000}],
|
||||||
|
[{type: 'network', record: {url: 'https://m.example.com/'}}, {startTime: 10000}],
|
||||||
|
[{type: 'network', record: {url: 'https://m.example.com/final'}}, {startTime: 17000}],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Audit.audit(mockArtifacts(FAILING_THREE_REDIRECTS), {}).then(output => {
|
||||||
assert.equal(output.score, 0);
|
assert.equal(output.score, 0);
|
||||||
assert.equal(output.details.items.length, 4);
|
assert.equal(output.details.items.length, 4);
|
||||||
assert.equal(output.rawValue, 17000);
|
assert.equal(output.rawValue, 17000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails when 2 redirects detected', () => {
|
it('fails when 2 redirects detected', () => {
|
||||||
return Audit.audit(mockArtifacts(FAILING_TWO_REDIRECTS)).then(output => {
|
nodeTimings = new Map([
|
||||||
|
[{type: 'network', record: {url: 'http://lisairish.com/'}}, {startTime: 0}],
|
||||||
|
[{type: 'network', record: {url: 'https://lisairish.com/'}}, {startTime: 300}],
|
||||||
|
[{type: 'network', record: {url: 'https://www.lisairish.com/'}}, {startTime: 638}],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Audit.audit(mockArtifacts(FAILING_TWO_REDIRECTS), {}).then(output => {
|
||||||
assert.equal(output.score, 0.65);
|
assert.equal(output.score, 0.65);
|
||||||
assert.equal(output.details.items.length, 3);
|
assert.equal(output.details.items.length, 3);
|
||||||
assert.equal(Math.round(output.rawValue), 638);
|
assert.equal(Math.round(output.rawValue), 638);
|
||||||
|
@ -89,7 +104,12 @@ describe('Performance: Redirects audit', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes when one redirect detected', () => {
|
it('passes when one redirect detected', () => {
|
||||||
return Audit.audit(mockArtifacts(SUCCESS_ONE_REDIRECT)).then(output => {
|
nodeTimings = new Map([
|
||||||
|
[{type: 'network', record: {url: 'https://lisairish.com/'}}, {startTime: 0}],
|
||||||
|
[{type: 'network', record: {url: 'https://www.lisairish.com/'}}, {startTime: 510}],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return Audit.audit(mockArtifacts(SUCCESS_ONE_REDIRECT), {}).then(output => {
|
||||||
// If === 1 redirect, perfect score is expected, regardless of latency
|
// If === 1 redirect, perfect score is expected, regardless of latency
|
||||||
assert.equal(output.score, 1);
|
assert.equal(output.score, 1);
|
||||||
// We will still generate a table and show wasted time
|
// We will still generate a table and show wasted time
|
||||||
|
@ -99,7 +119,7 @@ describe('Performance: Redirects audit', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes when no redirect detected', () => {
|
it('passes when no redirect detected', () => {
|
||||||
return Audit.audit(mockArtifacts(SUCCESS_NOREDIRECT)).then(output => {
|
return Audit.audit(mockArtifacts(SUCCESS_NOREDIRECT), {}).then(output => {
|
||||||
assert.equal(output.score, 1);
|
assert.equal(output.score, 1);
|
||||||
assert.equal(output.details.items.length, 0);
|
assert.equal(output.details.items.length, 0);
|
||||||
assert.equal(output.rawValue, 0);
|
assert.equal(output.rawValue, 0);
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"include": [
|
"include": [
|
||||||
"lighthouse-cli/**/*.js",
|
"lighthouse-cli/**/*.js",
|
||||||
"lighthouse-core/audits/audit.js",
|
"lighthouse-core/audits/audit.js",
|
||||||
|
"lighthouse-core/audits/redirects.js",
|
||||||
"lighthouse-core/audits/accessibility/**/*.js",
|
"lighthouse-core/audits/accessibility/**/*.js",
|
||||||
"lighthouse-core/audits/dobetterweb/**/*.js",
|
"lighthouse-core/audits/dobetterweb/**/*.js",
|
||||||
"lighthouse-core/audits/byte-efficiency/**/*.js",
|
"lighthouse-core/audits/byte-efficiency/**/*.js",
|
||||||
|
|
|
@ -113,7 +113,7 @@ declare global {
|
||||||
requestSpeedline(trace: Trace): Promise<LH.Artifacts.Speedline>;
|
requestSpeedline(trace: Trace): Promise<LH.Artifacts.Speedline>;
|
||||||
|
|
||||||
// Metrics.
|
// Metrics.
|
||||||
requestConsistentlyInteractive(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
requestInteractive(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
||||||
requestEstimatedInputLatency(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
requestEstimatedInputLatency(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
||||||
requestFirstContentfulPaint(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
requestFirstContentfulPaint(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
||||||
requestFirstCPUIdle(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
requestFirstCPUIdle(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
||||||
|
@ -121,7 +121,7 @@ declare global {
|
||||||
requestSpeedIndex(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
requestSpeedIndex(data: LH.Artifacts.MetricComputationDataInput): Promise<Artifacts.LanternMetric|Artifacts.Metric>;
|
||||||
|
|
||||||
// Lantern metrics.
|
// Lantern metrics.
|
||||||
requestLanternConsistentlyInteractive(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
requestLanternInteractive(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
||||||
requestLanternEstimatedInputLatency(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
requestLanternEstimatedInputLatency(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
||||||
requestLanternFirstContentfulPaint(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
requestLanternFirstContentfulPaint(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
||||||
requestLanternFirstCPUIdle(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
requestLanternFirstCPUIdle(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче