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

lightsoutt

v0.1.1

Published

The network-floor analyzer: computes the earliest first paint the network allows by measuring your gzipped HTML against the ~14 KB TCP slow-start window and the render-blocking resources in <head>. Validated against 77 real sites.

Readme

lightsout

The network-floor analyzer: the earliest point at which the network allows your page to paint.

lightsout computes one honest, defensible number — the network floor — and is careful about what that number is not. It is not a prediction of when your page actually paints. It is the earliest first paint the network permits; real first paint is usually later, gated by JavaScript. We know that because we validated it against 77 real sites — actual FCP ran ~10× later than the floor and barely correlated with it.

When a browser opens a TCP connection, the server can't send data at full speed right away — it has to probe the network with "slow start". The first burst it's allowed to send before waiting for an acknowledgment is the initial congestion window, ~10 packets on modern servers:

10 packets × ~1460 B payload  ≈  14,600 bytes  ≈  "14 KB"

If your gzipped HTML document fits in that ~14 KB window, the browser gets the whole thing in the first round-trip and can start painting immediately. Bust it, and the network floor rises by a round-trip (50–200 ms on real mobile) before anything can appear.

lightsout measures that for you — and, crucially, separates two things most size tools conflate:

  1. The HTML document vs the 14 KB budget. This is the verdict.
  2. Render-blocking resources in <head> (stylesheets, synchronous scripts). These don't add to the 14 KB — the browser can't even discover them until the HTML arrives — but each one costs an additional round-trip before first paint. lightsout lists them separately, because that's how loading really works.

Then it models the whole thing as a round-trip waterfall: how many network round-trips first paint actually costs, accounting for TCP slow-start (the congestion window roughly doubles each trip, so a doc bigger than 14 KB can still arrive in 2 trips, and render-blocking resources fetched in parallel add their own).

Install

npm i -g lightsoutt    # then the command is `lightsoutt`
# or run without installing:  npx lightsoutt <url> --fcp

Usage

lightsoutt https://example.com               # network floor + document evidence
lightsoutt https://example.com --fcp         # + real FCP, paint readiness, classification
lightsoutt scan ./my-project                 # analyze your codebase directly (most accurate)
lightsoutt --file ./index.html               # measure a local HTML file
lightsoutt https://x.com --budget 14336      # override the byte budget
lightsoutt https://x.com --rtt 100           # assume a 100 ms RTT for the floor
lightsoutt https://x.com --json              # machine-readable, for CI

lightsout scan — analyze the project, not a black-box URL

A live fetch can't tell a client-rendered SPA from a server-rendered page, can't see your build setup, and can't read the bundle a runtime injects. Running inside the project lifts all three blind spots — it reads package.json (framework + render strategy), the HTML entry/build output, and the actual JS/CSS from disk to give accurate, framework-aware advice:

  lightsout scan — /path/to/my-spa
  ══════════════════════════════════════════════════════
  framework          React  ·  Vite
  render strategy    React SPA — likely client-rendered
  network floor      300 ms   (2 round-trips @ 150 ms RTT)
  javascript         69.0 KB gzip   (all scripts the page loads)
  rendering          ❌ client-rendered shell (only ~0 chars of static content in <body>)

  recommendations:
    ⛔ [architectural] client-rendered SPA — first paint waits on the JS bundle (69 KB gzip)
        …no 14 KB tweak reaches the floor while rendering is client-side. Fix:
        add a prerender/SSG step (vite-plugin-ssr, vite-react-ssg) or move to Astro.

Every recommendation is graded by risk (✓ safe, ⚠ caution, ⛔ architectural) and the fix is tailored to your stack (CRA → react-snap; Vite → vite-ssg; Svelte → SvelteKit prerender; …). Pair it with lightsout <url> --fcp for the measured PRR.

The URL output leads with paint efficiency and treats the byte budget as a diagnostic, not a verdict — because a passing budget barely predicts early paint (Spotify fits and is awful; Figma busts and is excellent):

  lightsout — https://www.spotify.com
  ══════════════════════════════════════════════════════
  paint efficiency   ❌ JS-bound
  PRR                0.03   paint waits 10×+ the floor; the HTML budget is irrelevant here

  network floor      450 ms     (3 round-trips @ 150 ms RTT)
  actual FCP      13,500 ms     (measured in headless Chrome)

  document (diagnostic, not a verdict)
    19.71 KB (gzip~ est.) — spills past the first window — adds 1 round-trip to the floor

Same tool, a site that does it right:

  lightsout — https://www.dell.com --fcp
  paint efficiency   ✅ Floor-limited
  PRR                1.00
  network floor      600 ms
  actual FCP         292 ms     (measured in headless Chrome)

Interactive explorer (TUI)

npx lightsoutt-tui        # or: npm run tui

A zero-dependency terminal UI: a colour-coded PRR leaderboard from the committed benchmark (Floor-limited green → JS-bound red), plus a live analyzer — type a URL (or press ⏎ on a row) and it renders the page in headless Chrome and animates a floor-vs-FCP gauge where the floor bar's length is the PRR. lightsout-tui --selftest prints one static frame (handy for non-TTY/screenshots).

Paint Readiness Ratio & classification

PRR = network floor ÷ actual FCP   (capped at 1.0)

| PRR | Classification | Meaning | |---|---|---| | ≥ 0.8 | Floor-limited | paints at the network floor — the network is the only cost left | | 0.5–0.8 | Efficient | paints close to the floor; little JS in the way | | 0.2–0.5 | Moderately delayed | real paint runs a few× past the floor | | 0.1–0.2 | JS-taxed | the browser waits 5–10× the floor — JavaScript is the bottleneck | | < 0.1 | JS-bound | paint waits 10×+ the floor; the HTML budget is irrelevant here |

--fcp drives the system Chrome over the DevTools Protocol (no Puppeteer, no install) under a --rtt throttle and reads the page's real Paint Timing entry. Without --fcp you still get the network floor and document evidence; PRR needs a real render.

Exit code is 1 when the HTML document busts the budget, so lightsout still works as a CI page-weight gate.

Honest measurement

The number that matters is what actually travels. For a live URL, lightsout reads the server's real content-encoding / content-length and reports those bytes. Only when the server doesn't expose a usable length (e.g. compressed chunked responses) does it fall back to a gzip estimate — and it labels that with est. so you always know which number you're looking at.

As a library

import { analyze } from "lightsout";

const html = await (await fetch("https://example.com")).text();
const report = await analyze(html, { baseUrl: "https://example.com" });
// → {
//     budget,
//     document:     { gzip, raw, wire, encoding, estimated, fits, pct },
//     blocking:     [ { type, url, wire, gzip, encoding, estimated } ],
//     criticalPath: { roundTrips, htmlTrips, blockingTrips, networkFloorMs },
//   }
// networkFloorMs = roundTrips × the assumed RTT (`rtt` option, default 150 ms) —
// the earliest first paint the network allows, NOT a prediction of actual FCP.

Turn a real First Contentful Paint into a paint-readiness verdict:

import { paintReadiness, classifyPRR } from "lightsout";

const prr = paintReadiness(report.criticalPath.networkFloorMs, fcpMs); // floor ÷ FCP, capped at 1
classifyPRR(prr).label; // → "Floor-limited" | "Efficient" | … | "JS-bound"

To measure fcpMs yourself without Puppeteer, the package also ships a headless Chrome driver: import { launchBrowser } from "lightsout/cdp" (used by --fcp).

GitHub Action

Gate every pull request on the budget. The action wraps the same analysis and writes a render-blocking breakdown to the PR's job summary; it fails the job when the HTML document busts the first round-trip (toggle with fail-on-bust).

# .github/workflows/lightsout.yml
name: lightsout
on: pull_request
jobs:
  budget:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: lightsout/action@v1
        with:
          file: dist/index.html         # …or  url: https://your-preview-deploy
          # budget: 14336               # optional (default ~14 KB)
          # fail-on-bust: true          # optional (default true)

Outputs fits, document-bytes, and round-trips for downstream steps. A full example lives in examples/github-workflow.yml.

Validation against 77 real sites (the centerpiece)

Most projects build a model. Far fewer test the model against reality and publish the result — including where it fails. That's the most interesting thing here.

bench/ runs the exact lightsout analysis across ~100 popular homepages, then renders the reachable ones in a throttled headless Chrome (~150 ms RTT) and records each page's real First Contentful Paint from the Paint Timing API. Everything is measured and committed — see bench/RESULTS.md and bench/dataset.csv.

| Metric | Value | |---|---:| | Mean network floor (model) | 608 ms | | Mean actual FCP (browser) | 6,521 ms | | Ratio (actual ÷ floor) | 10.7× | | Correlation (predicted RTTs ↔ FCP) | ≈ 0 |

The finding: network constraints matter, but JavaScript dominates. Real first paint runs well past the network floor and barely tracks it, because popular homepages paint when their JavaScript is ready — not when the HTML arrives. The 14 KB rule removes the network excuse; it does nothing about the JS.

Controlled re-run (to kill the "noisy measurement" objection). The table above renders many sites in concurrent throttled Chrome tabs, so per-site FCP is noisy. A separate high-quality run — 20 sites, one tab at a time, 3 cold reps each — shows the repetition noise is tiny (median per-site PRR SD ≈ 0.02), so the floor-vs-paint gap is real, not an artifact. It also corrects the picture: free of CPU contention, mean PRR is ~0.46, not the contended run's ~0.14. Some sites (google, dell, figma) paint right at the floor; the JS-bound tail (spotify 0.03, x 0.07, github/microsoft 0.10) is genuine and stable. JavaScript dominates where it dominates — and the clean numbers say exactly where.

Paint Readiness Ratio (PRR)

A single number that falls straight out of the benchmark and exposes JS-bound sites instantly:

PRR  =  network floor  ÷  actual FCP        (capped at 1.0)

1.0 means the page paints as soon as the network permits. 0.05 means the browser waits ~20× longer than the network requires — a JavaScript-bound page.

| Paints near the floor | PRR | | Waits on JavaScript | PRR | |---|---:|---|---|---:| | dell.com | ~1.00 | | reddit.com | ~0.01 | | lyft.com | ~1.00 | | outlook.live.com | ~0.01 | | figma.com | ~0.62 | | wikipedia.org | ~0.02 | | mozilla.org | ~0.76 | | open.spotify.com | ~0.03 |

Figma succeeds not because it is small (257 KB doc, 6-RTT floor) but because it paints early. SoundCloud's document fits the 14 KB budget and still paints ~20× past its floor. Small and early are different goals — PRR measures the one that matters. (Per-site FCP is noisy under concurrent throttled tabs; the aggregate and the high/low split are the robust signal.)

Reproduce it:

npm run bench                      # network layer only — writes dataset.json/.csv + RESULTS.md
node bench/run-bench.mjs --fcp     # also measure real FCP + PRR in headless Chrome

Why this exists

Inspired by “Why your website should be under 14 kB in size”. That article explains the rule. This tool started out trying to enforce it as a performance predictor — then the benchmark showed that the network round-trip isn't what makes real pages slow to paint. So it makes a narrower, defensible claim instead: it reports the network floor, the earliest paint the network allows, and is explicit that JavaScript is what usually keeps a page from reaching it.

License

MIT