Skip to content

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Feb 2, 2026

fixes #6480

Summary by CodeRabbit

  • New Features
    • Expanded public utilities to strip TypeScript-only exports/imports, run dead-code elimination (with optional targets), and locate referenced identifiers.
  • Tests
    • Added comprehensive tests ensuring type-only imports/exports are removed while value code is preserved across many edge cases.
  • Chores
    • Updated package dependency declarations and adjusted imports across packages related to AST/dead-code tooling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

📝 Walkthrough

Walkthrough

Consolidates AST dead-code utilities into router-utils: adds stripTypeExports and a deadCodeElimination wrapper, moves babel-dead-code-elimination into router-utils, updates imports in plugins to use the new utilities, and adds tests for TypeScript type-only AST stripping. Dependency entries adjusted in three packages.

Changes

Cohort / File(s) Summary
Dependency manifest updates
packages/router-plugin/package.json, packages/start-plugin-core/package.json, packages/router-utils/package.json
Removed babel-dead-code-elimination from router-plugin and start-plugin-core; added babel-dead-code-elimination to router-utils; moved @babel/types from devDependencies to dependencies in router-utils.
AST utilities implementation & public API
packages/router-utils/src/ast.ts, packages/router-utils/src/index.ts
Added stripTypeExports(ast) to remove TypeScript-only imports/exports and top-level type/interface declarations; added deadCodeElimination(ast, candidates?) that calls stripTypeExports then delegates to babel-dead-code-elimination; re-exported findReferencedIdentifiers; exported new utilities from index.ts.
Tests
packages/router-utils/tests/stripTypeExports.test.ts
Added Vitest suite covering removal of type-only aliases/interfaces, type-only imports/exports, mixed specifiers, and preservation of value code and edge cases.
Importer updates in plugins
packages/router-plugin/src/core/code-splitter/compilers.ts, packages/start-plugin-core/src/start-compiler-plugin/compiler.ts
Updated imports to source deadCodeElimination and findReferencedIdentifiers (and existing AST helpers) from @tanstack/router-utils instead of importing directly from babel-dead-code-elimination; minor formatting tweaks.

Sequence Diagram

sequenceDiagram
    participant Plugin as Plugin
    participant AST as AST (file)
    participant Strip as stripTypeExports()
    participant Wrapper as deadCodeElimination()
    participant Babel as babel-dead-code-elimination

    Plugin->>AST: provide parsed AST (may include TS-only nodes)
    Plugin->>Strip: call stripTypeExports(AST)
    Strip-->>AST: remove type-only imports/exports & top-level types
    Plugin->>Wrapper: call deadCodeElimination(AST, candidates?)
    Wrapper->>Babel: delegate cleaned AST (+ candidates)
    Babel-->>AST: eliminate unreferenced identifiers
    AST-->>Plugin: return cleaned AST
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 I nibbled through stray typey vines,

Snipped exports that blurred the lines,
Bundles breathe, the burrow's neat,
Plugins share tools — a tidy treat,
Hoppity hop, clean code tonight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: strip type exports upon DCE' is specific and directly related to the main change, which adds stripTypeExports function to handle type-only exports during dead code elimination.
Linked Issues check ✅ Passed All code changes implement the core requirement of #6480: stripping type-only exports to prevent server-only code leakage into client builds.
Out of Scope Changes check ✅ Passed All changes directly support the issue objective of preventing type definitions from pulling server-only dependencies into client builds.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-6480

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

@nx-cloud
Copy link

nx-cloud bot commented Feb 2, 2026

View your CI Pipeline Execution ↗ for commit d6728db

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 11m 22s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 56s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-02 23:30:07 UTC

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: 5

🤖 Fix all issues with AI agents
In `@packages/router-plugin/src/core/code-splitter/compilers.ts`:
- Around line 4-9: The import member order in compilers.ts violates
sort-imports; reorder the named imports from '@tanstack/router-utils' so they
are alphabetically sorted (deadCodeElimination, findReferencedIdentifiers,
generateFromAst, parseAst) to satisfy the linter; update the import line that
currently lists generateFromAst, parseAst, deadCodeElimination,
findReferencedIdentifiers accordingly.

In `@packages/router-utils/src/ast.ts`:
- Around line 78-128: The stripTypeExports logic currently misses type-only
export-all statements; inside the same traversal/function (stripTypeExports) add
a handler for Babel's ExportAllDeclaration nodes and return false when
node.exportKind === 'type' so that `export type * from '...'` is removed just
like the ImportDeclaration and ExportNamedDeclaration cases (refer to
ExportAllDeclaration and the stripTypeExports function to locate where to add
this check).
- Around line 150-153: The `candidates` parameter of deadCodeElimination is
currently typed as Set<any>; change it to the exact return type of
findReferencedIdentifiers (e.g., Set<...>) by using ReturnType<typeof
findReferencedIdentifiers> (or the concrete Set element type returned) so the
signature becomes deadCodeElimination(ast: ParseResult<_babel_types.File>,
candidates?: ReturnType<typeof findReferencedIdentifiers>): void; update any
internal usages assuming the stronger type and import/find the
findReferencedIdentifiers symbol if needed to ensure the type resolves
correctly.

In `@packages/router-utils/tests/stripTypeExports.test.ts`:
- Around line 1-2: Reorder the named imports so they satisfy the `sort-imports`
rule: in the first import keep the test functions (describe, expect, test) as-is
if already sorted, and in the second import put the specifiers in alphabetical
order — change the import list from parseAst, generateFromAst, stripTypeExports
to generateFromAst, parseAst, stripTypeExports so the named imports for
parseAst/generateFromAst/stripTypeExports are sorted.

In `@packages/start-plugin-core/src/start-compiler-plugin/compiler.ts`:
- Around line 4-10: Reorder the named imports from '@tanstack/router-utils' in
compiler.ts to satisfy the sort-imports rule: place the specifiers
alphabetically as deadCodeElimination, findReferencedIdentifiers,
generateFromAst, parseAst (so the import line uses that order) and keep the
default babel import as-is.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 2, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6582

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@6582

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6582

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@6582

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@6582

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@6582

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@6582

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6582

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6582

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6582

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@6582

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@6582

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@6582

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@6582

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@6582

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@6582

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@6582

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@6582

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@6582

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@6582

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@6582

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@6582

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6582

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6582

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6582

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6582

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6582

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6582

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6582

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@6582

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6582

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6582

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6582

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6582

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-devtools@6582

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-ssr-query@6582

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6582

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6582

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6582

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6582

commit: d6728db

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: 1

🤖 Fix all issues with AI agents
In `@packages/router-utils/package.json`:
- Around line 66-71: Update the dependency "babel-dead-code-elimination" in
package.json from "^1.0.11" to "^1.0.12" (update the version specifier for the
package name "babel-dead-code-elimination"), then regenerate the lockfile by
running your package manager (npm install or yarn install) so transitive deps
resolve to the newer safe versions; commit the updated package.json and
lockfile.

- Bump babel-dead-code-elimination from ^1.0.11 to ^1.0.12
- Sort imports alphabetically in compilers.ts to satisfy eslint
- Preserve non-exported type/interface declarations in stripTypeExports
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: 1

🤖 Fix all issues with AI agents
In `@packages/router-utils/src/ast.ts`:
- Around line 45-131: The stripTypeExports function currently misses
TypeScript's "export default type/interface" and the import-equals type-only
form; update stripTypeExports to also filter out t.isExportDefaultDeclaration
nodes whose declaration is a TSTypeAliasDeclaration or TSInterfaceDeclaration
(or whose exportKind === 'type') and to filter out t.isTSImportEqualsDeclaration
nodes when node.isTypeOnly is true so type-only import-equals are removed;
modify the AST filtering logic in stripTypeExports to check for
t.isExportDefaultDeclaration and t.isTSImportEqualsDeclaration and return false
for those type-only cases (and preserve other behavior for mixed/value
declarations).

Comment on lines +45 to +131
/**
* Strips TypeScript type-only exports and imports from an AST.
*
* This is necessary because babel-dead-code-elimination doesn't handle
* TypeScript type exports/imports. When a type export references an import
* that pulls in server-only code, the dead code elimination won't remove
* that import because it sees the type as still referencing it.
*
* This function removes:
* - `export type Foo = ...`
* - `export interface Foo { ... }`
* - `export type { Foo } from './module'`
* - `export type * from './module'`
* - Type specifiers in mixed exports: `export { value, type Foo }` -> `export { value }`
* - `import type { Foo } from './module'`
* - Type specifiers in mixed imports: `import { value, type Foo } from './module'` -> `import { value }`
*
* Note: Non-exported type/interface declarations are preserved as they may be
* used as type annotations within the code.
*
* @param ast - The Babel AST (or ParseResult) to mutate
*/
export function stripTypeExports(ast: ParseResult<_babel_types.File>): void {
// Filter the program body to remove type-only nodes
ast.program.body = ast.program.body.filter((node) => {
// Handle export declarations
if (t.isExportNamedDeclaration(node)) {
// Remove entire export if it's a type-only export
// e.g., `export type Foo = string`, `export interface Bar {}`, `export type { X } from './y'`
if (node.exportKind === 'type') {
return false
}

// For value exports with mixed specifiers, filter out type-only specifiers
// e.g., `export { value, type TypeOnly }` -> `export { value }`
if (node.specifiers.length > 0) {
node.specifiers = node.specifiers.filter((specifier) => {
if (t.isExportSpecifier(specifier)) {
return specifier.exportKind !== 'type'
}
return true
})

// If all specifiers were removed, remove the entire export declaration
// (unless it has a declaration like `export const x = 1`)
if (node.specifiers.length === 0 && !node.declaration) {
return false
}
}
}

// Handle type-only export-all declarations
// e.g., `export type * from './module'`
if (t.isExportAllDeclaration(node)) {
if (node.exportKind === 'type') {
return false
}
}

// Handle import declarations
if (t.isImportDeclaration(node)) {
// Remove entire import if it's a type-only import
// e.g., `import type { Foo } from './module'`
if (node.importKind === 'type') {
return false
}

// For value imports with mixed specifiers, filter out type-only specifiers
// e.g., `import { value, type TypeOnly } from './module'` -> `import { value }`
if (node.specifiers.length > 0) {
node.specifiers = node.specifiers.filter((specifier) => {
if (t.isImportSpecifier(specifier)) {
return specifier.importKind !== 'type'
}
return true
})

// If all specifiers were removed, remove the entire import declaration
if (node.specifiers.length === 0) {
return false
}
}
}

return true
})
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if these TypeScript syntaxes exist in the repo
echo "=== Checking for 'export default interface/type' syntax ==="
rg -nP "export\s+default\s+(interface|type)\b" --type=ts --type=tsx

echo ""
echo "=== Checking for 'import type Foo = require' syntax ==="
rg -nP "import\s+type\s+\w+\s*=\s*require" --type=ts --type=tsx

Repository: TanStack/router

Length of output: 248


🏁 Script executed:

#!/bin/bash
# Check if these TypeScript syntaxes exist in the repo
echo "=== Checking for 'export default interface/type' syntax ==="
rg -nP "export\s+default\s+(interface|type)\b" packages/router-utils/src/

echo ""
echo "=== Checking for 'import type.*require' syntax ==="
rg -nP "import\s+type\s+\w+\s*=\s*require" packages/router-utils/src/

echo ""
echo "=== Broader search for TSImportEqualsDeclaration usage in entire codebase ==="
rg -n "import\s+type\s+\w+\s*=" --type=ts

echo ""
echo "=== Check for export default with type/interface in whole repo ==="
rg -n "export\s+default\s+(type|interface)" --type=ts

Repository: TanStack/router

Length of output: 317


🏁 Script executed:

#!/bin/bash
# Check where stripTypeExports is used
echo "=== Usages of stripTypeExports ==="
rg -n "stripTypeExports" packages/router-utils/

echo ""
echo "=== Check test files for stripTypeExports ==="
fd -e test.ts -e test.tsx -e spec.ts packages/router-utils/ | head -20

echo ""
echo "=== Look for test files in router-utils ==="
find packages/router-utils -name "*.test.*" -o -name "*.spec.*" | head -20

echo ""
echo "=== Examine the ast.ts file context ==="
head -50 packages/router-utils/src/ast.ts

Repository: TanStack/router

Length of output: 2804


🏁 Script executed:

#!/bin/bash
# Read the test file to see what cases are tested
echo "=== stripTypeExports test file ==="
cat packages/router-utils/tests/stripTypeExports.test.ts

Repository: TanStack/router

Length of output: 9056


Handle edge cases for export default type declarations and TypeScript import-equals syntax.

stripTypeExports currently covers ExportNamedDeclaration, ExportAllDeclaration, and ImportDeclaration only. While export default interface/type and import type Foo = require(...) syntaxes don't currently appear in the codebase, they are valid TypeScript patterns that would not be stripped if introduced, potentially allowing server-only imports to remain.

Proposed patch
   ast.program.body = ast.program.body.filter((node) => {
+    // Handle default type-only exports: `export default interface Foo {}` / `export default type Foo = ...`
+    if (t.isExportDefaultDeclaration(node)) {
+      const decl = node.declaration
+      if (t.isTSInterfaceDeclaration(decl) || t.isTSTypeAliasDeclaration(decl)) {
+        return false
+      }
+    }
+
+    // Handle TS import-equals type-only: `import type Foo = require('./module')`
+    if (t.isTSImportEqualsDeclaration(node) && node.isTypeOnly) {
+      return false
+    }
🤖 Prompt for AI Agents
In `@packages/router-utils/src/ast.ts` around lines 45 - 131, The stripTypeExports
function currently misses TypeScript's "export default type/interface" and the
import-equals type-only form; update stripTypeExports to also filter out
t.isExportDefaultDeclaration nodes whose declaration is a TSTypeAliasDeclaration
or TSInterfaceDeclaration (or whose exportKind === 'type') and to filter out
t.isTSImportEqualsDeclaration nodes when node.isTypeOnly is true so type-only
import-equals are removed; modify the AST filtering logic in stripTypeExports to
check for t.isExportDefaultDeclaration and t.isTSImportEqualsDeclaration and
return false for those type-only cases (and preserve other behavior for
mixed/value declarations).

@schiller-manuel schiller-manuel merged commit 77b18bb into main Feb 2, 2026
6 checks passed
@schiller-manuel schiller-manuel deleted the fix-6480 branch February 2, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type Definition Colocated with createServerFn Causes: Failed to resolve import "tanstack-start-injected-head-scripts:v"

2 participants