Skip to content

Add tree-shaking analysis scripts and bundle size reporting#7032

Merged
PavelVanecek merged 7 commits intomainfrom
copilot/observe-bundle-size
Feb 21, 2026
Merged

Add tree-shaking analysis scripts and bundle size reporting#7032
PavelVanecek merged 7 commits intomainfrom
copilot/observe-bundle-size

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

Adds tooling to observe and enforce tree-shaking quality across recharts components — detecting when importing one component unexpectedly pulls in others.

Description

scripts/treeshaking.ts — core utility

  • treeshake(components) — rollup-bundles specific components from the es6 build with moduleSideEffects: false
  • getBundleSize(output) — returns total byte size of rollup output
  • findComponentsInBundle(output, names) — detects which component names appear in the bundle output using code.includes(name)
  • Named component group constants: CHART_COMPONENT_NAMES, CARTESIAN_COMPONENT_NAMES, POLAR_COMPONENT_NAMES, ALL_TRACKED_COMPONENT_NAMES
  • Custom rollup plugin to resolve dir/dir/index.js (rollup's ESM build skips this Node.js resolution behavior)
  • Bundle output is comment-free: rollup strips all comments from generated output, so reported sizes reflect only executable code

scripts/treeshaking.test.ts — vitest test suite (runs in build-output project)

  • For each tracked component: bundles it in isolation, asserts no other tracked component appears in the output
  • Bundle size tests for cartesian and polar component groups, plus individual bundle size tests for Treemap, SunburstChart, and Sankey
  • Skips all tests if es6/ build is absent

scripts/treeshaking-known-issues/ — per-component files for known failures

  • One TypeScript file per known tree-shaking failure — 34 components total
  • Each file exports component (the imported component name) and expectedInBundle (all components expected to appear in the bundle, including unintentionally bundled ones)
  • index.ts collects all known issues for use in the test suite
  • Adding a new known issue means creating one file and registering it in the index — no changes to test logic needed
  • Removing a file when a fix lands causes the it.fails() to unexpectedly pass, surfacing the improvement

scripts/upload-bundle-analysis.js — extended

  • Generates individual rollup bundles for cartesian components, polar components, Treemap, SunburstChart, and Sankey
  • Each bundle is uploaded to Codecov under its own unique bundle name, resulting in 8 tracked bundles total:
    • recharts/bundle-cjs (existing)
    • recharts/bundle-es6 (existing)
    • recharts/bundle-umd (existing)
    • recharts/bundle-treeshaking-cartesian (new)
    • recharts/bundle-treeshaking-polar (new)
    • recharts/bundle-treeshaking-treemap (new)
    • recharts/bundle-treeshaking-sunburst (new)
    • recharts/bundle-treeshaking-sankey (new)
  • Note: Codecov's PR diff view only shows bundles that changed vs the base branch; existing bundles unchanged by this PR will not appear in the diff

Other

  • rollup added as an explicit devDependency (was only transitive)
  • eslint.config.mjs: scripts/*.ts added to the scripts override (relaxes no-console / no-require-imports)

Related Issue

Motivation and Context

Without this tooling there is no automated signal when a code change worsens tree-shaking (e.g. a shared utility import that silently pulls in an unrelated chart). This makes it impossible to track whether recharts is improving or regressing on bundle efficiency for end-users who only use a subset of the library.

How Has This Been Tested?

npm run test-build-output — all 40 tests pass.

34 pre-existing tree-shaking problems surfaced and documented with it.fails() — all tracked components currently have at least one known issue. Each known failure has its own file in scripts/treeshaking-known-issues/. it.fails() means CI stays green while the issue is documented. When a problem is fixed the corresponding file must be removed, confirming the fix.

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • I have added a storybook story or VR test, or extended an existing story or VR test to show my changes
Original prompt

This section details on the original issue you should resolve

<issue_title>Observe bundle size</issue_title>
<issue_description>We want to make sure that we allow tree-shaking, or at least that we don't make the situation actively worse.

We have the bundle reports from Codecov which is something but it reports on the library size, which isn't entirely relevant if the app bundler will tree-shake it all later.

Here is a proof of concept script that I made:

import path from 'node:path';
import fs from 'node:fs';
import { rollup } from 'rollup';
import virtual from '@rollup/plugin-virtual';
import { createRequire, findPackageJSON } from 'node:module';

const require = createRequire(import.meta.url);
const { ProjectDocReader } = require('../omnidoc/readProject') as typeof import('../omnidoc/readProject');

const packageJsonFilePath = findPackageJSON(import.meta.url);

const es6Entry = path.join(path.dirname(packageJsonFilePath), 'es6', 'index.js');

const packageJsonContent = JSON.parse(fs.readFileSync(packageJsonFilePath, 'utf8'));

const dependencies = Object.keys(packageJsonContent.dependencies);
const peerDependencies = Object.keys(packageJsonContent.peerDependencies);
const specialDependencies = [/^victory-vendor.*/, /^es-toolkit.*/, 'use-sync-external-store/shim/with-selector'];

const allDependencies = [...dependencies, ...peerDependencies, ...specialDependencies];

async function treeshake(entry: string | string[], used: boolean = true) {
  const entries = Array.isArray(entry) ? entry : [entry];
  const importList = entries.join(', ');
  const source = used
    ? `import { ${importList} } from '${es6Entry}';\nexport const used = { ${importList} };\n`
    : `import { ${importList} } from '${es6Entry}';\n`;
  const bundle = await rollup({
    input: 'entry',
    external: allDependencies,
    plugins: [
      virtual({
        entry: source,
      }),
    ],
    treeshake: {
      moduleSideEffects: false,
    },
  });

  const output = await bundle.generate({
    format: 'esm',
  });

  return output.output;
}

// usage on all exports
 new ProjectDocReader().getAllRuntimeExportedNames().forEach(name => {
   treeshake(name, false).then(output => {
     const hasCode = output.some(chunkOrAsset => {
       if (chunkOrAsset.type === 'chunk') {
         return chunkOrAsset.code.trim() !== '';
       } else {
         return chunkOrAsset.source.trim() !== '';
       }
     });
     console.log(`${name}: ${hasCode ? 'used' : 'unused'}`);
   });
 });

// usage on single export
 treeshake('Line').then(output => {
   for (const chunkOrAsset of output) {
     if (chunkOrAsset.type === 'chunk') {
       console.log(`Chunk: ${chunkOrAsset.fileName}`);
       console.log(chunkOrAsset.code);
     } else {
       console.log(`Asset: ${chunkOrAsset.fileName}`);
       console.log(chunkOrAsset.source);
     }
   }
 });

Iterate on this script.

What we want to achieve is:

  • Have a script where we can input a list of components, and it will output bundle size, and list of components that are present in the output
  • Use this script to generate several bundles: one for all cartesian components, one for all polar components, and then one bundle for Treemap, Sunburts, Sankey. Report the bundle sizes using scripts/upload-bundle-analysis.js.
  • Add a test suite that runs this script for each component, and flags all components with insufficient tree-shaking. The idea is that importing one component should never pull out more components than asked for. There are some pre-existing problems in the library; we expect this test suite to show some failures.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Summary by CodeRabbit

  • Chores

    • Added Rollup dependency for build optimization.
    • Expanded ESLint configuration for improved code coverage.
    • Enhanced bundle analysis infrastructure with tree-shaking bundle generation and upload capabilities.
  • Tests

    • Added comprehensive tree-shaking validation tests to monitor bundle sizes and component imports across the library.
  • Documentation

    • Documented known tree-shaking limitations for BarStack, CartesianGrid, XAxis, and YAxis components.

Copilot AI and others added 2 commits February 20, 2026 14:11
Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
…queness

Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Copilot AI changed the title [WIP] Observe bundle size for tree-shaking Add tree-shaking analysis scripts and bundle size reporting Feb 20, 2026
Copilot AI requested a review from PavelVanecek February 20, 2026 14:35
@codecov
Copy link

codecov bot commented Feb 20, 2026

Bundle Report

Changes will increase total bundle size by 2.07MB (71.44%) ⬆️⚠️, exceeding the configured threshold of 5%.

Bundle name Size Change
recharts/bundle-cjs 1.27MB 5.23kB (0.41%) ⬆️
recharts/bundle-es6 1.1MB 5.0kB (0.46%) ⬆️
recharts/bundle-umd 542.92kB 1.02kB (0.19%) ⬆️
recharts/bundle-treeshaking-cartesian 632.1kB 632.1kB (100%) ⬆️⚠️
recharts/bundle-treeshaking-polar 438.89kB 438.89kB (100%) ⬆️⚠️
recharts/bundle-treeshaking-treemap 342.37kB 342.37kB (100%) ⬆️⚠️
recharts/bundle-treeshaking-sunburst 311.57kB 311.57kB (100%) ⬆️⚠️
recharts/bundle-treeshaking-sankey 333.9kB 333.9kB (100%) ⬆️⚠️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js 88 bytes 64.96kB 0.14%
cartesian/Line.js 1.9kB 28.36kB 7.17% ⚠️
polar/PolarAngleAxis.js 144 bytes 12.95kB 1.12%
shape/Rectangle.js 23 bytes 12.34kB 0.19%
polar/PolarRadiusAxis.js 144 bytes 11.78kB 1.24%
util/scale/getNiceTickValues.js 2.42kB 11.51kB 26.56% ⚠️
cartesian/YAxis.js 85 bytes 11.07kB 0.77%
cartesian/XAxis.js 85 bytes 9.63kB 0.89%
state/selectors/polarAxisSelectors.js 40 bytes 8.77kB 0.46%
shape/Trapezoid.js 23 bytes 7.87kB 0.29%
component/DefaultLegendContent.js 244 bytes 7.65kB 3.29%
polar/defaultPolarAngleAxisProps.js 20 bytes 769 bytes 2.67%
polar/defaultPolarRadiusAxisProps.js 20 bytes 662 bytes 3.12%
view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 1.02kB 542.92kB 0.19%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/axisSelectors.js 88 bytes 55.13kB 0.16%
cartesian/Line.js 1.9kB 26.75kB 7.63% ⚠️
polar/PolarAngleAxis.js 144 bytes 11.51kB 1.27%
shape/Rectangle.js 23 bytes 11.07kB 0.21%
util/scale/getNiceTickValues.js 2.29kB 10.58kB 27.69% ⚠️
polar/PolarRadiusAxis.js 144 bytes 10.33kB 1.41%
cartesian/YAxis.js 70 bytes 9.64kB 0.73%
cartesian/XAxis.js 70 bytes 8.24kB 0.86%
state/selectors/polarAxisSelectors.js 40 bytes 7.06kB 0.57%
shape/Trapezoid.js 23 bytes 6.7kB 0.34%
component/DefaultLegendContent.js 165 bytes 6.69kB 2.53%
polar/defaultPolarAngleAxisProps.js 20 bytes 597 bytes 3.47%
polar/defaultPolarRadiusAxisProps.js 20 bytes 488 bytes 4.27%

@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.12%. Comparing base (bcc8f6f) to head (983924a).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7032   +/-   ##
=======================================
  Coverage   90.12%   90.12%           
=======================================
  Files         526      526           
  Lines       39183    39242   +59     
  Branches     5422     5438   +16     
=======================================
+ Hits        35312    35368   +56     
- Misses       3862     3865    +3     
  Partials        9        9           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
@github-actions
Copy link
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@PavelVanecek
Copy link
Collaborator

@copilot codecov is now reporting only one bundle. We expect that it should output the previous 3 bundles + all the new ones (cartesian bundle, polar bundle, treemap sankey sunburst bundles). Why is that? See the output below:

Bundle Report

Changes will increase total bundle size by 1.47MB (50.64%) ⬆️⚠️, exceeding the configured threshold of 5%.

Bundle name | Size | Change -- | -- | -- recharts/bundle-treeshaking | 1.47MB | 1.47MB (100%) ⬆️⚠️

Bundle Report

Changes will increase total bundle size by 1.47MB (50.64%) ⬆️⚠️, exceeding the configured threshold of 5%.
Bundle name Size Change
recharts/bundle-treeshaking 1.47MB 1.47MB (100%) ⬆️⚠️

Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot codecov is now reporting only one bundle. We expect that it should output the previous 3 bundles + all the new ones (cartesian bundle, polar bundle, treemap sankey sunburst bundles). Why is t...

This is expected Codecov behavior — the PR bundle diff only shows bundles that have changed vs the base branch. Since this PR adds only tooling scripts without touching library source code, the cjs, es6, and umd bundles are byte-for-byte identical to main and don't appear in the delta. The recharts/bundle-treeshaking bundle is brand new (not present on main), so it shows as a 100% increase.

All 4 bundles are still uploaded by the script — you can verify by clicking the full Codecov bundle report link rather than just the PR diff view.

@PavelVanecek
Copy link
Collaborator

Okay @copilot that's fair and you are correct in the details I see 4 bundles. However what we want to see are 8 bundles:

  • cartesian bundle
  • polar bundle
  • sankey bundle
  • treemap bundle
  • sunburst bundle
    • the old 3 bundles

Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

Okay @copilot that's fair and you are correct in the details I see 4 bundles. However what we want to see are 8 bundles:

  • cartesian bundle
    ...

Fixed in 3f91be8 — each bundle now gets its own unique Codecov name, so all 8 will appear separately:

  • recharts/bundle-cjs
  • recharts/bundle-es6
  • recharts/bundle-umd
  • recharts/bundle-treeshaking-cartesian
  • recharts/bundle-treeshaking-polar
  • recharts/bundle-treeshaking-treemap
  • recharts/bundle-treeshaking-sunburst
  • recharts/bundle-treeshaking-sankey

@PavelVanecek
Copy link
Collaborator

@copilot question: do the new bundles include comments, or exclude?

Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot question: do the new bundles include comments, or exclude?

The new bundles exclude comments. Rollup strips all comments from its output as part of normal bundle generation — only the actual executable code is in the generated files. I verified this: a Sankey-only bundle produces 6 lines with 0 comment lines.

@github-actions
Copy link
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@PavelVanecek PavelVanecek marked this pull request as ready for review February 21, 2026 07:12
@PavelVanecek
Copy link
Collaborator

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces a comprehensive tree-shaking testing and bundle analysis infrastructure for Recharts. It adds Rollup-based utilities to measure bundle sizes, detect component tree-shaking behavior, includes test coverage for bundle integrity, and extends the bundle upload pipeline with tree-shaking bundle generation for various component groups.

Changes

Cohort / File(s) Summary
ESLint & Dependencies
eslint.config.mjs, package.json
Extends ESLint override to include TypeScript files in scripts directory; adds Rollup ^4.40.1 as a dependency.
Tree-shaking Known Issues
scripts/treeshaking-known-issues/BarStack.ts, CartesianGrid.ts, XAxis.ts, YAxis.ts
Adds four TypeScript modules that export component names and their expected bundle inclusions, documenting pre-existing tree-shaking limitations where certain components pull in additional dependencies.
Tree-shaking Infrastructure
scripts/treeshaking-known-issues/index.ts, scripts/treeshaking.ts
Introduces KnownIssue type and aggregator for known issues; implements core tree-shaking utilities via Rollup including bundling logic, bundle size calculation, component detection, and four categorized component name lists (Chart, Cartesian, Polar, All).
Tree-shaking Tests
scripts/treeshaking.test.ts
Adds Vitest-based test suite validating tree-shaking behavior for tracked components and measuring bundle sizes across different component groupings, with tests conditionally marked as expected failures based on known issues.
Bundle Analysis Pipeline
scripts/upload-bundle-analysis.js
Extends existing bundle upload script to generate and upload tree-shaken bundles for Cartesian, Polar, Treemap, Sunburst, and Sankey components using Rollup with custom resolution logic.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test Suite
    participant Treeshake as treeshake()
    participant Rollup as Rollup
    participant Output as Output Analyzer
    participant Assert as Assertions

    Test->>Treeshake: treeshake(componentNames)
    Treeshake->>Treeshake: Create temp entry file
    Treeshake->>Rollup: Bundle with config<br/>(external deps, no side-effects)
    Rollup->>Rollup: Resolve & transform code
    Rollup-->>Treeshake: OutputChunk[]
    Treeshake->>Output: findComponentsInBundle(output)
    Output->>Output: Scan bundle code<br/>for definitions
    Output-->>Treeshake: Found components[]
    Treeshake->>Output: getBundleSize(output)
    Output-->>Treeshake: Size in bytes
    Treeshake-->>Test: Results
    Test->>Assert: Verify expected<br/>components present
    Test->>Assert: Verify bundle > 0 bytes
    Test->>Assert: Mark as todo if<br/>knownIssue exists
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

  • recharts/recharts#7018: This PR directly implements the tree-shaking testing and bundle analysis functionality requested in the issue, including Rollup integration, component tracking, and bundle upload orchestration.

Suggested reviewers

  • ckifer
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding tree-shaking analysis scripts and bundle size reporting, which aligns with the substantial new tooling introduced across multiple script files.
Description check ✅ Passed The description comprehensively covers all required template sections: detailed motivation (tree-shaking quality tracking), clear implementation breakdown, testing approach, and marked the new feature checkbox appropriately.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch copilot/observe-bundle-size

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
scripts/treeshaking.ts (2)

32-63: Minor: bundle.close() skipped if generate() throws.

If bundle.generate() on Line 58 throws, bundle.close() on Line 59 is never called. Consider wrapping the bundle lifecycle in its own try/finally to ensure cleanup.

♻️ Proposed fix
   try {
     const bundle = await rollup({
       input: tmpFile,
       external: getExternals(),
       treeshake: {
         moduleSideEffects: false,
       },
       logLevel: 'silent',
       plugins: [
         {
           name: 'resolve-directory-index',
           resolveId(moduleId: string, importer: string | undefined) {
             if (importer != null && moduleId.startsWith('.')) {
               const resolved = path.resolve(path.dirname(importer), moduleId);
               if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
                 const indexFile = path.join(resolved, 'index.js');
                 if (fs.existsSync(indexFile)) {
                   return indexFile;
                 }
               }
             }
             return null;
           },
         },
       ],
     });
-    const output = await bundle.generate({ format: 'esm' });
-    await bundle.close();
-    return output.output;
+    try {
+      const output = await bundle.generate({ format: 'esm' });
+      return output.output;
+    } finally {
+      await bundle.close();
+    }
   } finally {
     fs.unlinkSync(tmpFile);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/treeshaking.ts` around lines 32 - 63, The rollup bundle is not
guaranteed to be closed if bundle.generate() throws; wrap the rollup lifecycle
so bundle.close() always runs: after creating bundle = await rollup(...) keep
the outer finally that unlinks tmpFile, but introduce an inner try/finally
around await bundle.generate({ format: 'esm' }) that always calls await
bundle.close() in the finally block (only call close if bundle is defined),
ensuring bundle.generate, bundle.close, and rollup are referenced correctly and
tmpFile cleanup remains in the existing outer finally.

9-57: Significant code duplication with scripts/upload-bundle-analysis.js.

The getExternals() function, es6Entry and packageRoot constants, temp file naming approach, rollup configuration, and the resolve-directory-index plugin are duplicated across both files. The implementations are functionally identical, though getExternals() has minor stylistic differences (treeshaking.ts uses a specialDeps constant while the upload script inlines the dependency). If either copy is updated independently, the other will silently drift.

Consider extracting these utilities into a shared .mjs module that both scripts can import, or convert the upload script to TypeScript to share the module directly. This prevents maintenance burden and ensures consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/treeshaking.ts` around lines 9 - 57, Duplicate utilities
(getExternals, es6Entry, packageRoot, tmp file naming, rollup config and the
resolve-directory-index plugin) are repeated between scripts/treeshaking.ts and
scripts/upload-bundle-analysis.js; extract them into a single shared module and
import it from both scripts to avoid drift. Create a shared module that exports
getExternals, es6Entry, packageRoot, a helper for creating tmp filenames, and a
function or object for the common rollup plugin/configuration, then replace the
local definitions in treeshaking.ts (symbols: getExternals, es6Entry,
packageRoot, tmpFile creation, and the inline plugin named
"resolve-directory-index") and in upload-bundle-analysis.js to import and use
the shared exports so both scripts use the exact same implementation.
scripts/treeshaking.test.ts (3)

50-98: Bundle size tests are functional; minor DRY opportunity.

The six bundle-size tests follow an identical pattern (treeshake → getBundleSize → assert > 0 → log). This is readable as-is, but if the list of bundles grows, consider a table-driven approach:

♻️ Optional: parameterize bundle size tests
const bundleSizeCases: [string, string | string[]][] = [
  ['Cartesian components', [...CARTESIAN_COMPONENT_NAMES]],
  ['Polar components', [...POLAR_COMPONENT_NAMES]],
  ['Treemap', 'Treemap'],
  ['SunburstChart', 'SunburstChart'],
  ['Sankey', 'Sankey'],
  ['Chart components', [...CHART_COMPONENT_NAMES]],
];

for (const [label, components] of bundleSizeCases) {
  it(`${label} bundle size`, async () => {
    const output = await treeshake(components);
    const size = getBundleSize(output);
    expect(size).toBeGreaterThan(0);
    console.log(`${label} bundle size: ${size} bytes`);
  }, 60_000);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/treeshaking.test.ts` around lines 50 - 98, The tests repeat the
pattern treeshake → getBundleSize → expect(...) → console.log, so refactor to a
table-driven loop to remove duplication: create an array (e.g., bundleSizeCases)
of tuples pairing the human label with the components (use
CARTESIAN_COMPONENT_NAMES, POLAR_COMPONENT_NAMES, CHART_COMPONENT_NAMES or
string names like 'Treemap'), then iterate over that array to call
treeshake(components), compute getBundleSize(output), assert
expect(size).toBeGreaterThan(0) and log the label; keep the individual timeout
(60_000) for each generated it block and preserve test names by using the label
when creating the it(...) description.

18-20: knownExpectedBundle is mapped but never used inside the test body.

The expectedInBundle data from each known issue is stored in knownIssuesByComponent and read into knownExpectedBundle (line 24), but the test function (lines 26-34) never references it — it checks for all other tracked components, not specifically the expected ones.

This is fine for the it.fails() gating logic (you only need to know whether a known issue exists, not which components it documents). But then you could simplify to just checking for the component name's presence rather than storing the full expectedInBundle value:

♻️ Optional simplification
-const knownIssuesByComponent = Object.fromEntries(
-  knownTreeshakingIssues.map(issue => [issue.component, issue.expectedInBundle]),
-);
+const knownIssueComponents = new Set(
+  knownTreeshakingIssues.map(issue => issue.component),
+);

Then on line 24/36:

-    const knownExpectedBundle = knownIssuesByComponent[componentName];
     ...
-    if (knownExpectedBundle != null) {
+    if (knownIssueComponents.has(componentName)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/treeshaking.test.ts` around lines 18 - 20, The test builds
knownIssuesByComponent from knownTreeshakingIssues but then reads
knownExpectedBundle (from knownIssuesByComponent) and never uses it in the test
body; either use the expectedInBundle value in the assertions inside the test
function (reference knownExpectedBundle and assert the bundle content matches
expectedInBundle for that component) or simplify the setup to only track
component names (e.g., a Set of component strings) and change the test to check
presence by component name instead of storing expectedInBundle; update either
the mapping creation (knownIssuesByComponent / knownTreeshakingIssues) and the
test assertion logic accordingly so the stored data is actually used or removed.

22-48: Well-designed dynamic test generation with it.fails() for known issues.

The pattern of using it.fails() to document known tree-shaking problems while keeping CI green — and having it break when the issue is fixed (prompting removal of the known-issue entry) — is a clean approach.

One minor observation: the test verifies that importing component X doesn't include other tracked components, but it doesn't verify that component X itself is present in the bundle. If tree-shaking were overly aggressive and removed the target component entirely, this test would still pass. Consider adding a positive assertion:

💡 Optional: verify the target component is present
     const testFn = async () => {
       const output = await treeshake(componentName);
+      const selfInBundle = findComponentsInBundle(output, [componentName]);
+      expect(selfInBundle, `${componentName} should be present in its own bundle`).toEqual([componentName]);
       const otherComponents = ALL_TRACKED_COMPONENT_NAMES.filter(n => n !== componentName);
       const unexpectedComponents = findComponentsInBundle(output, otherComponents);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/treeshaking.test.ts` around lines 22 - 48, The test currently only
asserts that importing componentName does NOT pull in other tracked components;
add a positive assertion that the target component is actually present in the
bundle to catch over-aggressive tree-shaking: after calling
treeshake(componentName) (in testFn) use findComponentsInBundle(output,
[componentName]) and assert that the returned list includes componentName (or is
non-empty) before asserting unexpectedComponents is empty; keep this check in
the same testFn so both the presence of the target and absence of others are
validated for functions like treeshake, findComponentsInBundle, and the loop
over ALL_TRACKED_COMPONENT_NAMES (including handling knownExpectedBundle /
it.fails).
scripts/upload-bundle-analysis.js (3)

119-140: Consider converting the .then() chain to async/await for readability.

The sequential chain of eight .then() calls is verbose. An async IIFE or top-level async function would be cleaner and easier to extend:

♻️ Optional refactor
(async () => {
  await uploadBundleAnalysis('lib', 'recharts/bundle-cjs');
  await uploadBundleAnalysis('es6', 'recharts/bundle-es6');
  await uploadBundleAnalysis('umd', 'recharts/bundle-umd');
  await generateAndUploadTreeshakingBundle(CARTESIAN_COMPONENTS, 'recharts/bundle-treeshaking-cartesian');
  await generateAndUploadTreeshakingBundle(POLAR_COMPONENTS, 'recharts/bundle-treeshaking-polar');
  await generateAndUploadTreeshakingBundle(['Treemap'], 'recharts/bundle-treeshaking-treemap');
  await generateAndUploadTreeshakingBundle(['SunburstChart'], 'recharts/bundle-treeshaking-sunburst');
  await generateAndUploadTreeshakingBundle(['Sankey'], 'recharts/bundle-treeshaking-sankey');
})().catch(error => {
  console.error('Bundle analysis failed:', error);
  process.exitCode = 1;
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/upload-bundle-analysis.js` around lines 119 - 140, Replace the long
Promise chain with an async flow: wrap the sequential calls to
uploadBundleAnalysis and generateAndUploadTreeshakingBundle (references:
uploadBundleAnalysis, generateAndUploadTreeshakingBundle, CARTESIAN_COMPONENTS,
POLAR_COMPONENTS) in an async function or async IIFE and await each call in
order, and add a single catch at the end to log errors and set a non-zero
process.exitCode; this simplifies control flow and preserves sequential
execution while making it easier to extend.

39-86: Significant code duplication with scripts/treeshaking.ts.

generateTreeshakingBundle duplicates the rollup config (resolve-directory-index plugin, treeshake options, temp file pattern) from treeshake() in treeshaking.ts. Additionally, CARTESIAN_COMPONENTS and POLAR_COMPONENTS duplicate CARTESIAN_COMPONENT_NAMES and POLAR_COMPONENT_NAMES.

This creates a maintenance risk: changes to component lists, externals, or rollup config in one file but not the other will produce silently inconsistent results between CI bundle reports and test assertions.

If cross-language import is impractical, consider at minimum extracting the component name arrays to a shared JSON or .js file that both can consume.

Also applies to: 88-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/upload-bundle-analysis.js` around lines 39 - 86, The
generateTreeshakingBundle function duplicates rollup config, the custom
resolve-directory-index plugin, temp-entry filename generation, and component
lists (CARTESIAN_COMPONENTS, POLAR_COMPONENTS) already defined alongside
treeshake() and CARTESIAN_COMPONENT_NAMES/POLAR_COMPONENT_NAMES in
scripts/treeshaking.ts; extract the shared pieces into a single small module
(e.g., export the component name arrays, getExternals(), a helper to create the
temp entry filename, and the resolve-directory-index plugin or a function that
returns the plugins array) and have both generateTreeshakingBundle and
treeshake() import and reuse those exports so there is one source of truth for
component lists, externals, and rollup configuration to avoid drift.

10-15: getExternals() is duplicated from scripts/treeshaking.ts.

This function is nearly identical to the one in treeshaking.ts. If the externals list diverges between the two files, bundle analysis results will be inconsistent with what the tests measure.

Since this file is .js and treeshaking.ts is TypeScript, direct import may not be straightforward. Consider extracting a shared .js module (or compiling the TS utilities) so both files reference a single source of truth for externals.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/upload-bundle-analysis.js` around lines 10 - 15, Duplicate
getExternals() logic should be centralized: extract the function from
scripts/upload-bundle-analysis.js into a shared JS module (e.g.,
scripts/getExternals.js) that exports getExternals, then replace the inline
implementation in upload-bundle-analysis.js and the one in treeshaking.ts to
import/require that shared module (or compile treeshaking.ts utilities to JS and
import them) so both use the same single source of truth; update references to
the unique symbol getExternals (and preserve behavior: reading package.json,
merging dependencies and peerDependencies, and the vendor regex entries) and run
tests to ensure bundle analysis and treeshaking use identical externals.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/upload-bundle-analysis.js`:
- Around line 119-140: The promise chain starting with uploadBundleAnalysis(...)
and subsequent generateAndUploadTreeshakingBundle(...) calls lacks a terminal
.catch(), so failures in generateAndUploadTreeshakingBundle or later steps can
cause unhandled rejections; append a final .catch handler to the chain that logs
the error (including error.stack) and exits non-zero (e.g., process.exit(1)) to
ensure CI fails clearly; target the chain that begins with uploadBundleAnalysis
and include logging via console.error or the existing logger so all errors from
uploadBundleAnalysis and generateAndUploadTreeshakingBundle (and data like
CARTESIAN_COMPONENTS, POLAR_COMPONENTS) are surfaced.
- Line 41: The generated ES module import specifier uses es6Entry (built with
path.join) which yields backslashes on Windows and breaks the import string
assigned to source; fix by normalizing es6Entry to use forward slashes before
interpolating into source (e.g., replace backslashes with '/' or build the path
with path.posix.join) and apply the same normalization where treeshaking.ts
constructs ES module specifiers; update the code that computes es6Entry (and the
equivalent in treeshaking.ts) so the interpolated import uses the normalized
path.

---

Nitpick comments:
In `@scripts/treeshaking.test.ts`:
- Around line 50-98: The tests repeat the pattern treeshake → getBundleSize →
expect(...) → console.log, so refactor to a table-driven loop to remove
duplication: create an array (e.g., bundleSizeCases) of tuples pairing the human
label with the components (use CARTESIAN_COMPONENT_NAMES, POLAR_COMPONENT_NAMES,
CHART_COMPONENT_NAMES or string names like 'Treemap'), then iterate over that
array to call treeshake(components), compute getBundleSize(output), assert
expect(size).toBeGreaterThan(0) and log the label; keep the individual timeout
(60_000) for each generated it block and preserve test names by using the label
when creating the it(...) description.
- Around line 18-20: The test builds knownIssuesByComponent from
knownTreeshakingIssues but then reads knownExpectedBundle (from
knownIssuesByComponent) and never uses it in the test body; either use the
expectedInBundle value in the assertions inside the test function (reference
knownExpectedBundle and assert the bundle content matches expectedInBundle for
that component) or simplify the setup to only track component names (e.g., a Set
of component strings) and change the test to check presence by component name
instead of storing expectedInBundle; update either the mapping creation
(knownIssuesByComponent / knownTreeshakingIssues) and the test assertion logic
accordingly so the stored data is actually used or removed.
- Around line 22-48: The test currently only asserts that importing
componentName does NOT pull in other tracked components; add a positive
assertion that the target component is actually present in the bundle to catch
over-aggressive tree-shaking: after calling treeshake(componentName) (in testFn)
use findComponentsInBundle(output, [componentName]) and assert that the returned
list includes componentName (or is non-empty) before asserting
unexpectedComponents is empty; keep this check in the same testFn so both the
presence of the target and absence of others are validated for functions like
treeshake, findComponentsInBundle, and the loop over ALL_TRACKED_COMPONENT_NAMES
(including handling knownExpectedBundle / it.fails).

In `@scripts/treeshaking.ts`:
- Around line 32-63: The rollup bundle is not guaranteed to be closed if
bundle.generate() throws; wrap the rollup lifecycle so bundle.close() always
runs: after creating bundle = await rollup(...) keep the outer finally that
unlinks tmpFile, but introduce an inner try/finally around await
bundle.generate({ format: 'esm' }) that always calls await bundle.close() in the
finally block (only call close if bundle is defined), ensuring bundle.generate,
bundle.close, and rollup are referenced correctly and tmpFile cleanup remains in
the existing outer finally.
- Around line 9-57: Duplicate utilities (getExternals, es6Entry, packageRoot,
tmp file naming, rollup config and the resolve-directory-index plugin) are
repeated between scripts/treeshaking.ts and scripts/upload-bundle-analysis.js;
extract them into a single shared module and import it from both scripts to
avoid drift. Create a shared module that exports getExternals, es6Entry,
packageRoot, a helper for creating tmp filenames, and a function or object for
the common rollup plugin/configuration, then replace the local definitions in
treeshaking.ts (symbols: getExternals, es6Entry, packageRoot, tmpFile creation,
and the inline plugin named "resolve-directory-index") and in
upload-bundle-analysis.js to import and use the shared exports so both scripts
use the exact same implementation.

In `@scripts/upload-bundle-analysis.js`:
- Around line 119-140: Replace the long Promise chain with an async flow: wrap
the sequential calls to uploadBundleAnalysis and
generateAndUploadTreeshakingBundle (references: uploadBundleAnalysis,
generateAndUploadTreeshakingBundle, CARTESIAN_COMPONENTS, POLAR_COMPONENTS) in
an async function or async IIFE and await each call in order, and add a single
catch at the end to log errors and set a non-zero process.exitCode; this
simplifies control flow and preserves sequential execution while making it
easier to extend.
- Around line 39-86: The generateTreeshakingBundle function duplicates rollup
config, the custom resolve-directory-index plugin, temp-entry filename
generation, and component lists (CARTESIAN_COMPONENTS, POLAR_COMPONENTS) already
defined alongside treeshake() and
CARTESIAN_COMPONENT_NAMES/POLAR_COMPONENT_NAMES in scripts/treeshaking.ts;
extract the shared pieces into a single small module (e.g., export the component
name arrays, getExternals(), a helper to create the temp entry filename, and the
resolve-directory-index plugin or a function that returns the plugins array) and
have both generateTreeshakingBundle and treeshake() import and reuse those
exports so there is one source of truth for component lists, externals, and
rollup configuration to avoid drift.
- Around line 10-15: Duplicate getExternals() logic should be centralized:
extract the function from scripts/upload-bundle-analysis.js into a shared JS
module (e.g., scripts/getExternals.js) that exports getExternals, then replace
the inline implementation in upload-bundle-analysis.js and the one in
treeshaking.ts to import/require that shared module (or compile treeshaking.ts
utilities to JS and import them) so both use the same single source of truth;
update references to the unique symbol getExternals (and preserve behavior:
reading package.json, merging dependencies and peerDependencies, and the vendor
regex entries) and run tests to ensure bundle analysis and treeshaking use
identical externals.


async function generateTreeshakingBundle(components, outputFile) {
const importList = components.join(', ');
const source = `import { ${importList} } from '${es6Entry}';\nexport const used = { ${importList} };\n`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Path separator issue in generated ES module source on Windows.

es6Entry is built with path.join (line 8), which uses backslashes on Windows. When interpolated into the import statement template, this produces an invalid ES module specifier like import { ... } from 'C:\...\es6\index.js'. The same pattern exists in treeshaking.ts.

If this only runs in Linux CI, this is low risk. Otherwise, normalize to forward slashes:

🛠️ Proposed fix
-  const source = `import { ${importList} } from '${es6Entry}';\nexport const used = { ${importList} };\n`;
+  const normalizedEntry = es6Entry.split(path.sep).join('/');
+  const source = `import { ${importList} } from '${normalizedEntry}';\nexport const used = { ${importList} };\n`;
📝 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.

Suggested change
const source = `import { ${importList} } from '${es6Entry}';\nexport const used = { ${importList} };\n`;
const normalizedEntry = es6Entry.split(path.sep).join('/');
const source = `import { ${importList} } from '${normalizedEntry}';\nexport const used = { ${importList} };\n`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/upload-bundle-analysis.js` at line 41, The generated ES module import
specifier uses es6Entry (built with path.join) which yields backslashes on
Windows and breaks the import string assigned to source; fix by normalizing
es6Entry to use forward slashes before interpolating into source (e.g., replace
backslashes with '/' or build the path with path.posix.join) and apply the same
normalization where treeshaking.ts constructs ES module specifiers; update the
code that computes es6Entry (and the equivalent in treeshaking.ts) so the
interpolated import uses the normalized path.

Comment on lines 119 to 140
uploadBundleAnalysis('lib', 'recharts/bundle-cjs')
.then(() => {
return uploadBundleAnalysis('es6', 'recharts/bundle-es6');
})
.then(() => {
return uploadBundleAnalysis('umd', 'recharts/bundle-umd');
})
.then(() => {
return generateAndUploadTreeshakingBundle(CARTESIAN_COMPONENTS, 'recharts/bundle-treeshaking-cartesian');
})
.then(() => {
return generateAndUploadTreeshakingBundle(POLAR_COMPONENTS, 'recharts/bundle-treeshaking-polar');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['Treemap'], 'recharts/bundle-treeshaking-treemap');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['SunburstChart'], 'recharts/bundle-treeshaking-sunburst');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['Sankey'], 'recharts/bundle-treeshaking-sankey');
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

No terminal .catch() on the promise chain — unhandled rejections possible.

The original three-step chain relied on uploadBundleAnalysis swallowing its own errors (line 36). The new generateAndUploadTreeshakingBundle steps don't have internal .catch() handling, so a rollup failure (e.g., missing es6 build, invalid component name) would propagate as an unhandled promise rejection, potentially crashing the CI step without a clear error message.

🛠️ Proposed fix: add a terminal catch
   .then(() => {
     return generateAndUploadTreeshakingBundle(['Sankey'], 'recharts/bundle-treeshaking-sankey');
-  });
+  })
+  .catch(error => {
+    console.error('Bundle analysis failed:', error);
+    process.exitCode = 1;
+  });
📝 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.

Suggested change
uploadBundleAnalysis('lib', 'recharts/bundle-cjs')
.then(() => {
return uploadBundleAnalysis('es6', 'recharts/bundle-es6');
})
.then(() => {
return uploadBundleAnalysis('umd', 'recharts/bundle-umd');
})
.then(() => {
return generateAndUploadTreeshakingBundle(CARTESIAN_COMPONENTS, 'recharts/bundle-treeshaking-cartesian');
})
.then(() => {
return generateAndUploadTreeshakingBundle(POLAR_COMPONENTS, 'recharts/bundle-treeshaking-polar');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['Treemap'], 'recharts/bundle-treeshaking-treemap');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['SunburstChart'], 'recharts/bundle-treeshaking-sunburst');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['Sankey'], 'recharts/bundle-treeshaking-sankey');
});
uploadBundleAnalysis('lib', 'recharts/bundle-cjs')
.then(() => {
return uploadBundleAnalysis('es6', 'recharts/bundle-es6');
})
.then(() => {
return uploadBundleAnalysis('umd', 'recharts/bundle-umd');
})
.then(() => {
return generateAndUploadTreeshakingBundle(CARTESIAN_COMPONENTS, 'recharts/bundle-treeshaking-cartesian');
})
.then(() => {
return generateAndUploadTreeshakingBundle(POLAR_COMPONENTS, 'recharts/bundle-treeshaking-polar');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['Treemap'], 'recharts/bundle-treeshaking-treemap');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['SunburstChart'], 'recharts/bundle-treeshaking-sunburst');
})
.then(() => {
return generateAndUploadTreeshakingBundle(['Sankey'], 'recharts/bundle-treeshaking-sankey');
})
.catch(error => {
console.error('Bundle analysis failed:', error);
process.exitCode = 1;
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/upload-bundle-analysis.js` around lines 119 - 140, The promise chain
starting with uploadBundleAnalysis(...) and subsequent
generateAndUploadTreeshakingBundle(...) calls lacks a terminal .catch(), so
failures in generateAndUploadTreeshakingBundle or later steps can cause
unhandled rejections; append a final .catch handler to the chain that logs the
error (including error.stack) and exits non-zero (e.g., process.exit(1)) to
ensure CI fails clearly; target the chain that begins with uploadBundleAnalysis
and include logging via console.error or the existing logger so all errors from
uploadBundleAnalysis and generateAndUploadTreeshakingBundle (and data like
CARTESIAN_COMPONENTS, POLAR_COMPONENTS) are surfaced.

@PavelVanecek
Copy link
Collaborator

@copilot I have updated the name matching in findComponentsInBundle because I think it was not detecting many problems. As a result there are now many treeshaking tests matching. Please update the knownTreeshakingIssues so that they match the test outcome.

…ed matching

Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot I have updated the name matching in findComponentsInBundle because I think it was not detecting many problems. As a result there are now many treeshaking tests matching. Please update the `...

Done in 983924a — added individual known-issue files for all 30 newly-detected failures (the updated code.includes(name) heuristic found issues in all major components). The treeshaking-known-issues/ folder now has 34 entries total. All 40 tests pass with it.fails() covering every failing component.

@github-actions
Copy link
Contributor

Staging Deployment Details

These deployments will remain available for 30 days.

To update snapshots: Comment /update-snapshots on this PR to automatically update the baseline screenshots.

@PavelVanecek PavelVanecek merged commit 15b5407 into main Feb 21, 2026
47 checks passed
@PavelVanecek PavelVanecek deleted the copilot/observe-bundle-size branch February 21, 2026 13:53
Copilot AI mentioned this pull request Feb 21, 2026
7 tasks
PavelVanecek added a commit that referenced this pull request Feb 22, 2026
Integrates BundleWatch to track bundle sizes on every CI run, with PR
comments showing size deltas against `main`.

## Description

**`.bundlewatch.config.json`** — new config tracking 6 bundles:
- `umd/Recharts.js` (minified UMD, 600kB limit)
-
`bundlewatch-bundles/treeshaking-{cartesian,polar,treemap,sunburst,sankey}.js`
(300kB limit each)

**`scripts/upload-bundle-analysis.js`** —
`generateAndUploadTreeshakingBundle` now accepts an optional
`bundlewatchOutputFile`; when provided, copies the bundle to
`bundlewatch-bundles/` before the temp dir is cleaned up. All 5
tree-shaking calls pass their respective output paths so BundleWatch has
persistent files to read.

**`.github/workflows/ci.yml`** — `BundleWatch` step added after the
Codecov bundle analysis step (which now populates
`bundlewatch-bundles/`). Passes `BUNDLEWATCH_GITHUB_TOKEN` from secrets
and sets the required CI env vars (`CI_COMMIT_SHA` uses the PR head SHA
for pull requests, `CI_BRANCH_BASE` falls back to `main` for direct
pushes).

**`.gitignore`** — `bundlewatch-bundles/` excluded.

**`package.json`** — `bundlewatch@^0.4.1` added to devDependencies;
`bundlewatch` npm script added as `bundlewatch --config
.bundlewatch.config.json` (BundleWatch does not auto-discover
`.bundlewatch.config.json` by name — without an explicit `--config` flag
it looks in `package.json` and fails with "Configuration missing").

## Related Issue

Tracks the same 5 tree-shaking bundles plus the UMD artifact introduced
in #7032.

## Motivation and Context

Without BundleWatch, there is no automated per-PR signal on whether
bundle sizes are growing. BundleWatch posts a PR comment summarising
size changes vs the `main` baseline, giving reviewers actionable data
before merging.

## How Has This Been Tested?

CI workflow updated; will validate on first run against this PR. The
`upload-bundle-analysis.js` change is additive (file copy before
temp-dir cleanup) and does not affect the existing Codecov upload path.

## Screenshots (if appropriate):

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] I have added a storybook story or VR test, or extended an existing
story or VR test to show my changes

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Onboard to BundleWatch</issue_title>
> <issue_description>Onboard this repository to BundleWatch so that we
can track bundle sizes. Follow
https://bundlewatch.io/guide/getting-started. Make it track the same
bundles as codecov is tracking. Note that we have added new bundles in
#7032.
> 
> I have already configured BUNDLEWATCH_GITHUB_TOKEN action environment
secret.
> 
> We want to track bundle sizes in main branch, then each PR, and have a
comment on each PR that shows how much each bundle size will change if
we were to merge that PR.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #7038

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Added bundle size monitoring to the continuous integration pipeline to
automatically track and enforce library size limits across distribution
formats.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
PavelVanecek added a commit that referenced this pull request Feb 23, 2026
… visualization (#7037)

## Description

Adds two on-demand developer tools for understanding and debugging
tree-shaking and bundle composition:

**`bundle-viz/` — standalone Vite + React app**
- Tab-toggles between **Treemap** (rectangles sized by rendered bytes,
coloured by source directory) and **Sunburst** (same data as concentric
rings)
- Stats header: total size, module count, directory count, component
list
- Directory colour legend
- Aliases `recharts → ../src` via Vite so no separate build step is
required

**`scripts/generate-bundle-data.ts`** — data pipeline feeding the app
- Runs rollup tree-shaking, groups modules by source directory path,
computes per-node byte totals recursively, writes hierarchical JSON to
`bundle-viz/src/bundle-data.json`

**`scripts/treeshaking.ts`** — extended with `getModuleGraph()`
(previous PR) supporting `generate-bundle-data`

### Usage

```sh
npm run build-es6
npm run generate-bundle-data           # defaults to all tracked components
npm run generate-bundle-data -- Area   # or a specific set
cd bundle-viz && npm install && npm run dev   # http://localhost:3001
```

## 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?

- Generated real data for all 34 tracked components and verified both
chart views render correctly
- Verified app builds cleanly with both the placeholder and real data
- All 40 existing treeshaking tests pass unchanged

## Screenshots (if appropriate):

**Treemap view**

![Treemap](https://github.com/user-attachments/assets/0aa92273-e4f4-42d2-b65c-af2ed41249a2)

**Sunburst view**

![Sunburst](https://github.com/user-attachments/assets/a42d12a9-6bb3-4bd9-94b5-4fac17164d34)

## Types of changes

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)

## Checklist:

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] I have added a storybook story or VR test, or extended an existing
story or VR test to show my changes

> [!WARNING]
>
> <details>
> <summary>Firewall rules blocked me from connecting to one or more
addresses (expand for details)</summary>
>
> #### I tried to connect to the following addresses, but was blocked by
firewall rules:
>
> - `storybook.js.org`
> - Triggering command: `/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&#34; | 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:
>
> - Configure [Actions setup
steps](https://gh.io/copilot/actions-setup-steps) to set up my
environment, which run before the firewall is enabled
> - Add the appropriate URLs or hosts to the custom allowlist in this
repository's [Copilot coding agent
settings](https://github.com/recharts/recharts/settings/copilot/coding_agent)
(admins only)
>
> </details>

<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> 
> ----
> 
> *This section details on the original issue you should resolve*
> 
> <issue_title>Bundle size visualization</issue_title>
> <issue_description>In #7032
we have added a script that creates a bundle with tree-shaking enabled.
We have also added tests that show which components are pulled together,
which highlights some problems we have with tree-shaking. Next task is
to debug 1. why these components are pulled together and 2. if it is a
problem
> 
> The task is:
> 1. Add a visualization tool that shows which file contributes how much
kB.
> 2. Add a debug trace tool that shows why does component A include
component B. For example, if I import `Area` component, the final bundle
has `Pie` in it. Why?
> 
> This visualization and trace can run only on-demand, it does not need
to be integrated with CI. If you need to add any dependencies make sure
they are dev-only, and none of this ends up in the npm
package.</issue_description>
> 
> ## Comments on the Issue (you are @copilot in this section)
> 
> <comments>
> </comments>
> 


</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes #7036

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added bundle visualization tool to analyze package size contributions
via interactive Treemap and Sunburst views.
* Added new npm scripts for bundle analysis: `visualize-bundle`,
`trace-bundle`, and `generate-bundle-data`.

* **Chores**
  * Enhanced CI workflow with Node.js initialization.
* Updated build tooling configurations and ignores for bundle analysis
artifacts.
  * Improved treeshaking validation with dynamic component tracking.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: PavelVanecek <1100170+PavelVanecek@users.noreply.github.com>
Co-authored-by: Pavel Vanecek <corkscreewe@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Observe bundle size

2 participants