Skip to content

Tanstack-react: Keep JSX-only component references during dead-code elimination - #35206

Merged
huang-julien merged 6 commits into
storybookjs:nextfrom
yatishgoel:fix/tanstack-jsx-dead-import-35175
Jul 15, 2026
Merged

huang-julien merged 6 commits into
storybookjs:nextfrom
yatishgoel:fix/tanstack-jsx-dead-import-35175

Conversation

@yatishgoel

@yatishgoel yatishgoel commented Jun 18, 2026 •

Copy link
Copy Markdown
Member

Closes #35175

What I did

Fixed a dead-code-elimination bug in @storybook/tanstack-react where components referenced only from JSX were wrongly removed, producing a runtime ReferenceError: X is not defined.

The server-code-elimination Vite plugin strips a route's server option (server-only handlers/middleware) and then runs an iterative dead-code pass (removeDeadTopLevelDeclarations + removeDeadImportSpecifiers) to drop now-unused imports and declarations. That pass decides what is "still used" via collectReferencedIdentifiers, which only collected plain Babel Identifier nodes.

In Babel's AST, a JSX element name such as <ImportedPanel /> is a JSXIdentifier (and <Foo.Bar /> is a JSXMemberExpression), not an Identifier. So any component used only inside JSX looked unreferenced and was removed:

  • an imported component (<ImportedPanel />) → its import got deleted
  • a same-file component (<NestedComponent />) → its top-level declaration got deleted

This only surfaced when a server option was present, because that is what triggers the dead-code pass (state.modified).

The fix adds a JSXOpeningElement visitor to collectReferencedIdentifiers that records the element's name as a reference, unwrapping member expressions to their root binding (UI in <UI.Panel />). Because both elimination passes share this collector, imported and same-file JSX-only references are now kept.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Added regression tests in code/frameworks/tanstack-react/src/plugins/server-code-elimination.test.ts covering:

  • an imported component used only in JSX (<ImportedPanel />)
  • a same-file component used only in JSX (<NestedComponent />, the follow-up case from the issue)
  • a JSX member-expression reference (<UI.Panel />)

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

A maintainer can reproduce the original bug and confirm the fix using the reporter's reproduction:

  1. Generate a TanStack sandbox: yarn task sandbox --template react-vite/default-ts --start-from auto (or use the reporter's repro: https://github.com/hollandjake/github-cqpvv6n3/tree/middleware-breaks-deadcode).
  2. Create a route file that has both a server option (e.g. a middleware) and a component that renders a child imported into the file, e.g.:
    import { createFileRoute } from '@tanstack/react-router';
    import { ImportedPanel } from '../components/ImportedPanel';
    
    export const Route = createFileRoute('/demo')({
      component: RouteComponent,
      server: { handler: async () => ({}) },
    });
    
    function RouteComponent() {
      return <ImportedPanel />;
    }
  3. Run Storybook (npm run storybook / yarn storybook) and open a story that renders this route.
  4. Before this fix: the story fails with Story did something wrong : ReferenceError: ImportedPanel is not defined.
    After this fix: the story renders <ImportedPanel /> successfully.
  5. Repeat with a component defined in the same file (used only via JSX) to confirm the same-file case also renders.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

No documentation changes needed — this is an internal bug fix with no API or behavior surface for users to configure.

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Declare whether manual QA will be needed for this PR during the next release, through qa:needed or qa:skip

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where the server-code elimination feature incorrectly removed components and imports that were only referenced in JSX markup when stripping the server configuration, ensuring they remain available for rendering.

…ode elimination

The server-code-elimination plugin strips a route's server option and then
runs a dead-code pass. Its reference collector only counted plain Identifier
nodes, so a component used only inside JSX (<ImportedPanel />) — whether
imported or declared in the same file — looked unreferenced and its import or
declaration was removed, causing 'X is not defined' at runtime.

Collect JSX element names (including the root of <Foo.Bar /> member
expressions) as references so they are kept. Adds regression tests for
imported, same-file, and member-expression JSX references.

Fixes storybookjs#35175
@coderabbitai

coderabbitai Bot commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The head commit changed during the review from 02b9548 to 226e9a9.

📝 Walkthrough

Walkthrough

The collectReferencedIdentifiers AST traversal in server-code-elimination.ts gains a JSXOpeningElement handler that resolves the base identifier (including through JSXMemberExpression) and marks it as referenced, preventing imports used only in JSX from being eliminated. Three tests are added to verify this for imported components, same-file components, and namespace member expressions.

Changes

JSX Reference Preservation in Server-Code Elimination

Layer / File(s) Summary
JSXOpeningElement visitor in collectReferencedIdentifiers
code/frameworks/tanstack-react/src/plugins/server-code-elimination.ts
Adds a JSXOpeningElement case that walks JSXMemberExpression to its root object and adds the base identifier name to the referenced set, preventing JSX-only bindings from being treated as dead imports.
New dead import elimination tests for JSX
code/frameworks/tanstack-react/src/plugins/server-code-elimination.test.ts
Adds three tests: imported component used only in JSX is retained, same-file component used only in JSX is retained, and namespace member expression (UI.Panel) preserves its namespace import after server-block stripping.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/frameworks/tanstack-react/src/plugins/server-code-elimination.ts`:
- Around line 414-416: The code at lines 414-416 adds all JSXIdentifiers to the
referenced set, including intrinsic HTML tags like div and span, which falsely
keeps unrelated lowercase bindings alive during elimination. Modify the
condition within the `t.isJSXIdentifier(name)` check to exclude intrinsic JSX
tags before adding to the referenced set. You can identify intrinsic tags by
checking if the identifier name starts with a lowercase letter or matches known
HTML tag patterns, and only add component-like identifiers (those starting with
uppercase or custom components) to the referenced set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 549bec17-52bb-4947-9300-b9b71decd7f4

📥 Commits

Reviewing files that changed from the base of the PR and between 05c6dd2 and 55b21b2.

📒 Files selected for processing (2)
  • code/frameworks/tanstack-react/src/plugins/server-code-elimination.test.ts
  • code/frameworks/tanstack-react/src/plugins/server-code-elimination.ts

Comment thread code/frameworks/tanstack-react/src/plugins/server-code-elimination.ts Outdated
@huang-julien
huang-julien enabled auto-merge July 13, 2026 12:36
@huang-julien huang-julien added bug ci:normal Run our default set of CI jobs (choose this for most PRs). qa:skip Pull Requests that do not need any QA. (e.g. documentation) labels Jul 13, 2026
@huang-julien huang-julien changed the title fix(tanstack-react): keep JSX-only component references during dead-code elimination Tanstack-react: Keep JSX-only component references during dead-code elimination Jul 13, 2026
@huang-julien
huang-julien disabled auto-merge July 13, 2026 15:04
@huang-julien
huang-julien enabled auto-merge July 13, 2026 15:04
@huang-julien
huang-julien merged commit 385f16a into storybookjs:next Jul 15, 2026
145 of 146 checks passed
@huang-julien huang-julien added the patch:yes Bugfix & documentation PR that need to be picked to main branch label Jul 15, 2026
@storybook-app-bot

storybook-app-bot Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Package Benchmarks

Commit: 1186ee2, ran on 17 July 2026 at 12:22:53 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 73 73 0
Self size 21.32 MB 22.16 MB 🚨 +835 KB 🚨
Dependency size 36.44 MB 36.44 MB 0 B
Bundle Size Analyzer Link Link

@storybook/ember

Before After Difference
Dependency count 187 187 0
Self size 13 KB 15 KB 🚨 +2 KB 🚨
Dependency size 30.94 MB 30.94 MB 🎉 -694 B 🎉
Bundle Size Analyzer Link Link

@storybook/nextjs

Before After Difference
Dependency count 533 533 0
Self size 640 KB 663 KB 🚨 +23 KB 🚨
Dependency size 62.27 MB 62.31 MB 🚨 +34 KB 🚨
Bundle Size Analyzer Link Link

@storybook/nextjs-vite

Before After Difference
Dependency count 93 93 0
Self size 1.37 MB 1.39 MB 🚨 +22 KB 🚨
Dependency size 23.97 MB 24.01 MB 🚨 +38 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-native-web-vite

Before After Difference
Dependency count 122 122 0
Self size 29 KB 30 KB 🚨 +970 B 🚨
Dependency size 25.04 MB 25.08 MB 🚨 +38 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-vite

Before After Difference
Dependency count 83 83 0
Self size 32 KB 36 KB 🚨 +4 KB 🚨
Dependency size 21.75 MB 21.78 MB 🚨 +35 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 274 274 0
Self size 23 KB 23 KB 🚨 +777 B 🚨
Dependency size 48.06 MB 48.09 MB 🚨 +34 KB 🚨
Bundle Size Analyzer Link Link

@storybook/tanstack-react

Before After Difference
Dependency count 84 84 0
Self size 112 KB 110 KB 🎉 -2 KB 🎉
Dependency size 21.78 MB 21.82 MB 🚨 +38 KB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 205 205 0
Self size 827 KB 827 KB 🚨 +84 B 🚨
Dependency size 91.32 MB 92.16 MB 🚨 +835 KB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 198 198 0
Self size 32 KB 32 KB 🎉 -79 B 🎉
Dependency size 89.80 MB 90.63 MB 🚨 +835 KB 🚨
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 74 74 0
Self size 1.09 MB 1.09 MB 🎉 -654 B 🎉
Dependency size 57.77 MB 58.60 MB 🚨 +835 KB 🚨
Bundle Size Analyzer node node

@storybook/react

Before After Difference
Dependency count 59 59 0
Self size 1.49 MB 1.52 MB 🚨 +35 KB 🚨
Dependency size 12.45 MB 12.45 MB 🚨 +81 B 🚨
Bundle Size Analyzer Link Link

@ndelangen ndelangen added patch:done Patch/release PRs already cherry-picked to main/release branch and removed patch:yes Bugfix & documentation PR that need to be picked to main branch labels Jul 17, 2026
filipw01 pushed a commit to filipw01/storybook that referenced this pull request Jul 18, 2026
…-dead-import-35175

Tanstack-react: Keep JSX-only component references during dead-code elimination
(cherry picked from commit 385f16a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-scan:human bug ci:normal Run our default set of CI jobs (choose this for most PRs). patch:done Patch/release PRs already cherry-picked to main/release branch qa:skip Pull Requests that do not need any QA. (e.g. documentation) Stale tanstack

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: @storybook/tanstack-react middleware breaks imports

4 participants