npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

lighthouse-actions

v1.0.1

Published

Turn a Lighthouse Result (LHR) into a short, ranked list of plain-English things to do next.

Downloads

494

Readme

lighthouse-actions

A dependency-free TypeScript library that accepts a Lighthouse Result object (LHR) and returns a short, ranked list of plain-English things to do next.

import { prioritize } from 'lighthouse-actions';

const actions = prioritize(lhr, { limit: 5 });
// [
//   "[performance] Convert your 12 largest images to WebP or AVIF to save about 1.4 MB.",
//   "[performance] Defer the 3 render-blocking files in your <head> to cut about 900 ms off first paint.",
//   "[accessibility] Add alt text to the 8 images that are missing it.",
//   ...
// ]

See design-doc.md for the full design rationale — this README covers what's implemented, how to use it, and how it was validated.

Install

bun add lighthouse-actions

ESM only, zero runtime dependencies, Node >= 20. Also works in browsers and edge workers.

API

import { prioritize, analyze } from 'lighthouse-actions';

prioritize(lhr, options?): string[]
analyze(lhr, options?): Analysis

prioritize is analyze(lhr, options).actions.map(a => a.sentence). Use analyze when you need more than strings — category, priority, effort, impact numbers, and evidence per action.

Subpath exports lighthouse-actions/score and lighthouse-actions/render expose the priority model and the sentence registry directly, for callers who want to swap a piece out.

Options

| Option | Default | Notes | |---|---|---| | limit | 10 | Max actions returned | | categories | all present | Restrict to these category ids | | categoryWeights | { seo: 1.2, accessibility: 1.1, performance: 1.0, 'best-practices': 0.9 } | Multiplier per category. SEO outranks performance by default — see "Category weights" below | | minPriority | 0 | Drop actions below this priority | | includeInformative | false | Include informative audits (e.g. third-party-summary) | | voice | 'plain' | 'technical' allows LCP/CLS/TBT/FCP jargon | | effort | — | Override/extend the built-in effort table | | unknownAudits | 'generic' | 'skip' drops audits the registry doesn't recognize instead of using a generic sentence | | useStackPacks | false | Preview: prefer lhr.stackPacks framework-specific advice over the built-in template when available |

Architecture

Six pure stages (src/index.ts wires them together):

unknown → normalize → filter → score → render → select → Analysis
  • normalize (src/normalize/) — one adapter per Lighthouse major (v10–v13, plus a pre-v10 legacy adapter), dispatched by lighthouseVersion, all converging on a version-agnostic Finding model.
  • filter (src/filter.ts) — drops passing, notApplicable, manual, error, and (by default) informative audits, and restricts to options.categories.
  • score (src/score/priority.ts, src/score/effort.ts) — the priority model from design-doc.md §7: scoreGain × impactBoost ÷ effortCost.
  • render (src/render/) — a hand-written sentence per audit id where one exists (src/render/templates/*.ts), a generic fallback otherwise (src/render/fallback.ts), and an optional stack-pack override.
  • select — dedupe, apply limit and minPriority (in src/index.ts).

Category weights (§12.1)

Default weights are { seo: 1.2, accessibility: 1.1, performance: 1.0, 'best-practices': 0.9 } — SEO outranks performance by default, per the project's resolved open question. Override via options.categoryWeights if your context (e.g. an internal tool, not a public site) disagrees.

Effort table and priority sanity checks (§12.2)

src/score/effort.ts is curated judgment (src/data/known-audits.ts lists every audit id this repo has tested against; anything absent from the effort table falls back to 'unknown', which sits just above 'low' cost so unfamiliar audits neither dominate nor vanish).

Per §12.2, that table was meant to be evaluated against real reports for wikipedia.org, godaddy.com, slack.com, and ngdomain.ng. This sandbox has no outbound path to run that evaluation for real: there's no Chrome available to run Lighthouse locally, and the PageSpeed Insights API (the one network-only route to a real LHR) returns HTTP 429 with a zero daily quota for anonymous callers on this network — see scripts/gen-fixtures.ts for the exact failure. Run bun scripts/gen-fixtures.ts yourself in an environment with a PSI API key or local Chrome to regenerate test/fixtures/ from real reports; the loader (test/helpers/load-fixture.ts) doesn't care where the JSON came from.

In lieu of that, test/fixtures/{wikipedia,godaddy,slack,ngdomain,justiceo, amazon,jiji,jumia}.json are synthesized from each site's well-known characteristics (lean and fast for Wikipedia; heavy marketing images and third-party tags for GoDaddy; JS-bundle-heavy for Slack; unoptimized and pre-HTTPS-hardened for a smaller regional registrar; a lean personal site for justiceo.com; heavy third-party/ad tags at scale for Amazon; unoptimized classifieds imagery and slow backend for jiji.ng; a regional e-commerce mix of image weight, JS, and a known-vulnerable library for jumia.com.ng) using a shared audit catalog (scripts/fixture-catalog.ts) that mirrors real Lighthouse v10–13 audit ids and weights. Run bun scripts/inspect-fixtures.ts to print the top-5 for every fixture; the current output (also exercised by test/golden.test.ts) reads as a reviewer would expect — quick image/markup fixes outrank multi-week rewrites, and outright failures outrank near-misses. Treat the absolute ranking as a plausibility check, not a validated ground truth, until someone re-runs it against real reports.

How to validate against real data

scripts/inspect-fixtures.ts prints, per action, priority = gain x impact / effort — the exact terms from §7 — so a reviewer can see why something ranked where it did instead of cross-referencing priority.ts/ effort.ts by hand. To actually validate the model:

  1. Run bun scripts/gen-fixtures.ts in an environment with a PSI API key or local Chrome, pointed at wikipedia.org, godaddy.com, slack.com, ngdomain.ng, justiceo.com, amazon.com, jiji.ng, jumia.com.ng, and any other site whose profile isn't already represented.
  2. Replace the corresponding synthesized fixtures in test/fixtures/ with the real captures.
  3. Run bun run sync-audits (new real audit ids may need a template or an IGNORED entry — see "Coverage" below) then bun scripts/inspect-fixtures.ts.
  4. Read the top-5 breakdown for each site. If an ordering feels wrong, the breakdown tells you whether to adjust src/score/effort.ts (a specific audit's cost) or DEFAULT_CATEGORY_WEIGHTS in src/types.ts (a category-wide multiplier) — argue about one line, not the model.

Stack packs (§12.4, preview)

options.useStackPacks: true prefers lhr.stackPacks[].descriptions[auditId] over the built-in template when the LHR carries one, so a Next.js site gets "Use next/image..." instead of the generic form. Off by default: that text isn't ours to length- or tone-check.

Third-party attribution (§12.3)

third-party-summary is informative (no score) and excluded by default. With includeInformative: true, it's attributed to the top entity from lhr.entities/the audit's items — "Reduce or defer Google Tag Manager; it's costing you about 450 ms of main-thread time." — instead of a generic sentence.

Coverage (§9)

bun run sync-audits walks every committed fixture, writes the union of audit ids to src/data/known-audits.ts, and test/coverage.test.ts asserts every one is either in the registry or on the IGNORED list (src/data/ignored-audits.ts) with a reason. Adding a fixture from a new Lighthouse version therefore fails CI with exactly the ids that need attention.

89 audit ids are known, and all 89 have a hand-written template (100%, against the v1.0 target of 90%). The kitchen-sink fixture exists solely to exercise the ~46 ids that have a template but never appeared in a real or scenario-modeled fixture (things like canonical, tabindex, geolocation-on-start) — its catalog entries carry weight 0 so they can't perturb ranking in the other fixtures; see scripts/fixture-catalog.ts's COVERAGE_EXPANSION for the list and rationale.

Testing

bun test
  • test/priority.test.ts — the priority model's worked example from §7.
  • test/normalize.test.ts — version-drift adapters (v10–v13, legacy).
  • test/render.test.ts — voice, fallback, stack packs, third-party attribution.
  • test/sentence-rules.test.ts — the six sentence rules from §8, over every fixture.
  • test/coverage.test.ts — the §9 coverage backstop.
  • test/golden.test.ts — committed .expected.txt per fixture; regenerate with bun run gen-golden after an intentional model/template change and review the diff as English sentences.
  • test/analyze.test.ts — robustness (garbage input, runtimeError, single-category LHRs) and the invariants from §10 (length ≤ limit, unique sentences, non-increasing priority, deterministic output).

CLI companion (§11)

bunx @lighthouse-actions/cli report.json --limit 5

Lives in packages/cli, depends on the root package, and stays out of the core package's dependency graph. --json prints the full Analysis object; --category/--voice mirror the library options. Every sentence already carries a [category] tag straight from the library (§8.1 — Add a meta description... comes back as [seo] Add a meta description...); pass --hide-category for a bare sentence. --group-by category prints output under a ## <category> heading per group instead (and drops the now-redundant per-line tag); --show-effort extends the tag with low/medium/high, e.g. [seo, low]. Plain-text output also prints a trailing ...and N more (raise --limit to see them). line whenever Analysis.total exceeds what was shown, so a truncated CI comment doesn't read as a complete list.

Packaging

ESM only. bun build bundles the JS (see scripts/build.ts); bun run build runs that plus tsc -p tsconfig.build.json for .d.ts emission, since Bun's bundler has no declaration-emit equivalent. Zero runtime dependencies, sideEffects: false. Subpath exports: lighthouse-actions, lighthouse-actions/score, lighthouse-actions/render.

Milestone status

| | Scope | Status | |---|---|---| | v0.1 | Normalize (v13), priority model, ~30 performance templates, prioritize + analyze | Done | | v0.2 | Accessibility/SEO/best-practices templates, effort table, coverage test | Done | | v0.3 | v10–v12 adapters, full options surface, stack packs preview | Done | | v1.0 | Locked API, ≥90% audit coverage across fixtures, docs, CLI companion | Done |