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

ngx-phantom

v1.0.0

Published

Dead code eliminator for Angular monorepos — finds every exported symbol with zero consumers across your entire workspace

Readme

ngx-phantom

Dead code eliminator for Angular monorepos.

Statically analyzes your entire dependency graph and reports every publicly exported symbol that has zero consumers across the workspace. Then offers an auto-prune mode to remove them from barrel files.

Written in Zig — scans a 150-library monorepo in under 300ms.


Installation

npm install -g ngx-phantom
# or run without installing
npx ngx-phantom analyze

Requirements

Your monorepo must have a tsconfig.base.json (NX) or tsconfig.json at the root with compilerOptions.paths mapping library names to entry files:

// tsconfig.base.json
{
  "compilerOptions": {
    "paths": {
      "@myorg/ui":   ["libs/ui/src/index.ts"],
      "@myorg/core": ["libs/core/src/public-api.ts"]
    }
  }
}

Paths that point to a dist/ directory are automatically resolved to the source src/index.ts or src/public-api.ts sibling.


Commands

analyze — report dead exports

ngx-phantom analyze [path]

Scans all .ts files in the workspace and reports every symbol that is publicly exported from a library barrel but never imported by any consumer.

ngx-phantom ▸ Analyze
──────────────────────────────────────────────────
  Discovering workspace... found 24 libraries
  Analyzing...
  Done in 0.32s

Found 12 dead export(s) across 5 libraries:

  @myorg/sort-pipe (1 dead)
    ✗ SortPipe    libs/sort-pipe/src/lib/sort.pipe.ts:1

  @myorg/button (1 dead)
    ✗ ButtonType  libs/button/src/lib/models/button-type.ts:3
  ...

  87 exports analyzed across 24 libraries

  Run with --prune to automatically remove dead exports from barrel files.

Flags

| Flag | Default | Description | |---|---|---| | --root | . | Workspace root (where tsconfig.base.json lives) | | --format | text | Output format: text or json | | --exclude-tests | false | Exclude *.spec.ts files from consumer analysis | | --workers | 2×CPU | Number of parallel file-scan workers | | --fail | false | Exit with code 1 if dead exports are found (CI mode) | | --verbose | false | Print resolved library paths and worker count |

JSON output

ngx-phantom analyze --format=json
{
  "summary": {
    "totalDeadExports": 12,
    "totalExports": 87,
    "totalLibraries": 24,
    "skippedLibraries": ["@myorg/utils"]
  },
  "deadExports": [
    {
      "symbol": "SortPipe",
      "library": "@myorg/sort-pipe",
      "sourceFile": "libs/sort-pipe/src/lib/sort.pipe.ts",
      "line": 1
    }
  ]
}

CI integration

# Fail the pipeline if any dead exports are found
ngx-phantom analyze --exclude-tests --fail

prune — remove dead exports from barrel files

ngx-phantom prune [path]

Runs the same analysis as analyze, then rewrites each library's barrel file (index.ts / public-api.ts) to remove export statements whose symbols have zero consumers.

  • Named exports (export { Foo } from './foo') are removed or trimmed safely.
  • Multi-line exports are handled correctly.
  • Wildcard exports (export * from './foo') are left untouched and reported separately.

Always preview first with --dry-run:

ngx-phantom prune --dry-run
ngx-phantom ▸ Prune
──────────────────────────────────────────────────
  DRY RUN — no files will be modified

  Discovering workspace... found 24 libraries
  Analyzing...

  Found 12 dead export(s). Pruning barrel files...

  Would prune 1 export(s) from @myorg/sort-pipe
  Would prune 1 export(s) from @myorg/button
  ...

  Done in 0.31s — would remove 11 export(s)

Apply the changes:

ngx-phantom prune

Flags

| Flag | Default | Description | |---|---|---| | --root | . | Workspace root | | --dry-run | false | Preview changes without writing files | | --exclude-tests | false | Exclude *.spec.ts files from consumer analysis | | --workers | 2×CPU | Number of parallel file-scan workers |


explain — understand why a symbol is dead

ngx-phantom explain --lib <library> --symbol <symbol>

Shows a detailed breakdown for a single exported symbol: which files import from the library, which symbols they consume, and whether the specific symbol has any consumers. Use this to verify a reported dead export before pruning.

ngx-phantom explain --lib @myorg/sort-pipe --symbol SortPipe
ngx-phantom ▸ Explain
──────────────────────────────────────────────────
  Library : @myorg/sort-pipe
  Symbol  : SortPipe

  Export found: SortPipe
  Defined at : libs/sort-pipe/src/lib/sort.pipe.ts:1

  @myorg/sort-pipe is imported by 4 file(s):
    apps/my-app/src/app/app.module.ts
    apps/other-app/src/app/app.module.ts
    ...

  Symbols consumed from @myorg/sort-pipe (1):
    SortPipeModule

  ✗ "SortPipe" is exported but NEVER imported by any consumer.
  Similar symbols that ARE consumed: SortPipeModule

Flags

| Flag | Default | Description | |---|---|---| | --root | . | Workspace root | | --lib | required | Library name, e.g. @myorg/ui | | --symbol | required | Symbol name, e.g. ButtonComponent | | --exclude-tests | false | Exclude *.spec.ts files | | --workers | 2×CPU | Number of parallel file-scan workers |


How it works

  1. Workspace discovery — reads tsconfig.base.json and extracts all paths entries. Paths pointing to dist/ directories are resolved to the source src/index.ts or src/public-api.ts.

  2. Export parsing — for each library entry point, parses all export statements:

    • export { Foo, Bar } from './path'
    • export type { Foo } from './path'
    • export * from './path' — recursively resolved
    • export * as Namespace from './path'
    • Direct declarations (export class Foo, export const foo, etc.) when resolving wildcards
  3. Import scanning — walks every .ts file in the workspace (excluding node_modules, dist, .git, .angular, coverage) and records all import statements referencing known library paths:

    • Named imports: import { Foo } from '@myorg/ui'
    • Type imports: import type { Foo } from '@myorg/ui'
    • Namespace imports: import * as X from '@myorg/ui' (marks entire lib as opaque)
    • Dynamic imports: import('@myorg/ui')
    • Subpath imports: import { Foo } from '@myorg/ui/testing' (mapped to @myorg/ui)
    • Multi-line imports are handled correctly
  4. Dead export analysis — symbols exported by a library but absent from any consumer's import set are reported as dead. Libraries with namespace imports (import * as X) are skipped entirely since consumption cannot be determined statically.

  5. Pruning — rewrites barrel files in-place using regex replacement that handles single-line and multi-line export { ... } blocks. Wildcard exports are intentionally left untouched.


Common patterns explained

NgModule wrapper pattern

A common source of confusion: many Angular libraries export both the directive/pipe class and an NgModule that declares it:

// index.ts
export { SortPipe } from './lib/sort.pipe';
export { SortPipeModule } from './lib/sort-pipe.module';

If all consumers import SortPipeModule but nobody imports SortPipe directly, ngx-phantom will correctly flag SortPipe as a dead export. The class is used at runtime via the module declaration, but it is not part of the public TypeScript API.

Use explain to verify:

ngx-phantom explain --lib @myorg/sort-pipe --symbol SortPipe
# Shows: 4 files import from @myorg/sort-pipe, all consume SortPipeModule

If you are migrating to standalone components you can safely prune the class export alongside removing the module.

Same symbol exported from multiple libraries

If SymbolA is exported from both @myorg/lib-a and @myorg/lib-b, but consumers only import it from @myorg/lib-b, then SymbolA from @myorg/lib-a will be flagged as dead. This is correct — the public API of @myorg/lib-a contains a redundant re-export.

Namespace imports

Libraries consumed via import * as X from '@myorg/lib' cannot be analyzed statically. ngx-phantom skips them entirely and lists them under "Skipped libraries" in the report.


Limitations

  • Namespace imports (import * as X from '@lib') prevent per-symbol analysis for that library.
  • Wildcard exports (export * from './path') are not modified by prune — reported separately for manual review.
  • Template-only usage — if a component or directive is used only in an Angular template (not imported in TypeScript), it will still appear in the import statement of the component's .ts file, so this is not an issue in practice.
  • Dynamic string importsimport('@myorg/' + name) cannot be resolved statically and will be missed.
  • Type-only consumers — symbols imported only as TypeScript types are counted as consumers (by design — removing a type export is still a breaking API change).

History & Go → Zig rewrite

ngx-phantom was originally written in Go. It has since been fully rewritten in Zig for significantly lower resource usage and a smaller distribution footprint.

Benchmarked on a real Angular monorepo (~150 libraries, ~4,000 TypeScript files):

| Metric | Go | Zig | Improvement | |---|---|---|---| | Binary size | 5.9 MB | 288 KB | 95% smaller | | Wall-clock time | 271.9 ms | 226.3 ms | ~17% faster | | CPU time (user) | 1,017 ms | 63.7 ms | 16× less CPU | | Source lines | ~2,000 | 1,931 | comparable |