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 {
|
||||
/**
|
||||
* @return {!AuditMeta}
|
||||
* @return {LH.Audit.Meta}
|
||||
*/
|
||||
static get meta() {
|
||||
return {
|
||||
|
@ -20,68 +20,92 @@ class Redirects extends Audit {
|
|||
failureDescription: 'Has multiple page redirects',
|
||||
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).',
|
||||
requiredArtifacts: ['URL', 'devtoolsLogs'],
|
||||
requiredArtifacts: ['URL', 'devtoolsLogs', 'traces'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Artifacts} artifacts
|
||||
* @return {!AuditResult}
|
||||
* @param {LH.Artifacts} artifacts
|
||||
* @param {LH.Audit.Context} context
|
||||
* @return {Promise<LH.Audit.Product>}
|
||||
*/
|
||||
static audit(artifacts) {
|
||||
const data = {URL: artifacts.URL, devtoolsLog: artifacts.devtoolsLogs[Audit.DEFAULT_PASS]};
|
||||
return artifacts.requestMainResource(data)
|
||||
.then(mainResource => {
|
||||
// redirects is only available when redirects happens
|
||||
const redirectRequests = Array.from(mainResource.redirects || []);
|
||||
static async audit(artifacts, context) {
|
||||
const settings = context.settings;
|
||||
const trace = artifacts.traces[Audit.DEFAULT_PASS];
|
||||
const devtoolsLog = artifacts.devtoolsLogs[Audit.DEFAULT_PASS];
|
||||
|
||||
// add main resource to redirectRequests so we can use it to calculate wastedMs
|
||||
redirectRequests.push(mainResource);
|
||||
const traceOfTab = await artifacts.requestTraceOfTab(trace);
|
||||
const networkRecords = await artifacts.requestNetworkRecords(devtoolsLog);
|
||||
const mainResource = await artifacts.requestMainResource({URL: artifacts.URL, devtoolsLog});
|
||||
|
||||
let totalWastedMs = 0;
|
||||
const pageRedirects = [];
|
||||
const metricComputationData = {trace, devtoolsLog, traceOfTab, networkRecords, settings};
|
||||
const metricResult = await artifacts.requestLanternInteractive(metricComputationData);
|
||||
|
||||
// Kickoff the results table (with the initial request) if there are > 1 redirects
|
||||
if (redirectRequests.length > 1) {
|
||||
pageRedirects.push({
|
||||
url: `(Initial: ${redirectRequests[0].url})`,
|
||||
wastedMs: 0,
|
||||
});
|
||||
}
|
||||
/** @type {Map<string, LH.Gatherer.Simulation.NodeTiming>} */
|
||||
const nodeTimingsByUrl = new Map();
|
||||
for (const [node, timing] of metricResult.pessimisticEstimate.nodeTimings.entries()) {
|
||||
if (node.type === 'network') {
|
||||
const networkNode = /** @type {LH.Gatherer.Simulation.GraphNetworkNode} */ (node);
|
||||
nodeTimingsByUrl.set(networkNode.record.url, timing);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i < redirectRequests.length; i++) {
|
||||
const initialRequest = redirectRequests[i - 1];
|
||||
const redirectedRequest = redirectRequests[i];
|
||||
// redirects is only available when redirects happens
|
||||
const redirectRequests = Array.from(mainResource.redirects || []);
|
||||
|
||||
const wastedMs = (redirectedRequest.startTime - initialRequest.startTime) * 1000;
|
||||
totalWastedMs += wastedMs;
|
||||
// add main resource to redirectRequests so we can use it to calculate wastedMs
|
||||
redirectRequests.push(mainResource);
|
||||
|
||||
pageRedirects.push({
|
||||
url: redirectedRequest.url,
|
||||
wastedMs,
|
||||
});
|
||||
}
|
||||
let totalWastedMs = 0;
|
||||
const pageRedirects = [];
|
||||
|
||||
const headings = [
|
||||
{key: 'url', itemType: 'text', text: 'Redirected URL'},
|
||||
{key: 'wastedMs', itemType: 'ms', text: 'Time for Redirect', granularity: 1},
|
||||
];
|
||||
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,
|
||||
};
|
||||
// Kickoff the results table (with the initial request) if there are > 1 redirects
|
||||
if (redirectRequests.length > 1) {
|
||||
pageRedirects.push({
|
||||
url: `(Initial: ${redirectRequests[0].url})`,
|
||||
wastedMs: 0,
|
||||
});
|
||||
}
|
||||
|
||||
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 */
|
||||
const FAILING_THREE_REDIRECTS = {
|
||||
startTime: 17,
|
||||
url: 'http://exampel.com/',
|
||||
url: 'https://m.example.com/final',
|
||||
redirects: [
|
||||
{
|
||||
startTime: 0,
|
||||
|
@ -30,25 +30,25 @@ const FAILING_THREE_REDIRECTS = {
|
|||
|
||||
const FAILING_TWO_REDIRECTS = {
|
||||
startTime: 446.286,
|
||||
url: 'http://lisairish.com/',
|
||||
url: 'https://www.lisairish.com/',
|
||||
redirects: [
|
||||
{
|
||||
startTime: 445.648,
|
||||
url: 'https://lisairish.com/',
|
||||
url: 'http://lisairish.com/',
|
||||
},
|
||||
{
|
||||
startTime: 445.757,
|
||||
url: 'https://www.lisairish.com/',
|
||||
url: 'https://lisairish.com/',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const SUCCESS_ONE_REDIRECT = {
|
||||
startTime: 136.383,
|
||||
url: 'https://lisairish.com/',
|
||||
url: 'https://www.lisairish.com/',
|
||||
redirects: [{
|
||||
startTime: 135.873,
|
||||
url: 'https://www.lisairish.com/',
|
||||
url: 'https://lisairish.com/',
|
||||
}],
|
||||
};
|
||||
|
||||
|
@ -58,30 +58,45 @@ const SUCCESS_NOREDIRECT = {
|
|||
redirects: [],
|
||||
};
|
||||
|
||||
const mockArtifacts = (mockChain) => {
|
||||
return {
|
||||
devtoolsLogs: {
|
||||
[Audit.DEFAULT_PASS]: [],
|
||||
},
|
||||
requestNetworkRecords: () => {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
requestMainResource: function() {
|
||||
return Promise.resolve(mockChain);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
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', () => {
|
||||
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.details.items.length, 4);
|
||||
assert.equal(output.rawValue, 17000);
|
||||
});
|
||||
});
|
||||
|
||||
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.details.items.length, 3);
|
||||
assert.equal(Math.round(output.rawValue), 638);
|
||||
|
@ -89,7 +104,12 @@ describe('Performance: Redirects audit', () => {
|
|||
});
|
||||
|
||||
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
|
||||
assert.equal(output.score, 1);
|
||||
// We will still generate a table and show wasted time
|
||||
|
@ -99,7 +119,7 @@ describe('Performance: Redirects audit', () => {
|
|||
});
|
||||
|
||||
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.details.items.length, 0);
|
||||
assert.equal(output.rawValue, 0);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"include": [
|
||||
"lighthouse-cli/**/*.js",
|
||||
"lighthouse-core/audits/audit.js",
|
||||
"lighthouse-core/audits/redirects.js",
|
||||
"lighthouse-core/audits/accessibility/**/*.js",
|
||||
"lighthouse-core/audits/dobetterweb/**/*.js",
|
||||
"lighthouse-core/audits/byte-efficiency/**/*.js",
|
||||
|
|
|
@ -113,7 +113,7 @@ declare global {
|
|||
requestSpeedline(trace: Trace): Promise<LH.Artifacts.Speedline>;
|
||||
|
||||
// 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>;
|
||||
requestFirstContentfulPaint(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>;
|
||||
|
||||
// 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>;
|
||||
requestLanternFirstContentfulPaint(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
||||
requestLanternFirstCPUIdle(data: LH.Artifacts.MetricComputationData): Promise<Artifacts.LanternMetric>;
|
||||
|
|
Загрузка…
Ссылка в новой задаче