How shaking works#
resolve ─► parse ─► analyze ─► link ─► shake ─► emit
modules AST scopes, cross- reach- chunks with
(no exec) effects module ability original lines
- Resolve. Starting from the entry script, shakeout follows every
import, including ones inside functions, and locates modules the way Python would, without executing anything. Modules under your source roots are bundled. The standard library and other packages are external. - Parse and analyze. Each module is split into top-level statements. For each one, shakeout records the names it binds, the names it reads, using exact Python scoping rules, the modules it may import, and whether running it has side effects.
- Link. References like
pkg.sub.funcare followed through imports and re-exports to the statement that defines them. - Shake. Starting from the entry point, every statement with side effects in an imported module, and every export or keep, is marked live, then everything live code references. A module that nothing live imports is dropped entirely.
- Emit. Kept statements are written with their original line numbers, and kept imports are
rewritten to drop unused names. Removed names get a small
__getattr__that explains them if they are reached at runtime.
Per-module isolation#
Unlike JavaScript bundlers, shakeout doesn't merge modules into one namespace ("scope
hoisting"). Every module keeps its own globals, __name__, import order and circular-import
behavior. That costs a little size and buys exact semantics and honest tracebacks.
What is shaken#
The unit of removal is a top-level statement: a function, class, assignment or import. Methods inside a kept class stay. Whether an instance's method is called can't be decided statically.