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

@wolfcola/treeshake-check

v1.1.1

Published

Check whether a package can be fully tree-shaken by Rollup

Readme

@wolfcola/treeshake-check

A tree-shakeability analyzer for npm packages. Tells you whether your package can be fully tree-shaken by Rollup, and when it can't, points at the specific files, exports, and likely causes preventing it.

Built on Effect, @effect/cli, and Rollup.

Why this exists

When you publish a library, consumers' bundlers (webpack, Rollup, Vite, esbuild) try to eliminate unused exports from your package — that's tree-shaking. If your package isn't shakeable, every consumer who imports a single function pulls in your entire library, inflating their bundle size.

Tree-shakeability isn't visible from the outside. You can ship what looks like a clean ESM library and still have it be unshakeable due to a single Object.defineProperty call at module scope, a missing "sideEffects": false in package.json, or a transitive CJS dependency. This tool surfaces those problems.

The technique is the same one used by Rich Harris's agadoo: bundle your package as a side-effect-only import (import "your-package" with no bindings used) and see what Rollup couldn't eliminate. Anything that survives is what's preventing tree-shaking.

Installation

pnpm add -D @wolfcola/treeshake-check

Usage

Quickstart

From any package directory:

pnpm treeshake-check

You'll get one of two outcomes:

  • Fully tree-shakeable — ASCII tree celebration plus any recommendations for package.json improvements.
  • Has side effects — a per-module breakdown of what survived, with diagnostic info for each file.

Flags

| Flag | Alias | Description | | ---------------- | ----- | ------------------------------------------------------------------------------- | | --cwd <path> | -C | Directory containing package.json. Defaults to the current working directory. | | --entry <path> | -e | Analyze a specific entry file directly, skipping package.json resolution. | | --json | | Emit machine-readable JSON instead of human output. | | --quiet | -q | Suppress all output; rely on the exit code only. | | --top <n> | | Show only the N modules with the largest surviving byte count. |

Plus the standard --help, --version, --wizard, and --completions <shell> flags from @effect/cli.

Examples

Check the current package:

pnpm treeshake-check

Check a different package in the workspace:

pnpm treeshake-check --cwd packages/my-sdk

Check a specific built file directly:

pnpm treeshake-check --entry dist/index.js

Show only the worst 5 offenders:

pnpm treeshake-check --top 5

JSON output for CI tooling:

pnpm treeshake-check --json | jq '.modules[] | {id, renderedLength, suspectedCauses}'

Detected causes

The analyzer uses AST-based heuristics to classify surviving code:

| Cause | Description | | ----------------------- | --------------------------------------------------------- | | EnumPattern | TypeScript enum compiled to IIFE | | CommonJsContamination | require(), module.exports, __esModule markers | | PrototypeMutation | Object.defineProperty, .prototype.x = ... | | GlobalAssignment | Assignment to window, globalThis, self, or global | | UnannotatedCall | Top-level function call without /*#__PURE__*/ | | TopLevelSideEffect | Top-level statement with observable effects | | Unknown | None of the above patterns matched |

Labels are heuristic — a starting point for investigation, not a verdict.

Programmatic usage

import { Effect } from 'effect';
import { NodeContext } from '@effect/platform-node';
import { checkPackage } from '@wolfcola/treeshake-check';

const program = Effect.gen(function* () {
  const result = yield* checkPackage('./packages/sdk');

  if (result._tag === 'FullyTreeshakeable') {
    return true;
  }

  for (const m of result.modules) {
    console.log(`${m.id}: ${m.renderedLength}/${m.originalLength} bytes`);
    console.log(`  causes: ${m.suspectedCauses.join(', ')}`);
  }
  return false;
});

const isShakeable = await Effect.runPromise(program.pipe(Effect.provide(NodeContext.layer)));

CI integration

treeshake-check exits with code 1 when a package isn't fully shakeable, so it composes naturally as a quality gate.

GitHub Actions

- name: Tree-shake check
  run: pnpm -r --filter "./packages/*" exec treeshake-check --top 5

As a pre-publish hook

{
  "scripts": {
    "prepublishOnly": "treeshake-check --quiet"
  }
}

Across a monorepo

pnpm -r --parallel exec treeshake-check --top 3

How it works

  1. Reads package.json from the target directory and resolves the entry point (exportsmodulemain).
  2. Constructs a synthetic Rollup entry that imports the target as a side-effect-only import: import "/absolute/path/to/entry.js".
  3. Runs Rollup with default tree-shaking enabled.
  4. Inspects chunk.modules for per-module renderedLength, renderedExports, and removedExports.
  5. Classifies surviving code by AST analysis (with regex fallback for unparseable output).
  6. Reports per-module statistics, surviving code, and package.json recommendations.

Prior art

  • agadoo by Rich Harris — same technique, the original implementation. This package adds richer diagnostics, structured output, and Effect-based composition.
  • bundlephobia — measures the post-shake size from a consumer's perspective rather than analyzing why shaking succeeds or fails.