Add bundle-viz app: interactive Recharts Treemap/Sunburst bundle size visualization#7037
Conversation
…ging - scripts/visualize-bundle.ts: generates interactive HTML report showing which source files contribute how much kB to the tree-shaken bundle - scripts/trace-bundle.ts: shows import chain(s) why component A causes component B to be in the bundle - scripts/treeshaking.ts: extract helpers, add getModuleGraph() function - package.json: add visualize-bundle and trace-bundle npm scripts - .gitignore: ignore bundle-visualization.html output file Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
There was a problem hiding this comment.
@copilot How hard would it be to have the bundle visualisation represented by Recharts treemap chart? Or sunburst chart?
…tion - bundle-viz/: standalone Vite+React app that shows bundle sizes using Recharts Treemap and SunburstChart, colour-coded by directory - scripts/generate-bundle-data.ts: builds tree-shaken bundle and writes hierarchical JSON (grouped by source directory) to bundle-viz/src/bundle-data.json - package.json: adds generate-bundle-data npm script - .gitignore: ignores bundle-viz/node_modules and bundle-viz/dist - eslint.config.mjs: excludes bundle-viz from root eslint Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7037 +/- ##
=======================================
Coverage 90.12% 90.13%
=======================================
Files 526 526
Lines 39242 39254 +12
Branches 5438 5438
=======================================
+ Hits 35368 35380 +12
Misses 3865 3865
Partials 9 9 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Bundle ReportChanges will increase total bundle size by 1.19kB (0.02%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: recharts/bundle-cjsAssets Changed:
view changes for bundle: recharts/bundle-umdAssets Changed:
view changes for bundle: recharts/bundle-es6Assets Changed:
view changes for bundle: recharts/bundle-treeshaking-cartesianAssets Changed:
|
|
Staging Deployment Details
These deployments will remain available for 30 days. To update snapshots: Comment |
|
Staging Deployment Details
These deployments will remain available for 30 days. To update snapshots: Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughAdds bundle visualization and debugging tools for tree-shaking analysis. Introduces a Vite-based React visualization app showing bundle composition, scripts to generate bundle data and trace import paths, and refactors tree-shaking tests to use dynamic group-based expectations instead of hardcoded known issues. Changes
Sequence DiagramsequenceDiagram
participant User
participant CLI as generate-bundle-data<br/>Script
participant Bundler as Rollup<br/>Bundler
participant Graph as Module<br/>Graph Builder
participant FileSystem as File<br/>System
participant VisualizationApp as Bundle Visualization<br/>App
participant Browser
User->>CLI: run generate-bundle-data [components]
CLI->>Bundler: treeshake selected components<br/>with temporary entry
Bundler->>Graph: emit module metadata
Graph->>Graph: collect rendered sizes<br/>per module
Graph-->>Bundler: module graph data
Bundler-->>CLI: bundled ES module output
CLI->>CLI: parse output, build hierarchical tree<br/>insert modules, accumulate sizes
CLI->>CLI: compute totalSize, moduleCount<br/>assign stable colors to groups
CLI->>FileSystem: write bundle-data.json
User->>VisualizationApp: npm run dev
VisualizationApp->>FileSystem: load bundle-data.json
FileSystem-->>VisualizationApp: hierarchical data
VisualizationApp->>VisualizationApp: render Treemap or Sunburst<br/>apply colors, compute layout
VisualizationApp-->>Browser: visualize bundle composition<br/>interactive chart + stats
Browser-->>User: display module sizes,<br/>drill down capabilities
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (8)
.gitignore (1)
37-42: Consider gitignoringbundle-viz/src/bundle-data.json.
scripts/generate-bundle-data.tswrites its output directly tobundle-viz/src/bundle-data.json. Without a gitignore entry, developers who runnpm run generate-bundle-datawill have that generated file show up as a dirty working tree and could accidentally commit real bundle data alongside a PR. If the intent is to ship only a committed placeholder and treat generated output as local-only, adding the pattern prevents accidental commits.💡 Proposed addition
# bundle-viz app: dependencies and build artifacts bundle-viz/node_modules bundle-viz/dist + +# bundle-viz generated data (run `npm run generate-bundle-data` locally) +bundle-viz/src/bundle-data.json🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.gitignore around lines 37 - 42, Add the generated bundle-viz/src/bundle-data.json to .gitignore so running scripts/generate-bundle-data.ts (which writes that file) doesn't leave a dirty working tree or risk accidental commits; update the .gitignore by adding the pattern "bundle-viz/src/bundle-data.json" (or a broader "bundle-viz/src/*.json" if preferred) alongside the existing bundle-viz entries to ensure generated bundle data files are ignored.scripts/treeshaking-groups/BarStack.ts (1)
1-1: Remove orphaned comment.
/** The component being imported */on Line 1 is not attached to any declaration and has no equivalent in any othertreeshaking-groupsfile. It looks like a leftover template artifact.🧹 Suggested cleanup
-/** The component being imported */ - /** * All components expected to appear in the bundle when importing this component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/treeshaking-groups/BarStack.ts` at line 1, Remove the orphaned leading JSDoc comment `/** The component being imported */` from the top of scripts/treeshaking-groups/BarStack.ts; this comment is not attached to any declaration (no matching symbol like a function/class/variable in BarStack) and should simply be deleted to clean up the file.bundle-viz/vite.config.ts (1)
1-20: LGTM — optional: preferimport.meta.urlover__dirnamefor ESM portability.The difference between
__dirnameandimport.meta.urlin Vite configs is that modern Vite projects use ES modules where__dirnameisn't natively available. Vite does inject CJS shims when processing its config file so this works today, but the idiomatic ESM-safe alternative avoids the dependency on Vite's shim injection:♻️ Optional: modern ESM-safe equivalent
-import { resolve } from 'path'; +import { fileURLToPath, URL } from 'node:url'; export default defineConfig({ resolve: { alias: { - recharts: resolve(__dirname, '../src'), + recharts: fileURLToPath(new URL('../src', import.meta.url)), }, },Also note that
bundle-viz/tsconfig.jsonmapsrechartsto../src/index.ts(explicit file entry) while this config maps to../src(directory). The manual approach requires you to sync aliases betweentsconfig.jsonandvite.config.ts; if you ever add more aliases, you'll need to update both files. Both resolve identically forimport from 'recharts'in practice, but keeping them consistent avoids confusion.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bundle-viz/vite.config.ts` around lines 1 - 20, Replace the CommonJS __dirname usage with an ESM-safe import.meta.url-based path for the recharts alias: change the alias value from resolve(__dirname, '../src') to resolve(new URL('../src', import.meta.url).pathname) (or derive __dirname via fileURLToPath(import.meta.url) if you prefer), and ensure this alias remains consistent with the tsconfig.json mapping for recharts (currently ../src/index.ts) so both configs resolve the same module entry.scripts/treeshaking-groups/CartesianGrid.ts (1)
1-1: Orphan comment — appears to be a leftover template fragment.Line 1 (
/** The component being imported */) doesn't annotate any declaration. The meaningful documentation is already in the block comment on lines 3–6. Consider removing it.Proposed fix
-/** The component being imported */ - /** * All components expected to appear in the bundle when importing this component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/treeshaking-groups/CartesianGrid.ts` at line 1, Remove the orphan top-of-file JSDoc comment "/** The component being imported */" which does not annotate any declaration and duplicates existing documentation for CartesianGrid; delete that stray comment at the very start of CartesianGrid.ts so only the meaningful block comment (lines documenting the CartesianGrid component) remains.bundle-viz/README.md (1)
40-47: Add a language identifier to the fenced code block.The markdownlint tool flags this block (MD040). Use
```textfor plain directory listings.Proposed fix
-``` +```text bundle-viz/ package.json standalone Vite + React app vite.config.ts aliases recharts → ../src so no build step is needed src/ App.tsx chart components and layout main.tsx React entry point bundle-data.json placeholder shipped in git; overwritten by npm run generate-bundle-data -``` +```🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bundle-viz/README.md` around lines 40 - 47, Markdown linting flags the fenced code block under the bundle-viz directory listing for missing a language identifier; edit the README.md section showing the directory tree and change the opening fence from ``` to ```text (i.e., add the "text" language identifier for that directory listing block) so the block becomes ```text ... ``` and satisfies MD040.scripts/generate-bundle-data.ts (1)
111-111: Consider ensuring the output directory exists before writing.If
bundle-viz/src/is ever missing (e.g., after a partial clone or clean), this will throw. A one-liner guard would make the script more robust.Proposed fix
+ fs.mkdirSync(path.dirname(outFile), { recursive: true }); fs.writeFileSync(outFile, JSON.stringify(bundleData, null, 2), 'utf-8');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/generate-bundle-data.ts` at line 111, The script currently calls fs.writeFileSync(outFile, ...) which will throw if the target directory (e.g., bundle-viz/src/) is missing; before calling fs.writeFileSync, ensure the parent directory exists by creating it (use the directory from path.dirname(outFile)) with recursive directory creation (e.g., fs.mkdirSync(..., { recursive: true })) so the write is robust even after a clean/partial clone.scripts/treeshaking.ts (1)
165-176: Static analysis ReDoS warnings are low-risk here, butescapeRegExpwould be a cheap safety net.The
namevalues originate fromProjectDocReader.getAllRuntimeExportedNames()— valid JS identifiers that cannot contain regex metacharacters. So the ReDoS concern flagged by ast-grep is a false positive in practice. That said, sincematchComponentNamesInBundleis a public export, a simple escape (or switching tosourceCode.includes(...)with template strings) would make it defensively safe without a performance cost.Optional hardening
+function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + export function matchComponentNamesInBundle(sourceCode: string, componentNames: ReadonlyArray<string>): Set<string> { return new Set( componentNames.filter(name => { + const escaped = escapeRegExp(name); return ( - new RegExp(`var ${name} `).test(sourceCode) || - new RegExp(`const ${name} `).test(sourceCode) || - new RegExp(`function ${name}\\(`).test(sourceCode) || - new RegExp(`class ${name}\\s+{`).test(sourceCode) + new RegExp(`var ${escaped} `).test(sourceCode) || + new RegExp(`const ${escaped} `).test(sourceCode) || + new RegExp(`function ${escaped}\\(`).test(sourceCode) || + new RegExp(`class ${escaped}\\s+{`).test(sourceCode) ); }), ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/treeshaking.ts` around lines 165 - 176, matchComponentNamesInBundle builds RegExp objects from unescaped component names which triggers static ReDoS warnings; defensively escape the identifier before constructing the RegExp (or replace the RegExp checks with simple string checks using sourceCode.includes for patterns like `var ${name} `, `const ${name} `, `function ${name}(` and `class ${name} `). Update matchComponentNamesInBundle to call a small escape function (e.g. escapeRegExp) on the name or switch each test to sourceCode.includes so inputs from ProjectDocReader.getAllRuntimeExportedNames are safely handled and the ast-grep warning is eliminated.scripts/trace-bundle.ts (1)
50-82: BFS can explore exponentially many paths on dense graphs.The search clones and enqueues a new path array for every edge and only prunes cycles within a single path (line 75). On a heavily interconnected module graph this could cause the queue to grow very large before hitting
maxResults. For a dev tool this is likely acceptable givenMAX_DEPTH = 25andmaxResults = 8, but if performance is ever an issue, consider a level-limited BFS with a global visited set for single-shortest-path, then re-running with relaxed constraints for alternate paths.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/trace-bundle.ts` around lines 50 - 82, findShortestPaths currently clones and enqueues a full path for every edge which can explode on dense graphs; instead implement a two-phase approach inside findShortestPaths: (1) do a level-limited BFS using a global visited/distance map (use MAX_DEPTH and stop when endId is discovered) to compute shortest distance(s) from startId to every node without storing whole paths, and (2) perform a bounded backtracking/DFS from startId that uses the distance map to only follow edges that lead closer to endId and collect up to maxResults paths (this avoids enqueuing every intermediate path and reduces queue growth caused by pushing [...currentPath, next] for each importedId). Use the existing names nodeById, queue/MAX_DEPTH, node.importedIds and maxResults to locate where to change logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@bundle-viz/src/App.tsx`:
- Around line 137-140: The formatTooltip function accepts Array<number|string>
but currently calls Number(value) which yields NaN for arrays; update
formatTooltip to detect when value is an array (Array.isArray(value)), extract a
sensible numeric candidate (e.g., value[0]) or find the first element that
converts to a finite number, coerce that to Number, and fall back to 0 (or a
safe default) if conversion fails, then use that numeric value for the existing
(num/1024).toFixed(2) kB formatting so Treemap/Sunburst and other callers are
protected from NaN.
- Around line 70-84: CustomTreemapCell is currently deriving fill from COLORS
using index, so cell colors won't match the directory-based legend produced by
colorize()/groupColor; update CustomTreemapCell to accept and use the injected
fill prop instead of re-deriving by index: add fill?: string to the props
signature (or read props.fill), then compute const fill = props.fill ??
COLORS[index % COLORS.length] ?? COLORS[0] (so existing behavior remains as a
fallback), and remove the unconditional COLORS[index % COLORS.length] usage;
this ensures the treemap uses the fill produced by colorize() and matches
Legend/groupColor.
In `@bundle-viz/src/bundle-data.json`:
- Around line 1052-1067: The bundle tree includes a temp Rollup entry because
generate-bundle-data.ts doesn't exclude modules located in the OS temp dir,
causing an empty top-level name; update the module-filtering logic in
generate-bundle-data.ts (the place that iterates over the modules/modulesList
and builds the children tree, e.g., where modules are reduced/converted into a
tree) to skip any module whose resolved path startsWith(os.tmpdir()) or whose
top-level directory name is empty (e.g., path.split(path.sep)[0] === ''), so
these temporary entry files are filtered out before constructing the children
array.
In `@package.json`:
- Line 51: The package.json contains a broken npm script "visualize-bundle"
referencing a non-existent scripts/visualize-bundle.ts; remove the
"visualize-bundle" entry entirely or replace its value to point to the correct
tool (for example run the bundle-viz app) so the scripts section only references
existing commands like "trace-bundle" and "generate-bundle-data" and/or a valid
bundle-viz invocation; update the "visualize-bundle" key accordingly or delete
the key to prevent runtime failures.
In `@scripts/treeshaking-groups/Tooltip.ts`:
- Around line 1-12: The exported array expectedInBundle is currently typed as
mutable string[]; make it a readonly literal tuple by appending "as const" to
the array literal so downstream code that reads the exact literal types (e.g.,
the consumer in index.ts) gets the correct types; update the declaration of
expectedInBundle to close the array with "as const" to match other
treeshaking-groups files.
In `@scripts/treeshaking-groups/XAxis.ts`:
- Around line 1-2: Remove the dangling JSDoc comment `/** The component being
imported */` at the top of XAxis.ts since the annotated symbol (`component =
'XAxis' as const`) was removed; simply delete this orphaned comment so the file
no longer contains misleading leftover documentation.
In `@scripts/treeshaking-groups/YAxis.ts`:
- Around line 1-6: Remove the orphaned top-level JSDoc that references the
removed "component" export and update the comment above the expectedInBundle
array in YAxis.ts to remove or rephrase the misleading "unexpectedly" phrasing
about CartesianAxis (or delete the explanatory sentence entirely); locate the
comment that references expectedInBundle and CartesianAxis and either make it a
factual description of what is included in expectedInBundle or delete the
outdated explanation so the file only documents the current expectedInBundle
contents.
In `@scripts/treeshaking-groups/ZAxis.ts`:
- Line 1: The expectedInBundle array in ZAxis.ts is missing the component name;
update the exported constant expectedInBundle (in ZAxis.ts) to include the
string 'ZAxis' alongside 'getNiceTickValues' so it matches the pattern used by
other treeshaking-groups files (i.e., include 'ZAxis' in the tuple returned by
expectedInBundle).
In `@scripts/treeshaking.test.ts`:
- Line 33: The test uses Set.prototype.symmetricDifference
(allBundledComponents.symmetricDifference(knownExpectedBundle)) which requires
Node ≥22; change to a Node 18-compatible approach by replacing that call:
compute items present in allBundledComponents but not in knownExpectedBundle and
items present in knownExpectedBundle but not in allBundledComponents, then join
those two lists; update the `Importing ${componentName}... Diff: [...]`
expression to use this computed concatenation instead, or alternatively update
the package.json `engines.node` to ">=22" if you prefer to require a newer Node
version. Ensure you reference the same variables: allBundledComponents and
knownExpectedBundle and produce the same string format as before.
---
Nitpick comments:
In @.gitignore:
- Around line 37-42: Add the generated bundle-viz/src/bundle-data.json to
.gitignore so running scripts/generate-bundle-data.ts (which writes that file)
doesn't leave a dirty working tree or risk accidental commits; update the
.gitignore by adding the pattern "bundle-viz/src/bundle-data.json" (or a broader
"bundle-viz/src/*.json" if preferred) alongside the existing bundle-viz entries
to ensure generated bundle data files are ignored.
In `@bundle-viz/README.md`:
- Around line 40-47: Markdown linting flags the fenced code block under the
bundle-viz directory listing for missing a language identifier; edit the
README.md section showing the directory tree and change the opening fence from
``` to ```text (i.e., add the "text" language identifier for that directory
listing block) so the block becomes ```text ... ``` and satisfies MD040.
In `@bundle-viz/vite.config.ts`:
- Around line 1-20: Replace the CommonJS __dirname usage with an ESM-safe
import.meta.url-based path for the recharts alias: change the alias value from
resolve(__dirname, '../src') to resolve(new URL('../src',
import.meta.url).pathname) (or derive __dirname via
fileURLToPath(import.meta.url) if you prefer), and ensure this alias remains
consistent with the tsconfig.json mapping for recharts (currently
../src/index.ts) so both configs resolve the same module entry.
In `@scripts/generate-bundle-data.ts`:
- Line 111: The script currently calls fs.writeFileSync(outFile, ...) which will
throw if the target directory (e.g., bundle-viz/src/) is missing; before calling
fs.writeFileSync, ensure the parent directory exists by creating it (use the
directory from path.dirname(outFile)) with recursive directory creation (e.g.,
fs.mkdirSync(..., { recursive: true })) so the write is robust even after a
clean/partial clone.
In `@scripts/trace-bundle.ts`:
- Around line 50-82: findShortestPaths currently clones and enqueues a full path
for every edge which can explode on dense graphs; instead implement a two-phase
approach inside findShortestPaths: (1) do a level-limited BFS using a global
visited/distance map (use MAX_DEPTH and stop when endId is discovered) to
compute shortest distance(s) from startId to every node without storing whole
paths, and (2) perform a bounded backtracking/DFS from startId that uses the
distance map to only follow edges that lead closer to endId and collect up to
maxResults paths (this avoids enqueuing every intermediate path and reduces
queue growth caused by pushing [...currentPath, next] for each importedId). Use
the existing names nodeById, queue/MAX_DEPTH, node.importedIds and maxResults to
locate where to change logic.
In `@scripts/treeshaking-groups/BarStack.ts`:
- Line 1: Remove the orphaned leading JSDoc comment `/** The component being
imported */` from the top of scripts/treeshaking-groups/BarStack.ts; this
comment is not attached to any declaration (no matching symbol like a
function/class/variable in BarStack) and should simply be deleted to clean up
the file.
In `@scripts/treeshaking-groups/CartesianGrid.ts`:
- Line 1: Remove the orphan top-of-file JSDoc comment "/** The component being
imported */" which does not annotate any declaration and duplicates existing
documentation for CartesianGrid; delete that stray comment at the very start of
CartesianGrid.ts so only the meaningful block comment (lines documenting the
CartesianGrid component) remains.
In `@scripts/treeshaking.ts`:
- Around line 165-176: matchComponentNamesInBundle builds RegExp objects from
unescaped component names which triggers static ReDoS warnings; defensively
escape the identifier before constructing the RegExp (or replace the RegExp
checks with simple string checks using sourceCode.includes for patterns like
`var ${name} `, `const ${name} `, `function ${name}(` and `class ${name} `).
Update matchComponentNamesInBundle to call a small escape function (e.g.
escapeRegExp) on the name or switch each test to sourceCode.includes so inputs
from ProjectDocReader.getAllRuntimeExportedNames are safely handled and the
ast-grep warning is eliminated.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
bundle-viz/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (86)
.github/workflows/ci.yml.gitignorebundle-viz/README.mdbundle-viz/index.htmlbundle-viz/package.jsonbundle-viz/src/App.tsxbundle-viz/src/bundle-data.jsonbundle-viz/src/main.tsxbundle-viz/tsconfig.jsonbundle-viz/vite.config.tseslint.config.mjspackage.jsonscripts/generate-bundle-data.tsscripts/trace-bundle.tsscripts/treeshaking-groups/Area.tsscripts/treeshaking-groups/AreaChart.tsscripts/treeshaking-groups/Bar.tsscripts/treeshaking-groups/BarChart.tsscripts/treeshaking-groups/BarStack.tsscripts/treeshaking-groups/Brush.tsscripts/treeshaking-groups/CartesianAxis.tsscripts/treeshaking-groups/CartesianGrid.tsscripts/treeshaking-groups/ComposedChart.tsscripts/treeshaking-groups/Customized.tsscripts/treeshaking-groups/ErrorBar.tsscripts/treeshaking-groups/Funnel.tsscripts/treeshaking-groups/FunnelChart.tsscripts/treeshaking-groups/Line.tsscripts/treeshaking-groups/LineChart.tsscripts/treeshaking-groups/Pie.tsscripts/treeshaking-groups/PieChart.tsscripts/treeshaking-groups/PolarAngleAxis.tsscripts/treeshaking-groups/PolarGrid.tsscripts/treeshaking-groups/PolarRadiusAxis.tsscripts/treeshaking-groups/Radar.tsscripts/treeshaking-groups/RadarChart.tsscripts/treeshaking-groups/RadialBar.tsscripts/treeshaking-groups/RadialBarChart.tsscripts/treeshaking-groups/ReferenceArea.tsscripts/treeshaking-groups/ReferenceDot.tsscripts/treeshaking-groups/ReferenceLine.tsscripts/treeshaking-groups/Sankey.tsscripts/treeshaking-groups/Scatter.tsscripts/treeshaking-groups/ScatterChart.tsscripts/treeshaking-groups/SunburstChart.tsscripts/treeshaking-groups/Tooltip.tsscripts/treeshaking-groups/Treemap.tsscripts/treeshaking-groups/XAxis.tsscripts/treeshaking-groups/YAxis.tsscripts/treeshaking-groups/ZAxis.tsscripts/treeshaking-groups/index.tsscripts/treeshaking-known-issues/Area.tsscripts/treeshaking-known-issues/AreaChart.tsscripts/treeshaking-known-issues/Bar.tsscripts/treeshaking-known-issues/BarChart.tsscripts/treeshaking-known-issues/BarStack.tsscripts/treeshaking-known-issues/Brush.tsscripts/treeshaking-known-issues/CartesianAxis.tsscripts/treeshaking-known-issues/CartesianGrid.tsscripts/treeshaking-known-issues/ComposedChart.tsscripts/treeshaking-known-issues/ErrorBar.tsscripts/treeshaking-known-issues/Funnel.tsscripts/treeshaking-known-issues/FunnelChart.tsscripts/treeshaking-known-issues/Line.tsscripts/treeshaking-known-issues/LineChart.tsscripts/treeshaking-known-issues/Pie.tsscripts/treeshaking-known-issues/PieChart.tsscripts/treeshaking-known-issues/PolarAngleAxis.tsscripts/treeshaking-known-issues/PolarGrid.tsscripts/treeshaking-known-issues/PolarRadiusAxis.tsscripts/treeshaking-known-issues/Radar.tsscripts/treeshaking-known-issues/RadarChart.tsscripts/treeshaking-known-issues/RadialBar.tsscripts/treeshaking-known-issues/RadialBarChart.tsscripts/treeshaking-known-issues/ReferenceArea.tsscripts/treeshaking-known-issues/ReferenceDot.tsscripts/treeshaking-known-issues/ReferenceLine.tsscripts/treeshaking-known-issues/Sankey.tsscripts/treeshaking-known-issues/Scatter.tsscripts/treeshaking-known-issues/ScatterChart.tsscripts/treeshaking-known-issues/SunburstChart.tsscripts/treeshaking-known-issues/Treemap.tsscripts/treeshaking-known-issues/ZAxis.tsscripts/treeshaking-known-issues/index.tsscripts/treeshaking.test.tsscripts/treeshaking.ts
💤 Files with no reviewable changes (33)
- scripts/treeshaking-known-issues/RadarChart.ts
- scripts/treeshaking-known-issues/Funnel.ts
- scripts/treeshaking-known-issues/PolarAngleAxis.ts
- scripts/treeshaking-known-issues/ComposedChart.ts
- scripts/treeshaking-known-issues/BarChart.ts
- scripts/treeshaking-known-issues/PolarRadiusAxis.ts
- scripts/treeshaking-known-issues/Bar.ts
- scripts/treeshaking-known-issues/ZAxis.ts
- scripts/treeshaking-known-issues/SunburstChart.ts
- scripts/treeshaking-known-issues/ErrorBar.ts
- scripts/treeshaking-known-issues/Sankey.ts
- scripts/treeshaking-known-issues/LineChart.ts
- scripts/treeshaking-known-issues/ScatterChart.ts
- scripts/treeshaking-known-issues/ReferenceLine.ts
- scripts/treeshaking-known-issues/RadialBar.ts
- scripts/treeshaking-known-issues/CartesianAxis.ts
- scripts/treeshaking-known-issues/Brush.ts
- scripts/treeshaking-known-issues/Treemap.ts
- scripts/treeshaking-known-issues/Pie.ts
- scripts/treeshaking-known-issues/BarStack.ts
- scripts/treeshaking-known-issues/CartesianGrid.ts
- scripts/treeshaking-known-issues/Area.ts
- scripts/treeshaking-known-issues/RadialBarChart.ts
- scripts/treeshaking-known-issues/ReferenceDot.ts
- scripts/treeshaking-known-issues/FunnelChart.ts
- scripts/treeshaking-known-issues/Radar.ts
- scripts/treeshaking-known-issues/AreaChart.ts
- scripts/treeshaking-known-issues/index.ts
- scripts/treeshaking-known-issues/ReferenceArea.ts
- scripts/treeshaking-known-issues/PieChart.ts
- scripts/treeshaking-known-issues/PolarGrid.ts
- scripts/treeshaking-known-issues/Line.ts
- scripts/treeshaking-known-issues/Scatter.ts
| const COLORS = PALETTE; | ||
|
|
||
| function CustomTreemapCell(props: { | ||
| x?: number; | ||
| y?: number; | ||
| width?: number; | ||
| height?: number; | ||
| index?: number; | ||
| name?: string; | ||
| value?: number; | ||
| depth?: number; | ||
| root?: TreemapNode; | ||
| }) { | ||
| const { x = 0, y = 0, width = 0, height = 0, index = 0, name, value, depth = 0 } = props; | ||
| const fill = COLORS[index % COLORS.length] ?? COLORS[0]; |
There was a problem hiding this comment.
Treemap cell colors won't match the directory-based legend.
CustomTreemapCell uses COLORS[index % COLORS.length] (line 84), which colors cells by their render index — not by their top-level directory group. Meanwhile, colorize() carefully propagates a fill prop through the tree, and the Legend component displays directory-based colors from groupColor. The treemap cells will not visually correspond to the legend.
Use the fill prop that colorize() already injects instead of re-deriving from index:
Proposed fix
function CustomTreemapCell(props: {
x?: number;
y?: number;
width?: number;
height?: number;
index?: number;
+ fill?: string;
name?: string;
value?: number;
depth?: number;
root?: TreemapNode;
}) {
- const { x = 0, y = 0, width = 0, height = 0, index = 0, name, value, depth = 0 } = props;
- const fill = COLORS[index % COLORS.length] ?? COLORS[0];
+ const { x = 0, y = 0, width = 0, height = 0, name, value, depth = 0, fill: nodeFill } = props;
+ const fill = nodeFill ?? PALETTE[0];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const COLORS = PALETTE; | |
| function CustomTreemapCell(props: { | |
| x?: number; | |
| y?: number; | |
| width?: number; | |
| height?: number; | |
| index?: number; | |
| name?: string; | |
| value?: number; | |
| depth?: number; | |
| root?: TreemapNode; | |
| }) { | |
| const { x = 0, y = 0, width = 0, height = 0, index = 0, name, value, depth = 0 } = props; | |
| const fill = COLORS[index % COLORS.length] ?? COLORS[0]; | |
| const COLORS = PALETTE; | |
| function CustomTreemapCell(props: { | |
| x?: number; | |
| y?: number; | |
| width?: number; | |
| height?: number; | |
| index?: number; | |
| fill?: string; | |
| name?: string; | |
| value?: number; | |
| depth?: number; | |
| root?: TreemapNode; | |
| }) { | |
| const { x = 0, y = 0, width = 0, height = 0, name, value, depth = 0, fill: nodeFill } = props; | |
| const fill = nodeFill ?? PALETTE[0]; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bundle-viz/src/App.tsx` around lines 70 - 84, CustomTreemapCell is currently
deriving fill from COLORS using index, so cell colors won't match the
directory-based legend produced by colorize()/groupColor; update
CustomTreemapCell to accept and use the injected fill prop instead of
re-deriving by index: add fill?: string to the props signature (or read
props.fill), then compute const fill = props.fill ?? COLORS[index %
COLORS.length] ?? COLORS[0] (so existing behavior remains as a fallback), and
remove the unconditional COLORS[index % COLORS.length] usage; this ensures the
treemap uses the fill produced by colorize() and matches Legend/groupColor.
| function formatTooltip(value: number | string | Array<number | string>) { | ||
| const num = typeof value === 'number' ? value : Number(value); | ||
| return [`${(num / 1024).toFixed(2)} kB`, 'Size']; | ||
| } |
There was a problem hiding this comment.
formatTooltip doesn't handle the Array case gracefully.
When value is an Array, Number(value) produces NaN. Although in practice Treemap/Sunburst should pass a single number, the type signature explicitly accepts arrays. A quick guard would be safer:
Proposed fix
function formatTooltip(value: number | string | Array<number | string>) {
- const num = typeof value === 'number' ? value : Number(value);
+ const num = typeof value === 'number' ? value : Number(Array.isArray(value) ? value[0] : value);
return [`${(num / 1024).toFixed(2)} kB`, 'Size'];
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function formatTooltip(value: number | string | Array<number | string>) { | |
| const num = typeof value === 'number' ? value : Number(value); | |
| return [`${(num / 1024).toFixed(2)} kB`, 'Size']; | |
| } | |
| function formatTooltip(value: number | string | Array<number | string>) { | |
| const num = typeof value === 'number' ? value : Number(Array.isArray(value) ? value[0] : value); | |
| return [`${(num / 1024).toFixed(2)} kB`, 'Size']; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bundle-viz/src/App.tsx` around lines 137 - 140, The formatTooltip function
accepts Array<number|string> but currently calls Number(value) which yields NaN
for arrays; update formatTooltip to detect when value is an array
(Array.isArray(value)), extract a sensible numeric candidate (e.g., value[0]) or
find the first element that converts to a finite number, coerce that to Number,
and fall back to 0 (or a safe default) if conversion fails, then use that
numeric value for the existing (num/1024).toFixed(2) kB formatting so
Treemap/Sunburst and other callers are protected from NaN.
| { | ||
| "name": "", | ||
| "value": 383, | ||
| "children": [ | ||
| { | ||
| "name": "tmp", | ||
| "value": 383, | ||
| "children": [ | ||
| { | ||
| "name": "recharts-treeshake-entry-1771721895525-5vydbzhsc34.mjs", | ||
| "value": 383 | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } |
There was a problem hiding this comment.
Temp-file artifact leaking into the bundle tree
The entry at lines 1052–1067 has "name": "" and wraps a /tmp/recharts-treeshake-entry-…mjs node — this is the Rollup entry file created on disk by scripts/generate-bundle-data.ts during tree-shaking analysis. It leaks into the output because its resolved path falls outside the recharts source tree, producing an empty-string directory key.
It surfaces in both Treemap and Sunburst views as an unlabeled cell. The fix belongs in generate-bundle-data.ts: filter out modules whose resolved path starts with the OS temp directory (or whose top-level directory name is empty) before constructing the children tree.
🐛 Suggested filter in `generate-bundle-data.ts`
- // group modules by first path segment
+ // group modules by first path segment, skipping tmp/external artifacts
for (const [id, size] of moduleEntries) {
+ const rel = path.relative(srcRoot, id);
+ if (rel.startsWith('..') || path.isAbsolute(rel)) continue; // outside src root
const parts = rel.split(path.sep);
// …existing grouping logic
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bundle-viz/src/bundle-data.json` around lines 1052 - 1067, The bundle tree
includes a temp Rollup entry because generate-bundle-data.ts doesn't exclude
modules located in the OS temp dir, causing an empty top-level name; update the
module-filtering logic in generate-bundle-data.ts (the place that iterates over
the modules/modulesList and builds the children tree, e.g., where modules are
reduced/converted into a tree) to skip any module whose resolved path
startsWith(os.tmpdir()) or whose top-level directory name is empty (e.g.,
path.split(path.sep)[0] === ''), so these temporary entry files are filtered out
before constructing the children array.
There was a problem hiding this comment.
I will replace this whole app in next PR
| export const expectedInBundle = [ | ||
| 'Cross', | ||
| 'Curve', | ||
| 'DefaultTooltipContent', | ||
| 'DefaultZIndexes', | ||
| 'getNiceTickValues', | ||
| 'Global', | ||
| 'Rectangle', | ||
| 'Sector', | ||
| 'Tooltip', | ||
| 'ZIndexLayer', | ||
| ]; |
There was a problem hiding this comment.
as const is missing — type is inferred as string[] instead of a readonly literal tuple.
Every other treeshaking-groups/*.ts file closes the array with ] as const;. Without it, expectedInBundle has type string[] — mutable and lacking the literal element types that the consuming code in index.ts relies on. This is an inconsistency that can produce subtle type errors downstream.
🐛 Proposed fix
export const expectedInBundle = [
'Cross',
'Curve',
'DefaultTooltipContent',
'DefaultZIndexes',
'getNiceTickValues',
'Global',
'Rectangle',
'Sector',
'Tooltip',
'ZIndexLayer',
-];
+] as const;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const expectedInBundle = [ | |
| 'Cross', | |
| 'Curve', | |
| 'DefaultTooltipContent', | |
| 'DefaultZIndexes', | |
| 'getNiceTickValues', | |
| 'Global', | |
| 'Rectangle', | |
| 'Sector', | |
| 'Tooltip', | |
| 'ZIndexLayer', | |
| ]; | |
| export const expectedInBundle = [ | |
| 'Cross', | |
| 'Curve', | |
| 'DefaultTooltipContent', | |
| 'DefaultZIndexes', | |
| 'getNiceTickValues', | |
| 'Global', | |
| 'Rectangle', | |
| 'Sector', | |
| 'Tooltip', | |
| 'ZIndexLayer', | |
| ] as const; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/treeshaking-groups/Tooltip.ts` around lines 1 - 12, The exported
array expectedInBundle is currently typed as mutable string[]; make it a
readonly literal tuple by appending "as const" to the array literal so
downstream code that reads the exact literal types (e.g., the consumer in
index.ts) gets the correct types; update the declaration of expectedInBundle to
close the array with "as const" to match other treeshaking-groups files.
Description
Adds two on-demand developer tools for understanding and debugging tree-shaking and bundle composition:
bundle-viz/— standalone Vite + React apprecharts → ../srcvia Vite so no separate build step is requiredscripts/generate-bundle-data.ts— data pipeline feeding the appbundle-viz/src/bundle-data.jsonscripts/treeshaking.ts— extended withgetModuleGraph()(previous PR) supportinggenerate-bundle-dataUsage
Related Issue
Bundle size visualization tooling follow-up.
Motivation and Context
The existing tree-shaking tests document what ends up co-bundled but give no visual sense of how much each file contributes. This app answers that question at a glance, and makes it easy to spot unexpectedly large modules or directories.
How Has This Been Tested?
Screenshots (if appropriate):
Treemap view

Sunburst view

Types of changes
Checklist:
Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
storybook.js.org/opt/hostedtoolcache/node/24.13.0/x64/bin/node node /home/REDACTED/work/recharts/recharts/node_modules/.bin/vitest run scripts/treeshaking.test.ts /pro�� d" | sort | uniq3001 0/x64/bin/node h(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
Summary by CodeRabbit
New Features
visualize-bundle,trace-bundle, andgenerate-bundle-data.Chores