Code Splitting Widget

A compact panel that visualizes a JavaScript bundle's chunk composition. Pass in a flat list of chunks tagged with lazy?: boolean; the widget partitions them into an Initial tier (render-blocking) and a Deferred tier (lazy-loaded), stacks each tier into a proportional bar, and surfaces tier and chunk totals. Toggle a chunk between eager and lazy to watch the bars resize.

Preview

Bundle composition

Toggle vendor.js between eager and lazy to watch the tier bars resize.

Initial138 KB·36%
  • core.js110 KB
  • vendor.js28 KB
Deferred247 KB·64%
  • dashboard.js/dashboard92 KB
  • settings.js/settings74 KB
  • billing.js/billing81 KB
Customize
Chunk sizes (KB)
110KB
28KB
92KB
74KB
81KB
Options

Installation

npx shadcn@latest add https://craftbits.dev/r/code-splitting-widget.json

Usage

import { CodeSplittingWidget } from "@craft-bits/core";
 
<CodeSplittingWidget
  title="Bundle composition"
  chunks={[
    { id: "core", name: "core.js", size: 110 },
    { id: "vendor", name: "vendor.js", size: 28 },
    { id: "dashboard", name: "dashboard.js", size: 92, lazy: true, route: "/dashboard" },
    { id: "settings", name: "settings.js", size: 74, lazy: true, route: "/settings" },
  ]}
/>

Render the "before" monolithic case by passing a single chunk:

<CodeSplittingWidget
  chunks={[{ id: "main", name: "main.js", size: 385 }]}
/>

Anatomy

  • Header. Optional title (renders with the cb-label style) and a description sub-line. Omit both for a chromeless panel.
  • Tier. One rounded card per tier. The tier label sits left; the tier total and percent share of the whole bundle sit right.
  • Bar. A stacked horizontal bar where each segment's width is proportional to that chunk's share of its tier. Initial tier uses --cb-accent; deferred uses the accent at 40% alpha so the eye reads it as the lower-priority tier.
  • Chunk row. One row per chunk under the bar — name + optional route + size in tabular-nums so columns line up.

Understanding the component

  1. Partition. Chunks with lazy: true flow into the deferred tier; the rest land in the initial tier. The two tiers render in fixed order — Initial above Deferred — so the eye reads top-down as "what blocks render first".
  2. Proportional widths. Within a tier, each segment's width is chunk.size / tierTotal — the bar visualizes the internal split, not the absolute byte count. The tier total and percent share of the bundle live in the header so you can read both magnitudes at once.
  3. Layout animation. Each chunk segment uses Motion's layout prop and the SPRINGS.snap token. When you toggle a chunk between lazy and eager, the bars resize smoothly. Under prefers-reduced-motion the layout animation short-circuits and the bars snap.
  4. Empty tier. If no chunk is deferred, the deferred tier renders at 60% opacity with a "no chunks in this tier" placeholder — the panel stays the same height so toggling doesn't reflow surrounding content.

Props

PropTypeDefaultDescription
chunksCodeSplittingWidgetChunk[]requiredOrdered list of chunks.
titleReactNodeOptional heading above the panel.
descriptionReactNodeOptional sub-headline under the title.
unitstring'KB'Display unit suffix for sizes.
initialLabelReactNode'Initial'Label for the render-blocking tier.
deferredLabelReactNode'Deferred'Label for the lazy tier.
hideTotalsbooleanfalseHide the per-tier total + percent.
headingAs'h2' | 'h3' | 'h4''h3'Tag for the title element.
classNamestringMerged onto the root via cn().

CodeSplittingWidgetChunk

FieldTypeDescription
idstringStable identifier.
nameReactNodeVisible chunk label (e.g. core.js).
sizenumberChunk size in the widget's unit.
lazybooleanWhen true, the chunk lands in the deferred tier.
routeReactNodeOptional route or trigger annotation.

Accessibility

  • The wrapper is a <section> with data-cb-edu="code-splitting-widget". Tier cards expose data-cb-tier="initial" | "deferred" and data-cb-empty="true | false".
  • Chunk lists are role="list" with aria-live="polite" so size updates are announced without interrupting the user.
  • Tier totals carry an aria-label like tier total 110 KB so the verdict is conveyed without depending on color.
  • The bar segments themselves are decorative (aria-hidden) — the chunk list under each bar is the source of truth for assistive tech.
  • Layout animations short-circuit under prefers-reduced-motion.

Credits

  • Extracted from: terminal-dreams (src/components/frontend-design/sdp-web-performance/ui/CodeSplittingWidget.tsx). The original was wired to a fixed-shape PerfContext (a single codeSplitPct slider that split a hardcoded 385 KB main bundle into core.js and routes.js). This rewrite drops the context dependency and the two-bucket cap — consumers pass an arbitrary list of { id, name, size, lazy?, route? } chunks so the widget can model any bundle, from a one-route SPA to a multi-route app with vendor and shared chunks.