351 lines
11 KiB
JavaScript
351 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
const helpers = require('../../support/helpers');
|
|
const path = require('path');
|
|
const merge = require('lodash.merge');
|
|
const get = require('lodash.get');
|
|
const log = require('intel').getLogger('sitespeedio.plugin.html');
|
|
const chunk = require('lodash.chunk');
|
|
const packageInfo = require('../../../package');
|
|
const renderer = require('./renderer');
|
|
const metricHelper = require('./metricHelper');
|
|
const markdown = require('markdown').markdown;
|
|
const isEmpty = require('lodash.isempty');
|
|
|
|
const summaryBoxesSetup = require('./setup/summaryBoxes'),
|
|
detailedSetup = require('./setup/detailed');
|
|
|
|
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
|
|
|
class HTMLBuilder {
|
|
constructor(context, options) {
|
|
this.storageManager = context.storageManager;
|
|
this.timestamp = context.timestamp.format(TIME_FORMAT);
|
|
this.options = options;
|
|
this.summary = {};
|
|
this.budget = context.budget;
|
|
this.context = context;
|
|
this.pageRuns = [];
|
|
this.pageSummaries = [];
|
|
this.summaries = [];
|
|
this.inlineCSS = [];
|
|
}
|
|
|
|
addType(id, name, type) {
|
|
switch (type) {
|
|
case 'run': {
|
|
this.pageRuns.push({ id, name });
|
|
break;
|
|
}
|
|
case 'pageSummary': {
|
|
this.pageSummaries.push({ id, name });
|
|
break;
|
|
}
|
|
case 'summary': {
|
|
this.summaries.push({ id, name });
|
|
break;
|
|
}
|
|
default:
|
|
log.info('Got a undefined page type ' + type);
|
|
}
|
|
}
|
|
|
|
addInlineCSS(css) {
|
|
this.inlineCss.push(css);
|
|
}
|
|
|
|
async render(dataCollector) {
|
|
const options = this.options;
|
|
const name = this.context.name;
|
|
const timestamp = this.timestamp;
|
|
const nTestedPages = dataCollector.getURLs().length;
|
|
log.debug('Render HTML for %s page(s) ', nTestedPages);
|
|
const errors = dataCollector.getErrorUrls();
|
|
const css = this.inlineCSS.join('');
|
|
if (Object.keys(errors).length > 0) {
|
|
this.summary.errors = {
|
|
errors,
|
|
menu: 'errors'
|
|
};
|
|
}
|
|
|
|
const validPages = dataCollector.getWorkingUrls();
|
|
const summaryBoxes = summaryBoxesSetup(dataCollector.getSummary('index'));
|
|
const detailedBoxes = detailedSetup(dataCollector.getSummary('detailed'));
|
|
|
|
this.summary.pages = {
|
|
pageTitle: `Overview of ${helpers.plural(
|
|
nTestedPages,
|
|
'page'
|
|
)} for ${name} at ${timestamp}`,
|
|
pageDescription: 'See all the tested pages on a high level.',
|
|
pages: validPages
|
|
};
|
|
|
|
this.summary.index = {
|
|
pageTitle: `Executive Summary for ${name} tested ${helpers.plural(
|
|
nTestedPages,
|
|
'page'
|
|
)} at ${timestamp}`,
|
|
pageDescription:
|
|
'Executive summary of the sitespeed.io result. Act on red/yellow/green.',
|
|
boxes: chunk(summaryBoxes.filter(Boolean), 3)
|
|
};
|
|
|
|
this.summary.detailed = {
|
|
pageTitle: `In details summary for ${name} tested ${helpers.plural(
|
|
nTestedPages,
|
|
'page'
|
|
)} at ${timestamp}`,
|
|
pageDescription:
|
|
'Get all the details you need to fast track things you need to change.',
|
|
metrics: detailedBoxes
|
|
};
|
|
|
|
this.summary.domains = {
|
|
pageTitle: `The most used domains for ${name} tested at ${timestamp}`,
|
|
pageDescription:
|
|
'A list of the most used domains and the respective timings'
|
|
};
|
|
|
|
this.summary.assets = {
|
|
pageTitle: `Most used assets for ${name} tested at ${timestamp}`,
|
|
pageDescription: 'A list of the most used assets for the analyzed pages.'
|
|
};
|
|
|
|
this.summary.toplist = {
|
|
pageTitle: `Largest assets by type for ${name} tested at ${timestamp}`,
|
|
pageDescription: 'A list of the largest assets for the analyzed pages.'
|
|
};
|
|
|
|
if (options.budget) {
|
|
let totalFailing = 0;
|
|
let totalWorking = 0;
|
|
for (const url of Object.keys(this.budget.failing)) {
|
|
totalFailing = totalFailing + this.budget.failing[url].length;
|
|
}
|
|
for (const url of Object.keys(this.budget.working)) {
|
|
totalWorking = totalWorking + this.budget.working[url].length;
|
|
}
|
|
this.summary.budget = {
|
|
pageTitle: `Performance budget for ${name} with ${totalWorking} working and ${totalFailing} failing budgets.`,
|
|
pageDescription: 'The list of failing and working performance budgets.',
|
|
budget: this.budget,
|
|
totalFailing,
|
|
totalWorking
|
|
};
|
|
}
|
|
|
|
// TODO check that the coach is available
|
|
const aPage = validPages[Object.keys(validPages)[0]];
|
|
const coachData = get(aPage, 'data.coach.pageSummary.advice');
|
|
this.summary.help = {
|
|
pageTitle: 'Definitions and help in for all the metrics',
|
|
pageDescription: '',
|
|
coach: coachData
|
|
};
|
|
|
|
const summaryRenders = Object.keys(this.summary).map(name =>
|
|
this._renderSummaryPage(
|
|
name,
|
|
merge(
|
|
{
|
|
options,
|
|
noPages: dataCollector.getURLs().length,
|
|
css,
|
|
h: helpers,
|
|
rootPath: '',
|
|
menu: name,
|
|
pageTitle: name,
|
|
pageDescription: '',
|
|
headers: this.summary,
|
|
version: packageInfo.version,
|
|
timestamp: this.timestamp,
|
|
context: this.context
|
|
},
|
|
dataCollector.getSummary(name),
|
|
this.summary[name]
|
|
)
|
|
)
|
|
);
|
|
|
|
const urlPageRenders = [];
|
|
for (let url of Object.keys(validPages)) {
|
|
const pageInfo = validPages[url];
|
|
const runPages = dataCollector.getURLRuns(url);
|
|
const medianRun = metricHelper.pickMedianRun(runPages, pageInfo);
|
|
let summaryPageHAR = get(pageInfo, 'data.browsertime.har');
|
|
// if we don't use Browsertime, we don't get the browser version
|
|
const browser = summaryPageHAR
|
|
? {
|
|
name: summaryPageHAR.log.browser.name,
|
|
version: summaryPageHAR.log.browser.version
|
|
}
|
|
: {
|
|
name: '',
|
|
version: ''
|
|
};
|
|
// if we are on the summary page we inline the HAR and then make sure
|
|
// we only pick one HAR run (medianRun). But you can also choose to
|
|
// fetch the HAR in the HTML, then it isn't included.
|
|
if (!(isEmpty(runPages) || options.html.showAllWaterfallSummary)) {
|
|
// only if we have some browsertime metrics, take the HAR and pass it to the summary
|
|
const har = get(runPages[medianRun], 'data.browsertime.run.har');
|
|
if (har) {
|
|
summaryPageHAR = har;
|
|
}
|
|
}
|
|
|
|
let daurlAlias;
|
|
if (
|
|
options.urlsMetaData &&
|
|
options.urlsMetaData[url] &&
|
|
options.urlsMetaData[url].alias
|
|
) {
|
|
daurlAlias = options.urlsMetaData[url].alias;
|
|
}
|
|
|
|
const summaryTimestamp = get(
|
|
pageInfo,
|
|
'data.browsertime.pageSummary.timestamp',
|
|
this.timestamp
|
|
);
|
|
// Add pugs for extra plugins
|
|
const pugs = {};
|
|
const pageSummaries = this.pageSummaries.filter(
|
|
summary => !!get(pageInfo.data, [summary.id, 'pageSummary'])
|
|
);
|
|
|
|
let data = {
|
|
daurl: url,
|
|
daurlAlias,
|
|
pageInfo,
|
|
options,
|
|
runPages,
|
|
summaryPageHAR,
|
|
medianRun,
|
|
browser,
|
|
hasScreenShots: dataCollector.browsertimeScreenshots,
|
|
screenShotType: dataCollector.browsertimeScreenshotsType,
|
|
css,
|
|
h: helpers,
|
|
JSON: JSON,
|
|
markdown: markdown,
|
|
rootPath: this.storageManager.rootPathFromUrl(url),
|
|
menu: 'pages',
|
|
pageTitle: `Summary for ${helpers.plural(
|
|
this.options.browsertime.iterations,
|
|
'run'
|
|
)} ${url} at ${summaryTimestamp}`,
|
|
pageDescription: `${metricHelper.getMetricsFromPageSummary(
|
|
pageInfo
|
|
)} collected by sitespeed.io ${packageInfo.version}`,
|
|
headers: this.summary,
|
|
version: packageInfo.version,
|
|
timestamp: summaryTimestamp,
|
|
context: this.context,
|
|
pageSummaries
|
|
};
|
|
|
|
for (const summary of pageSummaries) {
|
|
pugs[summary.id] = renderer.renderTemplate(summary.id, data);
|
|
}
|
|
data.pugs = pugs;
|
|
|
|
await this._renderUrlPage(url, 'index', data);
|
|
for (let runIndex of Object.keys(runPages)) {
|
|
const iteration = Number(runIndex) + 1;
|
|
const pugs = {};
|
|
const pageInfo = runPages[runIndex];
|
|
const runTimestamp = get(
|
|
pageInfo,
|
|
'data.browsertime.run.timestamp',
|
|
this.timestamp
|
|
);
|
|
|
|
const pageRuns = this.pageRuns.filter(
|
|
run => !!get(pageInfo.data, [run.id, 'run'])
|
|
);
|
|
|
|
let data = {
|
|
daurl: url,
|
|
daurlAlias,
|
|
iteration,
|
|
runIndex,
|
|
pageInfo,
|
|
options,
|
|
runPages,
|
|
browser,
|
|
hasScreenShots: dataCollector.browsertimeScreenshots,
|
|
screenShotType: dataCollector.browsertimeScreenshotsType,
|
|
css,
|
|
h: helpers,
|
|
urlLink: './index.html',
|
|
JSON: JSON,
|
|
markdown: markdown,
|
|
rootPath: this.storageManager.rootPathFromUrl(url),
|
|
menu: 'pages',
|
|
pageTitle: `Run ${parseInt(runIndex) +
|
|
1} for ${url} at ${runTimestamp}`,
|
|
pageDescription: `${metricHelper.getMetricsFromRun(
|
|
pageInfo
|
|
)} collected by sitespeed.io ${packageInfo.version}`,
|
|
headers: this.summary,
|
|
version: packageInfo.version,
|
|
timestamp: runTimestamp,
|
|
context: this.context,
|
|
pageRuns
|
|
};
|
|
// Add pugs for extra plugins
|
|
for (const run of pageRuns) {
|
|
pugs[run.id] = renderer.renderTemplate(run.id, data);
|
|
}
|
|
data.pugs = pugs;
|
|
urlPageRenders.push(
|
|
this._renderUrlRunPage(url, parseInt(runIndex) + 1, data)
|
|
);
|
|
}
|
|
}
|
|
// Aggregate/summarize data and write additional files
|
|
return this.storageManager
|
|
.copyToResultDir(path.join(__dirname, 'assets'))
|
|
.then(() =>
|
|
Promise.all(summaryRenders)
|
|
.then(() => Promise.all(urlPageRenders))
|
|
.then(() =>
|
|
log.info('HTML stored in %s', this.storageManager.getBaseDir())
|
|
)
|
|
);
|
|
}
|
|
|
|
async _renderUrlPage(url, name, locals) {
|
|
log.debug('Render URL page %s', name);
|
|
|
|
return this.storageManager.writeHtmlForUrl(
|
|
renderer.renderTemplate('url/summary/' + name, locals),
|
|
name + '.html',
|
|
url
|
|
);
|
|
}
|
|
|
|
async _renderUrlRunPage(url, name, locals) {
|
|
log.debug('Render URL run page %s', name);
|
|
return this.storageManager.writeHtmlForUrl(
|
|
renderer.renderTemplate('url/iteration/index', locals),
|
|
name + '.html',
|
|
url
|
|
);
|
|
}
|
|
|
|
async _renderSummaryPage(name, locals) {
|
|
log.debug('Render summary page %s', name);
|
|
|
|
return this.storageManager.writeHtml(
|
|
renderer.renderTemplate(name, locals),
|
|
name + '.html'
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = HTMLBuilder;
|