Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,91 @@ jobs:
run: npm run lint
- name: Run unit tests
run: npm test

# Canary: install this branch into sitespeed.io@main and run its lint + unit
# tests. Catches base-class signature/import breakage across the ~25 built-in
# plugins. Browser-driver downloads are skipped so the job stays cheap (~2 min).
compat-sitespeed-io:
runs-on: ubuntu-22.04
steps:
- name: Check out this plugin
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
path: plugin
- name: Use Node.js 22
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22.x'
- name: Pack this plugin
working-directory: plugin
run: |
npm ci
npm pack
echo "PLUGIN_TARBALL=$GITHUB_WORKSPACE/plugin/$(ls *.tgz)" >> "$GITHUB_ENV"
- name: Check out sitespeed.io@main
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
repository: sitespeedio/sitespeed.io
ref: main
path: sitespeed.io
- name: Install sitespeed.io
working-directory: sitespeed.io
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
GECKODRIVER_SKIP_DOWNLOAD: true
run: npm ci
- name: Override @sitespeed.io/plugin with this branch
working-directory: sitespeed.io
env:
CHROMEDRIVER_SKIP_DOWNLOAD: true
GECKODRIVER_SKIP_DOWNLOAD: true
run: npm install --no-save "$PLUGIN_TARBALL"
- name: Lint sitespeed.io
working-directory: sitespeed.io
run: npm run lint
- name: Run sitespeed.io unit tests
working-directory: sitespeed.io
run: npm test

# Smoke: actually run one sitespeed.io invocation against a local URL with
# this branch's plugin swapped in. Exercises the full plugin lifecycle
# (constructor + open + processMessage for setup/summarize/prepareToRender/
# render + close) against the real framework, with a real browser.
smoke-sitespeed-io:
runs-on: ubuntu-22.04
steps:
- name: Check out this plugin
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
path: plugin
- name: Use Node.js 22
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22.x'
- name: Pack this plugin
working-directory: plugin
run: |
npm ci
npm pack
echo "PLUGIN_TARBALL=$GITHUB_WORKSPACE/plugin/$(ls *.tgz)" >> "$GITHUB_ENV"
- name: Check out sitespeed.io@main
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
repository: sitespeedio/sitespeed.io
ref: main
path: sitespeed.io
- name: Install scipy (used by sitespeed.io stats)
run: |
python -m pip install --upgrade --user pip
python -m pip install --user scipy
- name: Install sitespeed.io (with browser drivers)
working-directory: sitespeed.io
run: npm ci
- name: Override @sitespeed.io/plugin with this branch
working-directory: sitespeed.io
run: npm install --no-save "$PLUGIN_TARBALL"
- name: Run a single sitespeed.io test with this plugin
working-directory: sitespeed.io
run: bin/sitespeed.js -n 1 -b chrome --xvfb --outputFolder /tmp/sitespeed-result https://www.sitespeed.io/
- name: Verify HTML report was produced
run: test -f /tmp/sitespeed-result/index.html
39 changes: 34 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,59 @@ npm install @sitespeed.io/plugin

## Usage

sitespeed.io instantiates your plugin with the **positional** arguments
`new MyPlugin(options, context, queue)`. Your constructor must accept them in
that order and forward them to `super` as a config object. The other lifecycle
methods are called with their own positional arguments (shown below).

```js
import { SitespeedioPlugin } from '@sitespeed.io/plugin';

export default class MyPlugin extends SitespeedioPlugin {
// Optional: limit how many messages this plugin processes in parallel.
// concurrency = 1;

constructor(options, context, queue) {
super({ name: 'myplugin', options, context, queue });
}

async open() {
// Called once on startup. (context, options) are the same as the constructor's.
async open(context, options) {
// optional: setup on startup
}

async processMessage(message) {
// Called for every message on the queue. `queue` is the same as `this.queue`.
async processMessage(message, queue) {
if (message.type === 'url') {
this.log.info('Got a URL: %s', message.url);
await this.sendMessage('myplugin.data', { hello: 'world' });
}
}

async close() {
// Called once on shutdown.
async close(options, errors) {
// optional: cleanup on shutdown
}
}
```

## Lifecycle messages

While a run is in flight, sitespeed.io posts a few framework-level messages on
the queue. Handle them in `processMessage` to hook into the run:

| `message.type` | When |
| ----------------------------- | ---------------------------------------------------- |
| `sitespeedio.setup` | Plugins announce themselves / register filters |
| `sitespeedio.summarize` | All analysis is done — time to summarize |
| `sitespeedio.prepareToRender` | About to render output |
| `sitespeedio.render` | Write final output to storage |

Other plugins emit their own message types (for example `browsertime.pageSummary`,
`browsertime.har`, `pagexray.run`, …). See the
[plugin documentation](https://www.sitespeed.io/documentation/sitespeed.io/plugins/#how-to-create-your-own-plugin)
for the full list.

## API

- `this.name` / `getName()` — plugin name
Expand All @@ -50,8 +78,9 @@ export default class MyPlugin extends SitespeedioPlugin {
- `getStorageManager()` — storage manager for writing files
- `getFilterRegistry()` — filter registry for TimeSeries metrics
- `sendMessage(type, data, extras)` — post a message on the queue
- `open()` / `close()` — lifecycle hooks (override as needed)
- `processMessage(message)` — **must be implemented** by your subclass
- `concurrency` — optional class field; limits parallel `processMessage` calls
- `open(context, options)` / `close(options, errors)` — lifecycle hooks (override as needed)
- `processMessage(message, queue)` — **must be implemented** by your subclass

## License

Expand Down
37 changes: 32 additions & 5 deletions plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
* https://www.sitespeed.io/documentation/sitespeed.io/plugins/#how-to-create-your-own-plugin
*/
export class SitespeedioPlugin {
/**
* Optional. Set as a class field on your subclass (e.g. `concurrency = 1`)
* to limit how many messages this plugin processes in parallel. Read by
* sitespeed.io's queue handler; when unset the plugin is unlimited.
* @type {number|undefined}
*/
// concurrency;

constructor(config) {
if (this.constructor === SitespeedioPlugin) {
throw new Error("Abstract plugin can't be instantiated.");
Expand Down Expand Up @@ -79,24 +87,43 @@ export class SitespeedioPlugin {

/**
* Called when sitespeed.io starts up. Override this method to perform any setup tasks.
* sitespeed.io invokes it with `(context, options)` — the same objects passed to
* the constructor. They are passed again for backwards compatibility; you can
* also read them via `this.context` / `this.options`.
* @param {Object} [context] - sitespeed.io context (same as constructor `context`).
* @param {Object} [options] - sitespeed.io options (same as constructor `options`).
*/
async open() {}
// eslint-disable-next-line no-unused-vars
async open(context, options) {}

/**
* Sitespeed.io and plugins talk to each other using the messages in the
* message queue.
* message queue. Override this method to react to messages. sitespeed.io
* invokes it with `(message, queue)`; `queue` is the same queue handler
* available via `this.queue`.
*
* Common lifecycle message types you may want to handle:
* - 'sitespeedio.setup' — plugins announce themselves / register filters
* - 'sitespeedio.summarize' — all analysis done, time to summarize
* - 'sitespeedio.prepareToRender' — about to render output
* - 'sitespeedio.render' — write final output to storage
*
* @param {*} message
* @param {Object} message - Message from the queue (has `type`, optional `data`, `url`, `runIndex`, …).
* @param {Object} [queue] - The queue handler (same as `this.queue`).
*/
// eslint-disable-next-line no-unused-vars
async processMessage(message) {
async processMessage(message, queue) {
throw new Error("Method 'processMessage()' must be implemented.");
}

/**
* Called when sitespeed.io shuts down. Override this method to perform any cleanup tasks.
* sitespeed.io invokes it with `(options, errors)`.
* @param {Object} [options] - sitespeed.io options.
* @param {Array} [errors] - Errors collected during the run.
*/
async close() {}
// eslint-disable-next-line no-unused-vars
async close(options, errors) {}

/**
* Sends a message on the message queue.
Expand Down
Loading