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
15 changes: 10 additions & 5 deletions code-pushup.preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import {
} from './packages/plugin-jsdocs/src/lib/constants.js';
import { filterGroupsByOnlyAudits } from './packages/plugin-jsdocs/src/lib/utils.js';
import lighthousePlugin, {
type LighthouseUrls,
lighthouseGroupRef,
mergeLighthouseCategories,
} from './packages/plugin-lighthouse/src/index.js';
import typescriptPlugin, {
type TypescriptPluginOptions,
Expand Down Expand Up @@ -135,11 +137,14 @@ export const jsPackagesCoreConfig = async (): Promise<CoreConfig> => ({
});

export const lighthouseCoreConfig = async (
url: string,
): Promise<CoreConfig> => ({
plugins: [await lighthousePlugin(url)],
categories: lighthouseCategories,
});
urls: LighthouseUrls,
): Promise<CoreConfig> => {
const lhPlugin = await lighthousePlugin(urls);
return {
plugins: [lhPlugin],
categories: mergeLighthouseCategories(lhPlugin, lighthouseCategories),
};
};

export const jsDocsCoreConfig = (
config: JsDocsPluginConfig | string[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ exports[`PLUGIN collect report with lighthouse-plugin NPM package > should run p
"title": "Document has a valid \`hreflang\`",
},
],
"context": {
"urlCount": 1,
"weights": {
"1": 1,
},
},
"groups": [
{
"refs": [
Expand Down
11 changes: 7 additions & 4 deletions packages/cli/src/lib/implementation/filter.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,13 @@ function applyPluginFilters(
options: Pick<FilterOptions, 'skipPlugins' | 'onlyPlugins'>,
): CoreConfig['plugins'] {
const { skipPlugins = [], onlyPlugins = [] } = options;
const filteredPlugins = filterPluginsFromCategories({
categories,
plugins,
});
const filteredPlugins =
onlyPlugins.length === 0
? filterPluginsFromCategories({
categories,
plugins,
})
: plugins;
if (skipPlugins.length === 0 && onlyPlugins.length === 0) {
return filteredPlugins;
}
Expand Down
38 changes: 38 additions & 0 deletions packages/cli/src/lib/implementation/filter.middleware.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,44 @@ describe('filterMiddleware', () => {
),
);
});

it('should allow onlyPlugins to include plugins not referenced by categories', () => {
const { plugins } = filterMiddleware({
plugins: [
{
slug: 'p1',
audits: [{ slug: 'a1-p1', isSkipped: false }],
groups: [
{
slug: 'g1-p1',
refs: [{ slug: 'a1-p1', weight: 1 }],
isSkipped: false,
},
],
},
{
slug: 'p2',
audits: [{ slug: 'a1-p2', isSkipped: false }],
groups: [
{
slug: 'g1-p2',
refs: [{ slug: 'a1-p2', weight: 1 }],
isSkipped: false,
},
],
},
] as PluginConfig[],
categories: [
{
slug: 'c1',
refs: [{ type: 'group', plugin: 'p1', slug: 'g1-p1', weight: 1 }],
},
] as CategoryConfig[],
onlyPlugins: ['p2'],
});

expect(plugins.map(plugin => plugin.slug)).toStrictEqual(['p2']);
});
});

describe('filterSkippedInPlugins', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ export {
} from './lib/persist-config.js';
export {
pluginConfigSchema,
pluginContextSchema,
pluginMetaSchema,
type PluginConfig,
type PluginContext,
type PluginMeta,
} from './lib/plugin-config.js';
export {
Expand Down
7 changes: 7 additions & 0 deletions packages/models/src/lib/plugin-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import {
import { errorItems, hasMissingStrings } from './implementation/utils.js';
import { runnerConfigSchema, runnerFunctionSchema } from './runner-config.js';

export const pluginContextSchema = z
.record(z.unknown())
.optional()
.describe('Plugin-specific context data for helpers');
export type PluginContext = z.infer<typeof pluginContextSchema>;

export const pluginMetaSchema = packageVersionSchema()
.merge(
metaSchema({
Expand All @@ -31,6 +37,7 @@ export const pluginDataSchema = z.object({
runner: z.union([runnerConfigSchema, runnerFunctionSchema]),
audits: pluginAuditsSchema,
groups: groupsSchema,
context: pluginContextSchema,
});
type PluginData = z.infer<typeof pluginDataSchema>;

Expand Down
94 changes: 94 additions & 0 deletions packages/plugin-lighthouse/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,100 @@ export default {
};
```

## Multiple URLs

The Lighthouse plugin supports running audits against multiple URLs in a single invocation. To do this, provide an array of URLs as the first argument to the plugin:

```ts
import lighthousePlugin from '@code-pushup/lighthouse-plugin';

export default {
// ...
plugins: [
// ...
await lighthousePlugin(['https://example.com', 'https://example.com/contact']),
],
};
```

### Assigning weights to URLs

You can assign custom weights to URLs by passing an object instead of an array. This is useful when some pages are more important than others (e.g., your homepage vs. a contact page). The keys are URLs, and the values are their weights.

URLs with higher weights contribute more to the overall category scores. For example, a URL with weight 2 has twice the influence of a URL with weight 1.

```ts
import lighthousePlugin from '@code-pushup/lighthouse-plugin';

export default {
// ...
plugins: [
// ...
await lighthousePlugin({
'https://example.com': 2,
'https://example.com/contact': 1,
})
];
};
```

### Categories with multiple URLs

When running Lighthouse against multiple URLs, use the `mergeLighthouseCategories` utility to ensure categories are correctly expanded and results are aggregated per URL.

#### Basic usage

```ts
import lighthousePlugin, { mergeLighthouseCategories } from '@code-pushup/lighthouse-plugin';

const lhPlugin = await lighthousePlugin(urls);

export default {
plugins: [
// ...
lhPlugin,
],
categories: [
// ...
...mergeLighthouseCategories(lhPlugin),
],
};
```

#### Custom categories

If you provide custom categories, you can reference both groups and audits as usual. The merging utility will expand each referenced group or audit for every URL, assigning the correct per-URL weight.

```ts
import lighthousePlugin, { lighthouseAuditRef, lighthouseGroupRef, mergeLighthouseCategories } from '@code-pushup/lighthouse-plugin';

const lhPlugin = await lighthousePlugin(urls);

export default {
// ...
plugins: [
// ...
lhPlugin,
],
categories: [
// ...
...mergeLighthouseCategories(lhPlugin, [
{
slug: 'performance',
title: 'Performance',
refs: [lighthouseGroupRef('performance'), lighthouseAuditRef('first-contentful-paint', 2)],
},
]),
],
};
```

### Behavior Summary

- **No categories**: The plugin auto-generates categories from the plugin's default Lighthouse groups.
- **Custom categories**: The plugin expands all referenced audits and groups for each URL, applying appropriate weights.
- **Empty array** (`categories: []`): No categories are created or expanded, which is useful when you only want audit data.

## Flags

The plugin accepts an optional second argument, `flags`.
Expand Down
13 changes: 7 additions & 6 deletions packages/plugin-lighthouse/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ export {
LIGHTHOUSE_PLUGIN_SLUG,
LIGHTHOUSE_OUTPUT_PATH,
} from './lib/constants.js';
export {
lighthouseAuditRef,
lighthouseGroupRef,
type LighthouseGroupSlugs,
} from './lib/utils.js';
export type { LighthouseOptions } from './lib/types.js';
export { lighthouseAuditRef, lighthouseGroupRef } from './lib/utils.js';
export type {
LighthouseGroupSlug,
LighthouseOptions,
LighthouseUrls,
} from './lib/types.js';
export { lighthousePlugin } from './lib/lighthouse-plugin.js';
export default lighthousePlugin;
export { mergeLighthouseCategories } from './lib/merge-categories.js';
10 changes: 10 additions & 0 deletions packages/plugin-lighthouse/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ export const LIGHTHOUSE_OUTPUT_PATH = path.join(
DEFAULT_PERSIST_OUTPUT_DIR,
LIGHTHOUSE_PLUGIN_SLUG,
);

export const LIGHTHOUSE_GROUP_SLUGS = [
'performance',
'accessibility',
'best-practices',
'seo',
'pwa',
] as const;

export const SINGLE_URL_THRESHOLD = 1;
25 changes: 12 additions & 13 deletions packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,24 @@ import { createRequire } from 'node:module';
import type { PluginConfig } from '@code-pushup/models';
import { LIGHTHOUSE_PLUGIN_SLUG } from './constants.js';
import { normalizeFlags } from './normalize-flags.js';
import {
LIGHTHOUSE_GROUPS,
LIGHTHOUSE_NAVIGATION_AUDITS,
} from './runner/constants.js';
import { normalizeUrlInput, processAuditsAndGroups } from './processing.js';
import { createRunnerFunction } from './runner/runner.js';
import type { LighthouseOptions } from './types.js';
import { markSkippedAuditsAndGroups } from './utils.js';
import type { LighthouseOptions, LighthouseUrls } from './types.js';

export function lighthousePlugin(
url: string,
urls: LighthouseUrls,
flags?: LighthouseOptions,
): PluginConfig {
const { skipAudits, onlyAudits, onlyCategories, ...unparsedFlags } =
normalizeFlags(flags ?? {});

const { audits, groups } = markSkippedAuditsAndGroups(
LIGHTHOUSE_NAVIGATION_AUDITS,
LIGHTHOUSE_GROUPS,
{ skipAudits, onlyAudits, onlyCategories },
);
const { urls: normalizedUrls, context } = normalizeUrlInput(urls);

const { audits, groups } = processAuditsAndGroups(normalizedUrls, {
skipAudits,
onlyAudits,
onlyCategories,
});

const packageJson = createRequire(import.meta.url)(
'../../package.json',
Expand All @@ -35,11 +33,12 @@ export function lighthousePlugin(
icon: 'lighthouse',
audits,
groups,
runner: createRunnerFunction(url, {
runner: createRunnerFunction(normalizedUrls, {
skipAudits,
onlyAudits,
onlyCategories,
...unparsedFlags,
}),
context,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,39 @@ describe('lighthousePlugin-config-object', () => {
]);
});

it('should create valid plugin config with multiple URLs', () => {
const pluginConfig = lighthousePlugin([
'https://code-pushup-portal.com',
'https://code-pushup-portal.com/about',
]);
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();

const { audits, groups } = pluginConfig;
expect(audits.length).toBeGreaterThan(100);
expect(groups).toStrictEqual([
expect.objectContaining({ slug: 'performance-1' }),
expect.objectContaining({ slug: 'accessibility-1' }),
expect.objectContaining({ slug: 'best-practices-1' }),
expect.objectContaining({ slug: 'seo-1' }),
expect.objectContaining({ slug: 'performance-2' }),
expect.objectContaining({ slug: 'accessibility-2' }),
expect.objectContaining({ slug: 'best-practices-2' }),
expect.objectContaining({ slug: 'seo-2' }),
]);
});

it('should generate context for multiple URLs', () => {
const pluginConfig = lighthousePlugin({
'https://code-pushup-portal.com': 2,
'https://code-pushup-portal.com/about': 1,
});

expect(pluginConfig.context).toStrictEqual({
urlCount: 2,
weights: { 1: 2, 2: 1 },
});
});

it.each([
[
{ onlyAudits: ['first-contentful-paint'] },
Expand Down
Loading
Loading