svelte‑shaker
whole-program · source-level · Svelte 5

Your bundler ships component code your app never runs. svelte-shaker deletes it.

It reads how your whole app calls every component, folds props that never vary, and deletes the dead branches, CSS, and modules behind them — at the source, before the Svelte compiler. When a change can't be proven safe, the code is left untouched.

Try it

The engine runs in your browser. The app only ever renders <Button variant="primary"> — so loading folds away, the <Spinner> it guarded drops from the bundle, and .btn-danger can never match. Edit either side of the story and watch the output update.

compiling…
input
shaken output removed added

How it works

  1. Crawl every call site. From your entry, it records the value passed to every prop at every <Child/> — a literal, a default, or "unknown".
  2. Decide what's reachable. Props that never vary fold to their constant; only branches and CSS their values can reach survive. Folds cascade to a whole-program fixpoint.
  3. Slim the source, then compile. Dead code is deleted from the .svelte files, and Svelte compiles only what's left.

Sound by construction: anything unprovable (spreads, bind:, dynamic components…) bails and ships unchanged. Build-time only, as a Vite / Rollup plugin — entries is where the crawl starts, and it must cover every call site in the app.

# vite.config.ts
import { shaker } from 'svelte-shaker/vite';
plugins: [shaker({ entries: ['src'] }), svelte()]

Options

Every key is optional. Only entries matters for most apps — it must cover every call site. The rest tune speed and edge cases.

entries
Vite root Dirs the component crawl starts from; must reach every call site. Not an include filter.
preserve
[] Components whose prop interface must stay as written — a consumer the shake can't see passes props. Over-listing errs safe.
devOnly
test/mock/story globs Files that never ship; their call sites stop counting. Replaces the default — spread DEFAULT_DEV_ONLY to extend.
monomorphize
on Per-call-site specialization behind a measured net-win gate; never bloats. false for faster builds.
engine
'auto' Rust/WASM engine when loadable, else JS. Byte-identical output either way.
parser
'rsvelte' Soundness-neutral; 'svelte' is the fallback escape hatch.
dev
false Dev is a pass-through; opt into 'incremental' or 'coarse' dev shaking.
verbose
false Print a per-file size breakdown after the build.

Full reference: svelte-shaker options ↗