Route to chunk
The two bundlers expose this in opposite ways, which is why crust keeps one adapter interface and two implementations rather than branching inline.
On webpack, the client reference manifest holds the whole app’s client module table - the manifest
emitted for
/dashboard lists chunks belonging to /. Unioning it would attribute every client
component to every route, so crust intersects it against the route’s own convention chunk instead.
app-build-manifest.json does not exist in Next 16, and build-manifest.json carries no App Router
route data at all. It is still present in Next 15 - the manifest set genuinely differs across the
two supported majors.Chunk to source
Byte ranges are mapped through source maps. Each mapping owns the bytes from its own position to the next; regions no mapping covers are counted as unattributed rather than smeared onto the nearest source. Measured on the fixture apps:
webpack’s shortfall is almost entirely one file -
polyfills-*.js ships with no map. Excluding it,
webpack reaches ~99.6%.
Identifying your code
Deciding whether a mapped source is yours is not a prefix check. Next ships source maps that point into its ownsrc/, so a !path.includes('node_modules')
test reports Next’s router and segment cache as your application code. The bundlers also disagree
on anchoring: webpack emits webpack://_N_E/./components/x.tsx, Turbopack emits
turbopack:///[project]/… where [project] is the inferred workspace root, not the app directory.
crust resolves a source by asking whether it names a file that actually exists in the
workspace, matching in both directions - the source path can be shorter or longer than the indexed
path. Ambiguity resolves to nothing, never to a guess.
Buckets
Every byte lands in exactly one of:- your modules - resolved to a workspace file
- dependencies - resolved to a package name, with the pnpm virtual store segment skipped
- outside project - mapped, but to a path carrying no package name (Next’s own
../../src/…) - unattributed - no mapping covers it, or the chunk shipped without a map
Route to source line
Byte attribution answers what shipped. Cause chains answer why the route reached it. crust preserves binding names while walking components, imports, and first-party calls:Client cost and shared causes
Client boundaries are charged for their reachable subtree. Barrel cost includes only modules that become unreachable when that barrel is removed. Shared layouts, boundaries, packages, barrels, chunks, and call sites are grouped once with every affected route, so one root cause does not become twenty repeated findings. Across builds,crust diff compares three byte-attribution axes directly:
- packages - dependency movement grouped over every affected route
- client boundaries - movement in the attributed subtree crossing into the client
- barrels - movement in modules that reach a route only through the barrel import