Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions e2e/react-start/basic/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as Char45824Char54620Char48124Char44397RouteImport } from './routes/대한민국'
import { Route as UsersRouteImport } from './routes/users'
import { Route as TypeOnlyReexportRouteImport } from './routes/type-only-reexport'
import { Route as StreamRouteImport } from './routes/stream'
import { Route as ScriptsRouteImport } from './routes/scripts'
import { Route as PostsRouteImport } from './routes/posts'
Expand Down Expand Up @@ -63,6 +64,11 @@ const UsersRoute = UsersRouteImport.update({
path: '/users',
getParentRoute: () => rootRouteImport,
} as any)
const TypeOnlyReexportRoute = TypeOnlyReexportRouteImport.update({
id: '/type-only-reexport',
path: '/type-only-reexport',
getParentRoute: () => rootRouteImport,
} as any)
const StreamRoute = StreamRouteImport.update({
id: '/stream',
path: '/stream',
Expand Down Expand Up @@ -281,6 +287,7 @@ export interface FileRoutesByFullPath {
'/posts': typeof PostsRouteWithChildren
'/scripts': typeof ScriptsRoute
'/stream': typeof StreamRoute
'/type-only-reexport': typeof TypeOnlyReexportRoute
'/users': typeof UsersRouteWithChildren
'/대한민국': typeof Char45824Char54620Char48124Char44397Route
'/api/users': typeof ApiUsersRouteWithChildren
Expand Down Expand Up @@ -320,6 +327,7 @@ export interface FileRoutesByTo {
'/links': typeof LinksRoute
'/scripts': typeof ScriptsRoute
'/stream': typeof StreamRoute
'/type-only-reexport': typeof TypeOnlyReexportRoute
'/대한민국': typeof Char45824Char54620Char48124Char44397Route
'/api/users': typeof ApiUsersRouteWithChildren
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
Expand Down Expand Up @@ -361,6 +369,7 @@ export interface FileRoutesById {
'/posts': typeof PostsRouteWithChildren
'/scripts': typeof ScriptsRoute
'/stream': typeof StreamRoute
'/type-only-reexport': typeof TypeOnlyReexportRoute
'/users': typeof UsersRouteWithChildren
'/대한민국': typeof Char45824Char54620Char48124Char44397Route
'/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
Expand Down Expand Up @@ -406,6 +415,7 @@ export interface FileRouteTypes {
| '/posts'
| '/scripts'
| '/stream'
| '/type-only-reexport'
| '/users'
| '/대한민국'
| '/api/users'
Expand Down Expand Up @@ -445,6 +455,7 @@ export interface FileRouteTypes {
| '/links'
| '/scripts'
| '/stream'
| '/type-only-reexport'
| '/대한민국'
| '/api/users'
| '/multi-cookie-redirect/target'
Expand Down Expand Up @@ -485,6 +496,7 @@ export interface FileRouteTypes {
| '/posts'
| '/scripts'
| '/stream'
| '/type-only-reexport'
| '/users'
| '/대한민국'
| '/_layout/_layout-2'
Expand Down Expand Up @@ -530,6 +542,7 @@ export interface RootRouteChildren {
PostsRoute: typeof PostsRouteWithChildren
ScriptsRoute: typeof ScriptsRoute
StreamRoute: typeof StreamRoute
TypeOnlyReexportRoute: typeof TypeOnlyReexportRoute
UsersRoute: typeof UsersRouteWithChildren
Char45824Char54620Char48124Char44397Route: typeof Char45824Char54620Char48124Char44397Route
ApiUsersRoute: typeof ApiUsersRouteWithChildren
Expand Down Expand Up @@ -557,6 +570,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof UsersRouteImport
parentRoute: typeof rootRouteImport
}
'/type-only-reexport': {
id: '/type-only-reexport'
path: '/type-only-reexport'
fullPath: '/type-only-reexport'
preLoaderRoute: typeof TypeOnlyReexportRouteImport
parentRoute: typeof rootRouteImport
}
'/stream': {
id: '/stream'
path: '/stream'
Expand Down Expand Up @@ -982,6 +1002,7 @@ const rootRouteChildren: RootRouteChildren = {
PostsRoute: PostsRouteWithChildren,
ScriptsRoute: ScriptsRoute,
StreamRoute: StreamRoute,
TypeOnlyReexportRoute: TypeOnlyReexportRoute,
UsersRoute: UsersRouteWithChildren,
Char45824Char54620Char48124Char44397Route:
Char45824Char54620Char48124Char44397Route,
Expand All @@ -996,12 +1017,3 @@ const rootRouteChildren: RootRouteChildren = {
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
42 changes: 42 additions & 0 deletions e2e/react-start/basic/src/routes/type-only-reexport.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { loggingMiddleware } from '~/shared-lib'

/**
* This route tests that the compiler can handle re-exports through type-only modules.
*
* The loggingMiddleware is imported from ~/shared-lib, which re-exports from:
* - ./middleware (has runtime code)
* - ./types (has ONLY type exports - compiles to empty JS)
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

Fix documentation: path reference inconsistency.

The comment refers to ./types but the actual directory is ./typedefs (as seen in the import on line 3). Update the comment to match the actual directory name.

🔎 Proposed fix
- * - ./types (has ONLY type exports - compiles to empty JS)
+ * - ./typedefs (has ONLY type exports - compiles to empty JS)
📝 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
* - ./types (has ONLY type exports - compiles to empty JS)
* - ./typedefs (has ONLY type exports - compiles to empty JS)
🤖 Prompt for AI Agents
In e2e/react-start/basic/src/routes/type-only-reexport.tsx around line 10, the
inline comment references "./types" but the actual import directory is
"./typedefs"; update the comment to use "./typedefs" so the path in the
documentation matches the real import.

*
* If the compiler doesn't handle empty modules correctly, the build will fail with:
* "could not load module .../types/actions.ts"
*/

const getMessage = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
return 'Hello from server with type-only module re-exports!'
})

export const Route = createFileRoute('/type-only-reexport')({
component: TypeOnlyReexportPage,
loader: async () => {
const message = await getMessage()
return { message }
},
})

function TypeOnlyReexportPage() {
const { message } = Route.useLoaderData()
return (
<div className="p-2">
<h3 data-testid="type-only-heading">Type-Only Re-export Test</h3>
<p data-testid="message">{message}</p>
<p>
This page tests that the compiler can handle barrel files that re-export
from type-only modules (which compile to empty JavaScript).
</p>
</div>
)
}
12 changes: 12 additions & 0 deletions e2e/react-start/basic/src/shared-lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Library index - re-exports from middleware and types.
*
* This barrel file re-exports from both:
* 1. ./middleware - has runtime code (createMiddleware)
* 2. ./types - has ONLY type exports (compiles to empty JS)
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

Fix documentation: path reference inconsistency.

The comment refers to ./types but the actual export on line 12 uses ./typedefs. Update the comment to match the actual directory name.

🔎 Proposed fix
- * 2. ./types - has ONLY type exports (compiles to empty JS)
+ * 2. ./typedefs - has ONLY type exports (compiles to empty JS)
📝 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
* 2. ./types - has ONLY type exports (compiles to empty JS)
* 2. ./typedefs - has ONLY type exports (compiles to empty JS)
🤖 Prompt for AI Agents
In e2e/react-start/basic/src/shared-lib/index.ts around line 6, the inline
comment mentions "./types" but the actual export on line 12 uses "./typedefs";
update the comment to reference "./typedefs" (or rename the export to match the
comment) so the documentation and code match, keeping naming consistent across
the file.

*
* The compiler must handle the type-only module gracefully when
* tracing exports through this barrel file.
*/
export * from './middleware'
export * from './typedefs'
10 changes: 10 additions & 0 deletions e2e/react-start/basic/src/shared-lib/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Middleware that logs server function calls.
* This is exported through a barrel file that also re-exports type-only modules.
*/
import { createMiddleware } from '@tanstack/react-start'

export const loggingMiddleware = createMiddleware().server(({ next }) => {
console.log('[logging] Server function called')
return next()
})
11 changes: 11 additions & 0 deletions e2e/react-start/basic/src/shared-lib/typedefs/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* This file contains ONLY type exports.
* After TypeScript compilation, this becomes an empty JavaScript module.
* This tests that the compiler can handle re-exports through type-only modules.
*
* See: https://github.com/TanStack/router/issues/6198
*/

// biome-ignore lint/suspicious/noExplicitAny: Generic server function type
export type Action = (...deps: any[]) => any
export type ActionParams<TFunction extends Action> = Parameters<TFunction>[0]
4 changes: 4 additions & 0 deletions e2e/react-start/basic/src/shared-lib/typedefs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Types index - re-exports from type-only modules
*/
export * from './actions'
39 changes: 39 additions & 0 deletions e2e/react-start/basic/tests/type-only-reexport.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect } from '@playwright/test'
import { test } from '@tanstack/router-e2e-utils'

/**
* These tests verify the compiler can handle type-only module re-exports.
*
* The scenario:
* 1. ~/shared-lib/index.ts re-exports from ./middleware and ./types
* 2. ~/shared-lib/types/index.ts re-exports from ./actions.ts
* 3. ~/shared-lib/types/actions.ts contains ONLY type exports (no runtime code)
Comment on lines +8 to +10
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

Fix documentation: path reference inconsistency.

The comments refer to ~/shared-lib/types but the actual directory is ~/shared-lib/typedefs. Update references to match the actual directory structure:

  • Line 8: ./types./typedefs
  • Line 9: ~/shared-lib/types/index.ts~/shared-lib/typedefs/index.ts
  • Line 10: ~/shared-lib/types/actions.ts~/shared-lib/typedefs/actions.ts
🔎 Proposed fix
  * The scenario:
- * 1. ~/shared-lib/index.ts re-exports from ./middleware and ./types
- * 2. ~/shared-lib/types/index.ts re-exports from ./actions.ts
- * 3. ~/shared-lib/types/actions.ts contains ONLY type exports (no runtime code)
+ * 1. ~/shared-lib/index.ts re-exports from ./middleware and ./typedefs
+ * 2. ~/shared-lib/typedefs/index.ts re-exports from ./actions.ts
+ * 3. ~/shared-lib/typedefs/actions.ts contains ONLY type exports (no runtime code)
📝 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
* 1. ~/shared-lib/index.ts re-exports from ./middleware and ./types
* 2. ~/shared-lib/types/index.ts re-exports from ./actions.ts
* 3. ~/shared-lib/types/actions.ts contains ONLY type exports (no runtime code)
* 1. ~/shared-lib/index.ts re-exports from ./middleware and ./typedefs
* 2. ~/shared-lib/typedefs/index.ts re-exports from ./actions.ts
* 3. ~/shared-lib/typedefs/actions.ts contains ONLY type exports (no runtime code)
🤖 Prompt for AI Agents
In e2e/react-start/basic/tests/type-only-reexport.spec.ts around lines 8 to 10,
the inline comments reference a non-existent ./types directory and
~/shared-lib/types files; update those path references to match the actual
directory ~/shared-lib/typedefs by changing "./types" to "./typedefs" on line 8,
"~/shared-lib/types/index.ts" to "~/shared-lib/typedefs/index.ts" on line 9, and
"~/shared-lib/types/actions.ts" to "~/shared-lib/typedefs/actions.ts" on line 10
so the comments reflect the real project structure.

*
* After TypeScript compilation, actions.ts becomes an empty JavaScript module.
* The compiler must handle this gracefully when tracing exports through
* barrel files. Without the fix, the build would fail with:
* "could not load module .../types/actions.ts"
*
* If these tests pass, it proves the compiler correctly handles empty modules
* when following re-export chains.
*/

test('page using middleware from barrel with type-only re-exports builds and renders', async ({
page,
}) => {
// Navigate to the route that uses middleware from ~/shared-lib
// If the compiler fix isn't working, this page wouldn't exist because
// the build would have failed
await page.goto('/type-only-reexport')
await page.waitForURL('/type-only-reexport')

// The heading should be visible
await expect(page.getByTestId('type-only-heading')).toContainText(
'Type-Only Re-export Test',
)

// The server function should have executed and returned data
await expect(page.getByTestId('message')).toContainText(
'Hello from server with type-only module re-exports!',
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,11 @@ export function createServerFnPlugin(opts: {
loadModule: async (id: string) => {
if (this.environment.mode === 'build') {
const loaded = await this.load({ id })
if (!loaded.code) {
throw new Error(`could not load module ${id}`)
}
compiler!.ingestModule({ code: loaded.code, id })
// Handle modules with no runtime code (e.g., type-only exports).
// After TypeScript compilation, these become empty modules.
// Create an empty module info instead of throwing.
const code = loaded.code ?? ''
compiler!.ingestModule({ code, id })
} else if (this.environment.mode === 'dev') {
/**
* in dev, vite does not return code from `ctx.load()`
Expand Down
21 changes: 21 additions & 0 deletions packages/start-plugin-core/tests/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,24 @@ describe('edge cases for detectedKinds', () => {
expect(result!.code).toContain('server-impl')
})
})

test('ingestModule handles empty code gracefully', () => {
const compiler = new ServerFnCompiler({
env: 'client',
directive: 'use server',
lookupKinds: new Set(['ServerFn']),
lookupConfigurations: [],
loadModule: async () => {},
resolveId: async (id) => id,
})

// Should not throw when ingesting empty module
expect(() => {
compiler.ingestModule({ code: '', id: 'empty-types.ts' })
}).not.toThrow()

// Should also handle whitespace-only modules
expect(() => {
compiler.ingestModule({ code: ' \n\t ', id: 'whitespace.ts' })
}).not.toThrow()
})
Loading