diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 99660378b..f8927b7ce 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -20,9 +20,9 @@ import { groups, } from './packages/plugin-jsdocs/src/lib/constants.js'; import { + lighthouseCategories, lighthouseGroupRef, lighthousePlugin, - mergeLighthouseCategories, } from './packages/plugin-lighthouse/src/index.js'; import typescriptPlugin, { getCategories, @@ -224,7 +224,7 @@ export async function configureLighthousePlugin( ]; return { plugins: [lhPlugin], - categories: mergeLighthouseCategories(lhPlugin, lhCategories), + categories: lighthouseCategories(lhPlugin, lhCategories), }; } diff --git a/packages/plugin-lighthouse/README.md b/packages/plugin-lighthouse/README.md index 04920a1a4..2e469db1c 100644 --- a/packages/plugin-lighthouse/README.md +++ b/packages/plugin-lighthouse/README.md @@ -51,66 +51,6 @@ For more infos visit the [official docs](https://developer.chrome.com/docs/light 4. Run the CLI with `npx code-pushup collect` and view or upload the report (refer to [CLI docs](../cli/README.md)). -### Optionally set up categories - -Reference audits (or groups) which you wish to include in custom categories (use `npx code-pushup print-config --onlyPlugins=lighthouse` to list audits and groups). - -Assign weights based on what influence each Lighthouse audit has on the overall category score (assign weight 0 to only include as extra info, without influencing category score). -The plugin exports the helper `lighthouseAuditRef` and `lighthouseGroupRef` to reference Lighthouse category references for audits and groups. - -#### Reference audits directly with `lighthouseGroupRef` - -```ts -import { lighthouseGroupRef } from './utils'; - -export default { - // ... - categories: [ - { - slug: 'performance', - title: 'Performance', - refs: [lighthouseGroupRef('performance')], - }, - { - slug: 'a11y', - title: 'Accessibility', - refs: [lighthouseGroupRef('accessibility')], - }, - { - slug: 'best-practices', - title: 'Best Practices', - refs: [lighthouseGroupRef('best-practices')], - }, - { - slug: 'seo', - title: 'SEO', - refs: [lighthouseGroupRef('seo')], - }, - ], -}; -``` - -#### Reference groups with `lighthouseAuditRef` - -The Lighthouse categories are reflected as groups. -Referencing individual audits offers more granularity. However, keep maintenance costs of a higher number of audits in mind as well. - -```ts -import { lighthouseAuditRef } from './utils'; - -export default { - // ... - categories: [ - { - slug: 'core-web-vitals', - title: 'Core Web Vitals', - scoreTarget: 0.9, - refs: [lighthouseAuditRef('largest-contentful-paint', 3), lighthouseAuditRef('first-input-delay', 2), lighthouseAuditRef('cumulative-layout-shift', 2), lighthouseAuditRef('first-contentful-paint', 1)], - }, - ], -}; -``` - ## 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: @@ -148,63 +88,6 @@ export default { }; ``` -### 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`. @@ -324,4 +207,78 @@ For a complete guide on Lighthouse configuration read the [official documentatio > }) > ``` +## Category integration + +The plugin provides helpers to integrate Lighthouse results into your categories. + +### Auto-generate categories + +Use `lighthouseCategories` to automatically create categories from all plugin groups: + +```ts +import lighthousePlugin, { lighthouseCategories } from '@code-pushup/lighthouse-plugin'; + +const lighthouse = await lighthousePlugin('https://example.com'); + +export default { + plugins: [lighthouse], + categories: lighthouseCategories(lighthouse), +}; +``` + +The helper creates categories for all four Lighthouse groups: `performance`, `accessibility`, `best-practices`, and `seo`. For multi-URL setups, refs are automatically expanded for each URL with appropriate weights. + +### Custom categories + +For fine-grained control, provide your own categories. You can reference groups (Lighthouse's native categories) or individual audits: + +```ts +import lighthousePlugin, { lighthouseAuditRef, lighthouseCategories, lighthouseGroupRef } from '@code-pushup/lighthouse-plugin'; + +const lighthouse = await lighthousePlugin(['https://example.com', 'https://example.com/about']); + +export default { + plugins: [lighthouse], + categories: lighthouseCategories(lighthouse, [ + { + slug: 'performance', + title: 'Performance', + refs: [lighthouseGroupRef('performance')], + }, + { + slug: 'core-web-vitals', + title: 'Core Web Vitals', + refs: [lighthouseAuditRef('largest-contentful-paint', 3), lighthouseAuditRef('cumulative-layout-shift', 2), lighthouseAuditRef('first-contentful-paint', 1)], + }, + ]), +}; +``` + +> [!NOTE] +> Referencing individual audits offers more granularity but increases maintenance costs. Use `npx code-pushup print-config --onlyPlugins=lighthouse` to list all available audits and groups. + +> [!TIP] +> Weights determine each ref's influence on the category score. Use weight `0` to include a ref as info only, without affecting the score. + +> [!TIP] +> You can use `lighthouseGroupRef` and `lighthouseAuditRef` directly in your categories without the helper. However, wrapping them in `lighthouseCategories` future-proofs your config for multi-URL setups. + +### Helper functions + +| Function | Description | +| ---------------------- | -------------------------------------------- | +| `lighthouseCategories` | Auto-generates or expands categories | +| `lighthouseGroupRef` | Creates a category ref to a Lighthouse group | +| `lighthouseAuditRef` | Creates a category ref to a Lighthouse audit | + +### Type safety + +The `LighthouseGroupSlug` type is exported for discovering valid group slugs: + +```ts +import type { LighthouseGroupSlug } from '@code-pushup/lighthouse-plugin'; + +const group: LighthouseGroupSlug = 'performance'; +``` + If you want to contribute, please refer to [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/packages/plugin-lighthouse/src/index.ts b/packages/plugin-lighthouse/src/index.ts index 0d1525a03..7803807db 100644 --- a/packages/plugin-lighthouse/src/index.ts +++ b/packages/plugin-lighthouse/src/index.ts @@ -10,4 +10,7 @@ export { lighthouseAuditRef, lighthouseGroupRef } from './lib/utils.js'; export type { LighthouseGroupSlug, LighthouseOptions } from './lib/types.js'; export { lighthousePlugin } from './lib/lighthouse-plugin.js'; export default lighthousePlugin; -export { mergeLighthouseCategories } from './lib/merge-categories.js'; +export { + lighthouseCategories, + mergeLighthouseCategories, +} from './lib/categories.js'; diff --git a/packages/plugin-lighthouse/src/lib/merge-categories.ts b/packages/plugin-lighthouse/src/lib/categories.ts similarity index 93% rename from packages/plugin-lighthouse/src/lib/merge-categories.ts rename to packages/plugin-lighthouse/src/lib/categories.ts index cf19eba30..fd4058014 100644 --- a/packages/plugin-lighthouse/src/lib/merge-categories.ts +++ b/packages/plugin-lighthouse/src/lib/categories.ts @@ -28,10 +28,10 @@ import { isLighthouseGroupSlug } from './utils.js'; * const lhPlugin = await lighthousePlugin(urls); * const lhCoreConfig = { * plugins: [lhPlugin], - * categories: mergeLighthouseCategories(lhPlugin), + * categories: lighthouseCategories(lhPlugin), * }; */ -export function mergeLighthouseCategories( +export function lighthouseCategories( plugin: Pick, categories?: CategoryConfig[], ): CategoryConfig[] { @@ -45,13 +45,16 @@ export function mergeLighthouseCategories( return expandCategories(categories, plugin.context); } +/** + * @deprecated + * Helper is renamed, please use `lighthouseCategories` function instead. + */ +export const mergeLighthouseCategories = lighthouseCategories; + function createCategories( groups: Group[], context: PluginUrlContext, ): CategoryConfig[] { - if (!shouldExpandForUrls(context.urlCount)) { - return []; - } return extractGroupSlugs(groups).map(slug => createAggregatedCategory(slug, context), ); diff --git a/packages/plugin-lighthouse/src/lib/merge-categories.unit.test.ts b/packages/plugin-lighthouse/src/lib/categories.unit.test.ts similarity index 93% rename from packages/plugin-lighthouse/src/lib/merge-categories.unit.test.ts rename to packages/plugin-lighthouse/src/lib/categories.unit.test.ts index 752363a65..2d05db31d 100644 --- a/packages/plugin-lighthouse/src/lib/merge-categories.unit.test.ts +++ b/packages/plugin-lighthouse/src/lib/categories.unit.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from 'vitest'; import type { CategoryConfig } from '@code-pushup/models'; -import { LIGHTHOUSE_PLUGIN_SLUG } from './constants.js'; import { createAggregatedCategory, expandAggregatedCategory, extractGroupSlugs, - mergeLighthouseCategories, -} from './merge-categories.js'; + lighthouseCategories, +} from './categories.js'; +import { LIGHTHOUSE_PLUGIN_SLUG } from './constants.js'; -describe('mergeLighthouseCategories', () => { +describe('lighthouseCategories', () => { const mockMultiUrlPlugin = { groups: [ { @@ -68,12 +68,12 @@ describe('mergeLighthouseCategories', () => { describe('with no groups', () => { it('should return empty array when no groups and no categories provided', () => { - expect(mergeLighthouseCategories({ groups: undefined })).toEqual([]); + expect(lighthouseCategories({ groups: undefined })).toEqual([]); }); it('should return provided categories when no groups provided', () => { expect( - mergeLighthouseCategories({ groups: undefined }, mockUserCategories), + lighthouseCategories({ groups: undefined }, mockUserCategories), ).toEqual(mockUserCategories); }); }); @@ -98,12 +98,21 @@ describe('mergeLighthouseCategories', () => { }, }; - it('should return empty array when no categories provided', () => { - expect(mergeLighthouseCategories(plugin)).toEqual([]); + it('should create categories from groups when no categories provided', () => { + expect(lighthouseCategories(plugin)).toEqual([ + expect.objectContaining({ + slug: 'performance', + refs: [expect.objectContaining({ slug: 'performance', weight: 1 })], + }), + expect.objectContaining({ + slug: 'accessibility', + refs: [expect.objectContaining({ slug: 'accessibility', weight: 1 })], + }), + ]); }); it('should return provided categories unchanged', () => { - expect(mergeLighthouseCategories(plugin, mockUserCategories)).toEqual( + expect(lighthouseCategories(plugin, mockUserCategories)).toEqual( mockUserCategories, ); }); @@ -111,7 +120,7 @@ describe('mergeLighthouseCategories', () => { describe('with multiple URLs', () => { it('should create default aggregated categories when no categories provided', () => { - const result = mergeLighthouseCategories(mockMultiUrlPlugin); + const result = lighthouseCategories(mockMultiUrlPlugin); expect(result).toHaveLength(2); expect(result[0]).toEqual({ @@ -154,7 +163,7 @@ describe('mergeLighthouseCategories', () => { }); it('should expand user-provided categories', () => { - const result = mergeLighthouseCategories( + const result = lighthouseCategories( mockMultiUrlPlugin, mockUserCategories, ); @@ -201,7 +210,7 @@ describe('mergeLighthouseCategories', () => { it('should handle mixed group and audit refs', () => { expect( - mergeLighthouseCategories(mockMultiUrlPlugin, [ + lighthouseCategories(mockMultiUrlPlugin, [ { slug: 'mixed-performance', title: 'Mixed Performance', @@ -251,7 +260,7 @@ describe('mergeLighthouseCategories', () => { it('should preserve non-Lighthouse refs unchanged', () => { expect( - mergeLighthouseCategories(mockMultiUrlPlugin, [ + lighthouseCategories(mockMultiUrlPlugin, [ { slug: 'mixed-category', title: 'Mixed Category', @@ -312,14 +321,14 @@ describe('mergeLighthouseCategories', () => { }, ]; - expect( - mergeLighthouseCategories(mockMultiUrlPlugin, categories)[0], - ).toEqual(categories[0]); + expect(lighthouseCategories(mockMultiUrlPlugin, categories)[0]).toEqual( + categories[0], + ); }); it('should preserve all category properties', () => { expect( - mergeLighthouseCategories(mockMultiUrlPlugin, [ + lighthouseCategories(mockMultiUrlPlugin, [ { slug: 'performance', title: 'Performance', @@ -386,7 +395,7 @@ describe('mergeLighthouseCategories', () => { }, ]; - const result = mergeLighthouseCategories(plugin, categories); + const result = lighthouseCategories(plugin, categories); expect(result[0]?.refs).toHaveLength(3); expect(result[0]?.refs.map(({ slug }) => slug)).toEqual([ @@ -397,7 +406,7 @@ describe('mergeLighthouseCategories', () => { }); it('should filter out invalid Lighthouse groups', () => { - const result = mergeLighthouseCategories({ + const result = lighthouseCategories({ groups: [ { slug: 'performance-1', title: 'Performance 1', refs: [] }, { slug: 'invalid-group-1', title: 'Invalid Group 1', refs: [] }, @@ -454,12 +463,12 @@ describe('mergeLighthouseCategories', () => { describe('edge cases', () => { it('should handle empty categories array', () => { - expect(mergeLighthouseCategories(mockMultiUrlPlugin, [])).toEqual([]); + expect(lighthouseCategories(mockMultiUrlPlugin, [])).toEqual([]); }); it('should handle plugin with empty groups array', () => { expect( - mergeLighthouseCategories( + lighthouseCategories( { groups: [], context: { urlCount: 0, weights: {} } }, mockUserCategories, ), @@ -475,9 +484,9 @@ describe('mergeLighthouseCategories', () => { }, ]; - expect( - mergeLighthouseCategories(mockMultiUrlPlugin, category)[0], - ).toEqual(category[0]); + expect(lighthouseCategories(mockMultiUrlPlugin, category)[0]).toEqual( + category[0], + ); }); }); });