Documentation / Plugins
Plugins
Sitespeed.io uses a plugin architecture. This architecture allows you to add/remove/create your own plugins that can run additional tests on URLs or do other things with the result, like store it in a database.
The most basic things you can do are list configured plugins (which are currently used), disable one of the default plugins, or add/enable more plugins.
List configured plugins
You can list the plugins that will be used when you do a run:
docker run --rm -v "$(pwd):/sitespeed.io" sitespeedio/sitespeed.io:41.0.1 --plugins.list https://en.wikipedia.org/wiki/Barack_Obama And you will get a log entry that looks something like this:
...
The following plugins are enabled: assets,browsertime,coach,domains,html
...
The default plugins live in the plugin folder. This is a good starting place to look at if you want to build your own plugin.
Disable a plugin
You can remove/disable default plugins if needed. For instance, you may not want to output HTML and only send the data to Graphite.
docker run --rm -v "$(pwd):/sitespeed.io" sitespeedio/sitespeed.io:41.0.1 https://www.sitespeed.io --plugins.remove html If you want to disable multiple plugins, say you don't need the HTML and the HAR files (the harstorer plugin):
docker run --rm -v "$(pwd):/sitespeed.io" sitespeedio/sitespeed.io:41.0.1 https://www.sitespeed.io --plugins.remove html --plugins.remove harstorer At any time, if you want to verify that disabling worked, add the plugins.list to your command:
docker run --rm -v "$(pwd):/sitespeed.io" sitespeedio/sitespeed.io:41.0.1 https://www.sitespeed.io --plugins.remove html --plugins.remove harstorer --plugins.list Add a plugin
You can also add a plugin. This is great if you have plugins you created yourself, plugins others have created, or plugins that are not enabled by default.
There's a plugin bundled with sitespeed.io called the analysisstorer plugin that isn't enabled by default. It stores the original JSON data from all analyzers (from Browsertime, Coach data, WebPageTest etc.) to disk. You can enable this plugin:
docker run --rm -v "$(pwd):/sitespeed.io" sitespeedio/sitespeed.io:41.0.1 https://www.sitespeed.io --plugins.add analysisstorer If you want to run plugins that you created yourself or that are shared from others, you can either install the plugin using npm (locally or globally) and load it by name or point out the directory where the plugin lives.
Mount your plugin in Docker
If you run in Docker (and you should), you will need to mount your plugin directory as a volume. This is the recommended best practice. In practice you should clone your repo on your server and then mount it like this.
docker run --rm -v "$(pwd):/sitespeed.io" sitespeedio/sitespeed.io:41.0.1 -b firefox --plugins.add /sitespeed.io/myplugin -n 1 https://www.sitespeed.io/ Here's a full example of how you could do it with the WebPageTest plugin:
cd
mkdir test
cd test
git clone git@github.com:sitespeedio/plugin-webpagetest.git
cd plugin-webpagetest
npm install
pwd
/Users/peter/test/plugin-webpagetest
docker run --rm -v /Users/peter/:/sitespeed.io sitespeedio/sitespeed.io --plugins.add /sitespeed.io/test/plugin-webpagetest/index.js https://www.sitespeed.io Relative using Node.js
If you have your plugin installed on your computer, you can load it by a relative path.
sitespeed.io https://www.sitespeed.io --plugins.add ../my/super/plugin Globally using Node.js
If you installed your plugin globally, you can find it by its name. In this example we first install the Lighthouse plugin:
npm install @sitespeed.io/plugin-lighthouse -g And then run it:
sitespeed.io https://www.sitespeed.io --plugins.add @sitespeed.io/plugin-lighthouse Pre-baked Docker file
If you want to create an image of sitespeed.io with your plugins pre-baked for sharing you can also do so using the following Dockerfile.
FROM sitespeedio/sitespeed.io:<insert version here>
ENV SITESPEED_IO_PLUGINS__ADD /my-custom-plugin
# You need to have git to clone your repo
RUN sudo apt-get update && sudo apt-get install git -y
WORKDIR /my-custom-plugin
RUN git clone https://path.to/my-custom-plugin .
RUN npm install --production
VOLUME /sitespeed.io
WORKDIR /sitespeed.io Then build the docker image
docker build -t my-custom-sitespeedio . Finally you can run it the same way as mentioned above without the volume mount and without adding your plugin (that was automatically fixed in your Docker file).
docker run --rm -v "$(pwd):/sitespeed.io" my-custom-sitespeedio -b firefox --my-custom-plugin.option test -n 1 https://www.sitespeed.io/ How to create your own plugin
Plugins in sitespeed.io should inherit the sitespeed.io plugin. This was added in sitespeed.io 27.0.0. Implement that base class and you will get some functionality for free.
In your dependencies for your plugin, make sure to add the latest version of the plugin.
"@sitespeed.io/plugin": "1.0.0" Basic structure
Your plugin needs to follow this structure.
import { SitespeedioPlugin } from '@sitespeed.io/plugin';
export default class MyPlugin extends SitespeedioPlugin {
constructor(options, context, queue) {
super({ name: 'MyPlugin', options, context, queue });
// You need to call the constructor of the base plugin to make sure
// you can get hold of all the configuration.
}
open() {
// when sitespeed.io starts it calls the open function once for all plugins
// here you can prepare the things you need.
// If you want to log a message to the log you can log on info level
super.log('My plugin is starting');
// Or you can choose log level, here we debug log
super.log('This is debug info', 'debug');
}
async processMessage(message) {
// The plugin will get all messages sent through the queue
// and can act on specific messages by type:
// message.type
super.log(`Got a message of type ${message.type}`);
// If you need the startup options that was passed to sitespeed.io
// you can get those from the base class.
const options = super.getOptions();
// if your plugin needs to store data to disk, you can do that with the
// storage manager
const storageManager = super.getStorageManager();
}
close() {
// When all messages are done, the close function is called once.
// Options are the configuration options, and errors is an array of errors
// from the run.
super.log('Closing down my plugin');
}
}; open()
The open function is called once when sitespeed.io starts. It's in this function you can initialise whatever you need within your plugin. You will get the context and the options.
The context holds information for this specific run that is generated at runtime and looks like this:
{
storageManager, // The storage manager is what you use to store data to disk
resultUrls,
timestamp, // The timestamp of when you started the run
budget, // If you run with budget, the result will be here
name, // The name of the run (the start URL )
intel, // The log system that is used within sitespeed.io https://github.com/seanmonstar/intel
messageMaker, // Help methods to send messages in the queue,
statsHelpers, // Help methods to collect data per domain/tests instead of per URL
filterRegistry // Register metrics that will be sent to Graphite/InfluxDB
} You can check out the StorageManager, messageMaker, statsHelpers and filterRegistry to get a feel for how you can use them.
The options are the options that a user will supply in the CLI, check out the CLI implementation to see all the options.
processMessage(message, queue)
The processMessage function in your plugin is called for each and every message that is passed in the application. So what's a message, you may ask? Everything is a message in sitespeed.io :) A message contains the following information:
- uuid a unique id
- type the type of a message, so your plugin knows whether it is interested in this message type.
- timestamp when the message was created
- source who created the message
- data the data that is sent in the message
- extras whatever extra you want to include in a message
When you start the application and feed it with URLs, each URL will generate a message of type URL and will be sent to all configured plugins.
If you want to catch it, you can do something like this:
switch (message.type) {
case 'url':
{
// do some analyse on the URL
} When you are finished analysing the URL, your plugin can then send a message with the result, so other plugins can use it.
If you want to send messages from within your plugin, you get it from the context.
const messageMaker = context.messageMaker; close(options, errors)
When all URLs have been analysed, the close function is called once for each plugin. The idea with the close function is to close down assets that your plugin created. Normally the close function is nothing you need to implement.
Important messages
There are a couple of predefined messages that are always passed around in the queue.
- sitespeedio.setup - the first message that will be passed to all plugins. When you get this message you can pass information to other plugins. For example, if you send pug files to the HTML plugin or JavaScript to Browsertime.
- sitespeedio.summarize - all URLs are analysed and the plugins need to summarise the metrics.
- sitespeedio.render - it is time to render (=write data to disk).
Plugins also pass messages to each other. The HTML plugin sends an html.finished message when the HTML is written to disk. The S3 plugin listens for that message, and when it gets it, it uploads the files and sends an s3.finished message. The Slack plugin then listens for s3.finished messages and sends a Slack message.
Data from different tools is passed with three different message types:
- *.run - metrics collected for one run/iteration
- *.pageSummary - a summary of all the metrics collected for all iterations for one page. Here you will get min/median/max values for all runs for that page.
- *.summary - metrics collected for all pages tested.
Debug/log
You can use the sitespeed.io log to log messages. We use intel for logging.
You get the log object in the context object (so there's no need to require the log), but you should get a specific instance so that you can filter the log/see which part of sitespeed.io is writing to the log.
In the open function you can add something like this:
// Register a logger for this plugin, a unique name so we can filter the log
// And save the log for later
this.log = context.getLogger('sitespeedio.plugin.PLUGIN_NAME');
this.log.info('Plugin PLUGIN_NAME started'); Create HTML for your plugin
Since 6.0 your plugin can generate HTML. You can either generate HTML per run or per page. The first thing you need to do is to report your PUG file to the HTML plugin. You do that by sending a message to the HTML plugin.
Send your pug file to the HTML plugin
You start by listening to the generic setup message sitespeedio.setup. When you get that, you should send your pug file with a message called html.pug. That message needs to have four fields:
- id = the id of the plugin, need to be unique.
- name = the friendly name displayed in the tab showing the data.
- pug = the pug file as a String.
- type = can be run or pageSummary. run is data you collect on every run and it will be a tab on each run page. pageSummary is data for a specific page and will generate a tab on the page summary page. In most cases you will only need pageSummary data, but if you have a tool that does multiple runs, you should send the data per run (as Browsertime and WebPageTest do).
Sending a pug looks something like this:
case 'sitespeedio.setup': {
queue.postMessage(
make('html.pug', {
id: 'gpsi',
name: 'GPSI',
pug: this.pug,
type: 'pageSummary'
})
);
break;
} The HTML plugin will then listen to metrics sent with the type of the id and type, in this case gpsi.pageSummary.
Send the data
The HTML plugin will automatically pick up data sent with the types of *.run and *.pageSummary. All these need to have the URL so that the data can be mapped to the right place.
A message can look like this (the HTML plugin will pickup messages sent by combining the id + type):
queue.postMessage(
make('gpsi.pageSummary', result, {
url,
group
})
); You can look at the standalone GPSI plugin or the WebPageTest plugin as an example plugin that both sends run and pageSummary data.
Let your plugin collect metrics using Browsertime
One new feature in 6.0 is that your plugin can tell Browsertime to run synchronous JavaScript on the page you test to collect metrics.
In the setup phase, send the JavaScript you want to run to sitespeed.io:
case 'sitespeedio.setup': {
queue.postMessage(
make('browsertime.scripts', {
category: 'yourplugin',
scripts: {
userAgent: '(function() {return navigator.userAgent;})();',
title: '(function() {return document.title;})();'
}
})
)
break;
} You can also let Browsertime run asynchronous scripts, follow the same pattern and change the key to browsertime.asyncscripts.
You can then get the metrics back by listening on browsertime.run messages.
case 'browsertime.run': {
console.log(message.data.yourplugin);
break;
} And if you want to use it in your pug template you will find it under pageInfo.data.browsertime.run.yourplugin. In this example, if you want to print the title you can do like this.
#{pageInfo.data.browsertime.run.yourplugin.title} Let your plugin add metrics to the performance budget
In the sitespeedio.config phase (where plugins can talk to each other) make sure to tell the budget plugin that you want it to collect metrics from your plugin. Do that by sending a message of the type budget.addMessageType and add the type of the metrics message you want it to collect.
In this example we tell the budget plugin that it should collect metrics of the type gpsi.pagesummary.
const messageMaker = context.messageMaker;
...
queue.postMessage(make('budget.addMessageType', {type: 'gpsi.pagesummary'})); Testing your plugin
If your plugin lives on GitHub you can model your CI on the GPSI plugin's GitHub Actions — it checks out sitespeed.io and runs the plugin against the latest main on a schedule.
Example plugin(s)
You can look at the standalone GPSI plugin or the WebPageTest plugin.
Find plugins
We keep a list of plugins at https://github.com/sitespeedio/plugins. If you want to add your plugin, send a PR!
What's missing
There's no way for a plugin to tell the CLI what type of configuration/options are needed, but there's an issue for that. Help us out if you have ideas!