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

@yceffort/coldpath

v0.10.1

Published

Trace which bundled JavaScript executes in real browser scenarios and why it is loaded

Readme

coldpath

CI License: MIT

See which JavaScript runs initially, which runs only during interactions, and what changed in a pull request—backed by source maps and V8 coverage.

coldpath is a Rust CLI. Analysis, compression, and report generation run offline, without Node.js or a browser. Bring an existing Chrome, Playwright, Puppeteer, or Node coverage recording, or use the optional Chromium collector.

  • Attribute generated JavaScript to original sources using source maps.
  • Keep observed, unobserved, and unmeasured bytes separate.
  • Explore bundle/folder/package treemaps, with an optional source-code inspector.
  • Color bytes by their first observed scenario; inspect code separately for each recording.
  • Compare a baseline by source, package, and scenario; highlight growth and new modules.
  • Union recordings from the same build and reject mismatched source evidence.
  • Accept files/globs and export HTML, JSON, TSV, and Markdown; enforce byte budgets in CI.
  • Trace import chains and available locations from esbuild, webpack, Rollup/Vite, and Next.js Turbopack graphs.
  • Review suggested loading boundaries with explicitly estimated source gzip/Brotli sizes.
  • Measure per-source CPU self time from repeated Chromium profiles, with sample counts and spread, and compare it with a baseline.

Early-stage software: the CLI and JSON schema may change. Unobserved bytes are code that did not run during the supplied scenarios; they are not automatically safe to delete.

Install

npm install --save-dev @yceffort/coldpath

The npm package includes a prebuilt analyzer for macOS (arm64, x64) and Linux (arm64, x64; glibc 2.35 or newer) through an optional @yceffort/coldpath-<platform>-<arch> dependency. Windows is not supported. It puts the collector, graph export, and analyzer behind one coldpath command, and exports @yceffort/coldpath/rollup, @yceffort/coldpath/vite, and @yceffort/coldpath/webpack graph plugins. Collection and CPU profiling additionally need playwright in your project.

On other platforms, or to use the analyzer without Node.js, build it with Rust 1.88 or newer:

cargo install --git https://github.com/yceffort/coldpath --locked

Or build locally:

git clone https://github.com/yceffort/coldpath.git
cd coldpath
cargo build --locked --release
./target/release/coldpath --help

The analyzer is not published to crates.io. Node.js is only needed for collection, graph export, labeling, and integration tests.

Node package

coldpath collect --scenarios coldpath.scenarios.json
coldpath analyze --scenarios coldpath.scenarios.json --graph dist/coldpath.graph.json --treemap artifacts/actions.html

coldpath analyze (or coldpath with analyzer options) runs the Rust analyzer from COLDPATH_ANALYZER, the installed @yceffort/coldpath-<platform>-<arch> package, or the native coldpath binary on PATH (the npm wrapper skips itself), in that order. See collecting coverage for the scenario file.

Claude Code skill

The repository is also a Claude Code plugin with a coldpath skill, which guides an agent through graph setup, scenarios, reading observed and unobserved bytes, and measuring and comparing CPU self time in apps you build:

/plugin marketplace add yceffort/coldpath
/plugin install coldpath@coldpath

Marketplaces outside Anthropic's do not update automatically by default. After a release, run claude plugin update coldpath@coldpath, or turn on auto-update for the marketplace in /plugin.

Try the recorded example

From a checkout, run this without installing Node.js or Chromium:

cargo run --locked --release -- \
  --dir examples/recorded \
  --coverage examples/recorded/initial.coverage.json \
  --coverage examples/recorded/interaction.coverage.json \
  --initial-scenario initial \
  --json artifacts/report.json \
  --treemap artifacts/report.html --details

Expected totals: 293 bytes, 208 observed, 85 unobserved, 0 unmeasured. Open artifacts/report.html directly in your browser. Colors separate initial and later execution; click a source and select a scenario to inspect its code ranges. Its data, scripts, and styles are embedded; no report server or network requests are needed.

The example contains real V8 coverage, source maps, and multibyte characters. Do not reformat examples/recorded: the recordings verify the exact JavaScript and map hashes.

Walkthrough: find code that only runs after a click

This walkthrough uses examples/demo, a small React dashboard built with Vite. Its "Open report" button renders a chart component that Dashboard.jsx imports statically, and its search box loads search.js with import(). The screenshots below come from exactly these steps.

1. Emit a dependency graph from your build. Add the plugin and enable source maps (vite.config.mjs):

import coldpathGraph from '@yceffort/coldpath/vite' // @yceffort/coldpath/rollup and @yceffort/coldpath/webpack also exist

export default {
  plugins: [coldpathGraph()],
  build: {sourcemap: true},
}

vite build now also writes dist/coldpath.graph.json. The graph lets the report explain why a module is in the bundle (the import chain and the line of each import).

2. Describe the scenarios. A scenario is a page load plus optional Playwright actions. List them in the order a user would encounter them (coldpath.scenarios.json):

{
  "url": "http://127.0.0.1:4173/",
  "dir": "dist",
  "out": "artifacts/coverage",
  "scenarios": [
    {"name": "initial"},
    {"name": "open-report", "actions": "scenarios/open-report.mjs"},
    {"name": "search", "actions": "scenarios/search.mjs"}
  ]
}
// scenarios/open-report.mjs
export default async function ({page}) {
  await page.getByRole('button', {name: 'Open report'}).click()
  await page.getByRole('img', {name: 'Weekly orders'}).waitFor()
}

3. Serve the build and record each scenario in Chromium.

npx vite preview --port 4173 --host 127.0.0.1   # in another terminal
npx coldpath collect --scenarios coldpath.scenarios.json
initial: 1 scripts, 2 snapshots -> /path/to/demo/artifacts/coverage/initial.coverage.json
open-report: 1 scripts, 2 snapshots -> /path/to/demo/artifacts/coverage/open-report.coverage.json
search: 2 scripts, 2 snapshots -> /path/to/demo/artifacts/coverage/search.coverage.json

Each recording is bound to the exact files in dist by SHA-256, so it cannot be applied to a different build by mistake.

4. Analyze and open the report.

npx coldpath analyze --scenarios coldpath.scenarios.json \
  --graph dist/coldpath.graph.json --source-compression --details \
  --treemap artifacts/report.html --markdown artifacts/summary.md
Generated UTF-8 bytes: 222552 (2 bundles)
Observed: 57691 | Unobserved: 164861 | Unmeasured: 0
Unobserved means not executed during the supplied scenarios, not safe to delete.

Scenario initial: 43835 observed | 178406 unobserved | 311 unmeasured
Scenario open-report: 55121 observed | 167120 unobserved | 311 unmeasured
Scenario search: 54068 observed | 168484 unobserved | 0 unmeasured
       Bytes     Observed   Unobserved   Unmeasured  Package
      206936        51629       155307            0  react-dom
        8180         2048         6132            0  react
        3535         1888         1647            0  scheduler
        3008         1840         1168            0  [application]
         893          286          607            0  [unmapped]

Open artifacts/report.html in a browser (it works offline). The top of the report says what matters in plain sentences: how much of the loaded JavaScript never ran, which packages were loaded but mostly never ran, and which of your own files run only after a specific action. Select an entry to find it in the treemap.

Report summary: 164.9 KB of 222.6 KB never ran; react-dom and react are 75% unused; ReportChart.jsx runs only after "open-report"

Below the summary, files that no recording loaded are hidden (the summary offers to show them), search, grouping, and the color mode sit above the treemap, and the remaining filters live under "More options". Tile area is generated bytes, and the Area switch sizes tiles by the bytes that never ran instead. Each tile has one color: the share of its bytes that never ran, in five steps from warm (ran) to ice blue (never ran), with grey for unmeasured bytes. The First observed scenario and Initial comparison modes color each tile by its largest part. Bundles and folders with room show their contents one level deep under a line of header text, and children too small to see are merged into one "N smaller items" tile that zooms to them. Labels are plain text on the tile: the name, then the size and the share that never ran when they fit. The panel beside the map (below it on phones) shows the numbers of the tile under the pointer or keyboard focus, and otherwise the current view's totals and its five files with the most code that never ran. Selecting a tile zooms in or opens the file. Grouped by package, most of this bundle is react-dom code that none of the three scenarios executed, and the panel shows that 47% of scheduler never ran:

Treemap of the demo bundle grouped by package, colored by the share that never ran, with the panel showing scheduler

5. Follow a review action. "Review actions" (between the summary and the explorer) lists sources whose bytes first run after the initial load. Selecting ReportChart.jsx shows that 48 B of it run on the initial load (its top-level declarations) and 532 B run only in "open-report", the static import chain main.jsx → Dashboard.jsx:4:1 → ReportChart.jsx that pulls it into the initial bundle, isolated gzip estimates, and a code inspector per scenario:

Source detail for ReportChart.jsx with its import chain, review action, and code inspector

The action is "review lazy loading" because those 48 B are only top-level declarations (width and height) with no calls or property writes; every function in the module runs only after "open-report". A module whose functions run initially, or whose top level calls, constructs, or writes properties, gets "split later-only functionality" instead, because moving the whole import behind import() could change initial behavior. search.js is already behind a dynamic import, so the report asks you to record it in the initial scenario before drawing conclusions instead of suggesting a change. Estimates are for review, not guaranteed savings: rebuild and record again to measure the result.

6. Check pull requests in CI. The GitHub Action runs the same analysis on the base branch and the pull request, enforces growth budgets, and keeps one comment up to date on the pull request:

The Action's sticky pull request comment with observed, unobserved, and package totals

To reproduce this walkthrough from a checkout, run pnpm install --frozen-lockfile, pnpm exec playwright install chromium, pnpm build:js (which compiles the Vite plugin the demo imports), and cargo build --locked --release, then:

pnpm exec vite build examples/demo
pnpm exec vite preview examples/demo --port 4173 --host 127.0.0.1   # in another terminal
cd examples/demo
node ../../bin/coldpath.ts collect --scenarios coldpath.scenarios.json
COLDPATH_ANALYZER=../../target/release/coldpath node ../../bin/coldpath.ts analyze \
  --scenarios coldpath.scenarios.json --graph dist/coldpath.graph.json \
  --source-compression --details --treemap artifacts/report.html

Analyze your build

For a compact size explorer, pass files or a quoted glob:

coldpath 'dist/**/*.js' --treemap artifacts/size.html
coldpath dist/app.js dist/app.js.map --json artifacts/size.json
coldpath 'dist/**/*.js' --tsv -

Open the HTML directly in your browser. Bundles and folders with room show their contents one level deep, and tiles too small to see are merged into one that zooms to them. Click any box to zoom into it with an animated transition, zoom back out with breadcrumbs or the browser's back button, search sources, or group by package. Tile area represents bytes; tile color is the share of each tile's bytes that never ran, with grey for unmeasured code. Every file is available in the table, including small tiles. With file/glob inputs and no output option, the CLI writes coldpath.html in the current directory, replacing any existing file, and prints Wrote coldpath.html after saving it.

Build your application with source maps and point the CLI at its JavaScript output:

coldpath --dir dist --compression --html artifacts/bundle.html

Without a recording, all bytes are unmeasured. Missing source maps are allowed and remain [unmapped]; explicitly referenced missing or invalid maps cause an error.

To add a Chrome Coverage export:

coldpath --dir dist \
  --coverage chrome-coverage.json \
  --url-prefix https://example.com/assets/ \
  --json artifacts/coverage.json \
  --html artifacts/coverage.html

--url-prefix maps the URL suffix to a file under the analysis root and must end in /. Combine --dir with file/glob inputs to keep this root fixed when selecting only part of a build, for example --dir dist 'dist/assets/*.js'. Without --dir, the common parent of the selected JavaScript files becomes the root. Use repeated --coverage arguments to union scenarios from the same build. For coverage of unselected files, exact URL mappings, source-map overrides, Playwright inputs, and Node coverage, see the usage guide.

Understand the numbers

| Field | Meaning | | ----------------- | ----------------------------------------------------------- | | bytes | Generated, uncompressed UTF-8 bytes | | observedBytes | Bytes in ranges executed in at least one supplied recording | | unobservedBytes | Bytes not executed in a script that has a recording | | unmeasuredBytes | Bytes in a script with no supplied recording |

The three states sum to bytes. V8 offsets use UTF-16 code units; the analyzer converts them to UTF-8 byte boundaries. These numbers are neither original TypeScript file sizes nor compressed transfer sizes.

Source-map attribution is an estimate: a mapping owns bytes up to the next mapping on the same line or the line's end. Unmapped prefixes, line breaks, and segments without a source remain [unmapped]. Original source highlights identify mapping anchors, not exact source-level statement or branch coverage.

--treemap exports a compact size/coverage explorer; add --details to include its inline code inspector. --html exports the full generated/original code inspector. Detailed outputs include source code; summary JSON and default treemaps omit it. Large inspector payloads require a browser with DecompressionStream support.

Find loading boundaries

coldpath --dir dist \
  --coverage initial.json --coverage open-report.json --coverage search.json \
  --initial-scenario initial --scenario-order initial,open-report,search \
  --graph artifacts/graph.json --graph-root . --source-compression \
  --treemap artifacts/actions.html --details --markdown artifacts/actions.md

Export a graph from your bundler first. The report distinguishes static-import deferral candidates, sources that need to be split because part executes initially, existing dynamic boundaries, and missing initial measurements. Click a colored tile for import locations, estimates, and scenario-specific code highlights. Estimates compress source fragments in isolation; they are not guaranteed transfer savings.

Scenario order is explicit, not inferred from timestamps. Standard coverage exports use filenames as scenario names; hash-bound envelopes use their scenario field.

Collect a browser scenario

The optional collector requires Node.js 22.12+, Playwright, and Chromium:

npm install --save-dev playwright
npx playwright install chromium

# Serve the same build locally in another terminal.
coldpath collect \
  --url http://127.0.0.1:3000/ \
  --dir path/to/dist/assets \
  --prefix /assets/ \
  --out artifacts/initial.coverage.json

For Next.js, use the build's static output directory with --prefix /_next/static/. The collector accepts a local action module for clicks, searches, and other interactions. It prints the final page state, console errors, failed requests, and any requests it blocked, and --expect SELECTOR fails the recording when the page does not show what the scenario expects, such as after an API call failed. --repeat N records a scenario N times and the analyzer reports the run-to-run spread; coldpath --dir dist --diff a.coverage.json b.coverage.json lists what two recordings executed differently. See collecting coverage for the action API, page state checks, capture scope, and source-map limitations.

Measure CPU cost

coldpath profile records repeated CPU profiles of the scenarios in a scenario file, with only the profiler running, and --profile joins them to the same sources:

coldpath collect --scenarios coldpath.scenarios.json
coldpath profile --scenarios coldpath.scenarios.json
coldpath analyze --scenarios coldpath.scenarios.json \
  --profile artifacts/coverage/initial.profile.json \
  --profile artifacts/coverage/open-report.profile.json \
  --markdown artifacts/summary.md --treemap artifacts/report.html

analyze --scenarios also reads every scenario's coverage file; to analyze profiles alone, pass --dir and --profile to the analyzer.

Each source gets its self time in each scenario's page-load and action windows: the median and quartiles over 10 runs, with its sample count. Below 10 samples per run a value is marked insufficient, never shown as cheap. With --baseline, a source is reported as regressed only when a rank test across runs is significant and the shift is at least 25%; profile both builds on one machine. See CPU cost for attribution rules, the JSON fields, and the measurements behind these defaults.

Analyze a site you do not build

Without source maps, coldpath snapshot records a deployed page's scripts, coverage, and what caused each script to load; coldpath modules recovers webpack and Turbopack module boundaries as synthetic sources; and coldpath label asks a language model (Anthropic or any OpenAI-compatible endpoint) to summarize those modules and guess what they are, keeping only guesses whose evidence strings occur in the module and are rare elsewhere. The treemap then groups bundles by load cause and marks guessed names as inferred. See analyzing a site you do not build.

Use in CI

coldpath --dir dist \
  --exclude 'vendor/**' \
  --compression --max-bytes 1000000 \
  --json artifacts/report.json \
  --markdown artifacts/summary.md

Exit codes: 0 success, 1 input/analysis error, 2 budget exceeded. Argument syntax errors also use 2 (clap). --replay also exits 3 when the current analyzer differs from the recorded one and 4 when an excerpt reproduced only its selected bundles (replay). Reports are written before budget failure. Configuration also supports observed-scenario coverage budgets and gzip/Brotli budgets.

Compare a PR build against a saved report from main:

coldpath --dir dist --baseline artifacts/main.json \
  --max-added-bytes 10000 --treemap artifacts/pr.html \
  --json artifacts/pr.json --markdown artifacts/pr.md

Use the same analysis-root convention, bundle selection, and mode (with or without coverage) for both builds. Reports use schema version 3 with normalized source paths; regenerate older baselines. Add matching coverage scenarios to compare execution changes, and --initial-scenario initial --max-added-unobserved-bytes 10000 to budget initial unobserved growth. --max-added-package-bytes zod=5000 and --fail-on-new-package single out one dependency growing or entering the bundle, and --initial-html .next/server/app/index.html --max-added-initial-bytes 5000 budgets the bytes a prerendered document loads initially, without a browser. See scenario and PR comparisons for measurement requirements and visual controls.

GitHub Action

The repository is also a composite action. It builds the analyzer (or downloads a prebuilt one), runs your analyzer options on a checkout of the base branch and on the pull request build, uploads the report JSON, Markdown summary, and HTML treemap as an artifact, and creates or updates a single pull request comment. Budget failures fail the job after the comment is written.

permissions:
  contents: read
  pull-requests: write
steps:
  - uses: actions/checkout@v6
  - uses: actions/checkout@v6
    with:
      ref: ${{ github.event.pull_request.base.sha }}
      path: base
  # Build both checkouts and record coverage here.
  - uses: yceffort/coldpath@main
    with:
      base-directory: base
      args: |
        --dir
        dist
        --coverage
        artifacts/initial.coverage.json
        --initial-scenario
        initial
      max-added-bytes: 10000
      max-added-unobserved-bytes: 10000

args takes one argument per line, ignoring trailing whitespace, and must not include output options. An argument the analyzer rejects also exits with code 2 but writes no report, so the action fails at the analysis step instead of reporting a budget failure. Use baseline instead of base-directory to pass a report you already have, for example one downloaded from the base branch's last run. baseline must not point at this action's own report output from an earlier step in the same job, because the analysis step removes a stale report before it runs. The comment is found by a marker and must be written by a bot account (the default GITHUB_TOKEN is). Pull requests from forks get a read-only token, so the comment step only warns there; the artifact is still uploaded. By default the action builds the analyzer from its own checkout, so the runner needs Rust (cargo) and access to crates.io. analyzer: npm instead downloads the prebuilt analyzer published on npm for the action's version (analyzer-version picks another), which needs Node.js and access to the npm registry or your configured mirror. Any other analyzer value is the path of an analyzer binary that the workflow already provides. pr-report.yml runs the action on this repository's recorded example, both ways.

The comment includes CPU changes when args lists --profile files and both builds were profiled in the same job. args runs in both checkouts, so write each build's profiles to the same relative paths in its own checkout; a missing profile fails the base analysis. Profiles from another runner, such as those in a downloaded baseline, are reported as inconclusive: two runners measured the same build 22 to 32% apart. Profile on a Linux runner: GitHub's macOS runners sampled a third to an eighth as often, so far fewer sources reach 10 samples per run. See CPU cost in CI.

Development

cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked

# Optional integration checks with real Chromium and Node coverage:
pnpm install --frozen-lockfile
pnpm exec playwright install chromium
pnpm test:browser
pnpm test:corpus
pnpm test:package

CI runs Rust tests on Linux and macOS, checks the minimum Rust version on Linux, and exercises the HTML report, input adapters, collector, CPU profiles, and five real bundler builds in Chromium on both platforms, and repeats those browser checks on Linux with the minimum supported Node.js. See CONTRIBUTING.md for test boundaries and fixtures.

A reproducible comparison and explorer measurements record performance and compatibility on one saved build. These are development measurements, not a general performance ranking or proof of attribution accuracy across bundlers.

Measured attribution accuracy

The real-build corpus publishes reference-map agreement, known-origin probe errors, and per-scenario V8 range checks for esbuild, Rollup, Vite, webpack, and Next.js/Turbopack. It includes a negative control that changes source ownership while retaining every byte count.

The current Next.js fixture exposes 22 incorrectly attributed probe bytes out of 67, caused by source-map anchors inside inlined literals. Agreement with a source map does not prove semantic attribution is correct. These small fixtures do not establish an error rate for arbitrary applications.

Origin and license

Extracted from an experiment in yceffort/blog. The original repository keeps the blog-specific performance studies and measurement data; this repository maintains the reusable analyzer and collector.

MIT © 2026 yceffort.