From 09a92a6d4078a4bd533ce387bbcc1befb020dbda Mon Sep 17 00:00:00 2001 From: hungateJoseph Date: Sun, 27 Sep 2026 18:25:11 -0700 Subject: [PATCH 1/3] fix(router-plugin, router-utils): preserve route exports under code splitting and keep re-export aliases apart Automatic code splitting dropped public exports of a route file in three ways. An exported class used as a route option was not recognised as an export, so the virtual route tried to split it and threw. A route option exported through export { name as alias } was split as if it were private, leaving the alias bound to nothing. And when a function with TypeScript overload signatures moved into the shared module, the signatures stayed behind in the reference and split files, where the TypeScript transform reads them as a local declaration and drops the import from the shared module, so both the function and its alias came back undefined. Exported classes and local export specifiers are now retained like other exported route options, and overload signatures leave with the shared implementation. In the Start compiler, module info keyed re-exports by their imported name, so export { wrap as allowed } from './server' next to export { wrap as denied } from './client' collapsed onto one source and a server-only function was compiled as client-only. Re-exports are now keyed by the exported name, which is unique within a module and cannot shadow a local binding. Fixes #8505 --- .changeset/shy-exports-remain.md | 6 + .../src/core/code-splitter/compilers.ts | 75 ++++++++++-- .../tests/code-splitter-exports.test.ts | 107 ++++++++++++++++++ .../react/1-default/retain-exports-alias.tsx | 21 ++++ .../retain-exports-alias@component.tsx | 1 + .../retain-exports-alias@errorComponent.tsx | 1 + ...retain-exports-alias@notFoundComponent.tsx | 1 + .../1-default/retain-exports-alias@shared.tsx | 0 .../react/1-default/retain-exports-class.tsx | 16 +++ .../retain-exports-class@component.tsx | 0 .../retain-exports-class@errorComponent.tsx | 0 ...retain-exports-class@notFoundComponent.tsx | 0 .../1-default/retain-exports-class@shared.tsx | 0 .../1-default/shared-function-overloads.tsx | 10 ++ .../shared-function-overloads@component.tsx | 4 + ...ared-function-overloads@errorComponent.tsx | 2 + ...d-function-overloads@notFoundComponent.tsx | 2 + .../shared-function-overloads@shared.tsx | 4 + .../react/1-default/shared-runtime.tsx | 10 +- .../1-default/shared-runtime@component.tsx | 10 +- .../shared-runtime@errorComponent.tsx | 10 +- .../shared-runtime@notFoundComponent.tsx | 10 +- .../retain-exports-alias.tsx | 21 ++++ ...--notFoundComponent---pendingComponent.tsx | 1 + .../retain-exports-alias@loader.tsx | 1 + .../retain-exports-alias@shared.tsx | 0 .../retain-exports-class.tsx | 16 +++ ...--notFoundComponent---pendingComponent.tsx | 0 .../retain-exports-class@loader.tsx | 0 .../retain-exports-class@shared.tsx | 0 .../shared-function-overloads.tsx | 12 ++ ...--notFoundComponent---pendingComponent.tsx | 4 + .../shared-function-overloads@loader.tsx | 4 + .../shared-function-overloads@shared.tsx | 4 + .../shared-runtime.tsx | 10 +- ...--notFoundComponent---pendingComponent.tsx | 10 +- .../shared-runtime@loader.tsx | 10 +- .../retain-exports-alias.tsx | 21 ++++ ...--notFoundComponent---pendingComponent.tsx | 1 + .../retain-exports-alias@errorComponent.tsx | 1 + .../retain-exports-alias@shared.tsx | 0 .../retain-exports-class.tsx | 16 +++ ...--notFoundComponent---pendingComponent.tsx | 0 .../retain-exports-class@errorComponent.tsx | 0 .../retain-exports-class@shared.tsx | 0 .../shared-function-overloads.tsx | 15 +++ ...--notFoundComponent---pendingComponent.tsx | 10 ++ ...ared-function-overloads@errorComponent.tsx | 6 + .../shared-function-overloads@shared.tsx | 0 .../shared-runtime.tsx | 10 +- ...--notFoundComponent---pendingComponent.tsx | 10 +- .../shared-runtime@errorComponent.tsx | 10 +- .../test-files/react/retain-exports-alias.tsx | 21 ++++ .../test-files/react/retain-exports-class.tsx | 16 +++ .../react/shared-function-overloads.tsx | 14 +++ packages/router-utils/src/compiler-helpers.ts | 35 ++++-- .../tests/compiler-helpers.test.ts | 98 +++++++++++++++- .../start-plugin-core/tests/compiler.test.ts | 52 +++++++++ 58 files changed, 607 insertions(+), 112 deletions(-) create mode 100644 .changeset/shy-exports-remain.md create mode 100644 packages/router-plugin/tests/code-splitter-exports.test.ts create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@component.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@notFoundComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@component.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@notFoundComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@component.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@notFoundComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@component---errorComponent---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@loader.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@component---errorComponent---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@loader.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@component---errorComponent---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@loader.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@component---loader---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@component---loader---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@component---loader---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-alias.tsx create mode 100644 packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class.tsx create mode 100644 packages/router-plugin/tests/code-splitter/test-files/react/shared-function-overloads.tsx diff --git a/.changeset/shy-exports-remain.md b/.changeset/shy-exports-remain.md new file mode 100644 index 00000000000..e961932cf4c --- /dev/null +++ b/.changeset/shy-exports-remain.md @@ -0,0 +1,6 @@ +--- +'@tanstack/router-utils': patch +'@tanstack/router-plugin': patch +--- + +Keep route exports intact under automatic code splitting: exported classes and `export { name as alias }` specifiers are retained like other exported route options, and the overload signatures of a function moved into the shared module no longer stay behind to shadow its import. Re-exports of the same name from different sources, such as `export { wrap as allowed } from './server'` next to `export { wrap as denied } from './client'`, now resolve to their own sources in the Start compiler. diff --git a/packages/router-plugin/src/core/code-splitter/compilers.ts b/packages/router-plugin/src/core/code-splitter/compilers.ts index 89cdae41add..d2461726280 100644 --- a/packages/router-plugin/src/core/code-splitter/compilers.ts +++ b/packages/router-plugin/src/core/code-splitter/compilers.ts @@ -335,6 +335,9 @@ function findExportedSharedBindings( * Remove declarations of shared bindings from the AST. * Handles both plain and exported declarations, including destructured patterns. * Removes the entire statement if all bindings in it are shared. + * TypeScript overload signatures of a shared function are removed along with + * its implementation: left behind, they read as a local declaration of the + * same name and shadow the import from the shared module. */ function removeSharedDeclarations(ast: t.File, sharedBindings: Set) { ast.program.body = ast.program.body.filter((stmt) => { @@ -351,7 +354,10 @@ function removeSharedDeclarations(ast: t.File, sharedBindings: Set) { }) // If no declarators remain, remove the entire statement if (decl.declarations.length === 0) return false - } else if (t.isFunctionDeclaration(decl) && decl.id) { + } else if ( + (t.isFunctionDeclaration(decl) || t.isTSDeclareFunction(decl)) && + decl.id + ) { if (sharedBindings.has(decl.id.name)) return false } else if (t.isClassDeclaration(decl) && decl.id) { if (sharedBindings.has(decl.id.name)) return false @@ -1622,14 +1628,22 @@ function hasExport(ast: t.File, node: t.Identifier): boolean { }) } - // declared as `function loaderFn() {}` - if (t.isFunctionDeclaration(path.node.declaration)) { + // declared as `function loaderFn() {}` or `class Layout {}` + if ( + t.isFunctionDeclaration(path.node.declaration) || + t.isClassDeclaration(path.node.declaration) + ) { if (t.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) { found = true } } } + } else if (!path.node.source) { + // declared as `export { loaderFn }` or `export { loaderFn as load }` + if (hasLocalExportSpecifier(path.node, node.name)) { + found = true + } } }, ExportDefaultDeclaration(path) { @@ -1640,8 +1654,11 @@ function hasExport(ast: t.File, node: t.Identifier): boolean { } } - // declared as `export default function loaderFn() {}` - if (t.isFunctionDeclaration(path.node.declaration)) { + // declared as `export default function loaderFn() {}` or `export default class Layout {}` + if ( + t.isFunctionDeclaration(path.node.declaration) || + t.isClassDeclaration(path.node.declaration) + ) { if (t.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) { found = true @@ -1654,6 +1671,22 @@ function hasExport(ast: t.File, node: t.Identifier): boolean { return found } +/** + * Whether an `export { ... }` statement without a source exports the given + * local binding, under its own name or an alias. + */ +function hasLocalExportSpecifier( + node: t.ExportNamedDeclaration, + name: string, +): boolean { + return node.specifiers.some( + (specifier) => + t.isExportSpecifier(specifier) && + t.isIdentifier(specifier.local) && + specifier.local.name === name, + ) +} + function removeExports(ast: t.File, node: t.Identifier): boolean { let removed = false @@ -1687,8 +1720,11 @@ function removeExports(ast: t.File, node: t.Identifier): boolean { } } }) - } else if (t.isFunctionDeclaration(path.node.declaration)) { - // declared as `export const loaderFn = () => {}` + } else if ( + t.isFunctionDeclaration(path.node.declaration) || + t.isClassDeclaration(path.node.declaration) + ) { + // declared as `export function loaderFn() {}` or `export class Layout {}` if (t.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) { path.remove() @@ -1696,6 +1732,24 @@ function removeExports(ast: t.File, node: t.Identifier): boolean { } } } + } else if (!path.node.source) { + // declared as `export { loaderFn }` or `export { loaderFn as load }` + if (hasLocalExportSpecifier(path.node, node.name)) { + const specifiers = path.node.specifiers.filter( + (specifier) => + !( + t.isExportSpecifier(specifier) && + t.isIdentifier(specifier.local) && + specifier.local.name === node.name + ), + ) + if (specifiers.length === 0) { + path.remove() + } else { + path.node.specifiers = specifiers + } + removed = true + } } }, ExportDefaultDeclaration(path) { @@ -1705,8 +1759,11 @@ function removeExports(ast: t.File, node: t.Identifier): boolean { path.remove() removed = true } - } else if (t.isFunctionDeclaration(path.node.declaration)) { - // declared as `export default function loaderFn() {}` + } else if ( + t.isFunctionDeclaration(path.node.declaration) || + t.isClassDeclaration(path.node.declaration) + ) { + // declared as `export default function loaderFn() {}` or `export default class Layout {}` if (t.isIdentifier(path.node.declaration.id)) { if (path.node.declaration.id.name === node.name) { path.remove() diff --git a/packages/router-plugin/tests/code-splitter-exports.test.ts b/packages/router-plugin/tests/code-splitter-exports.test.ts new file mode 100644 index 00000000000..b79bbee54c4 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter-exports.test.ts @@ -0,0 +1,107 @@ +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' +import path from 'node:path' +import { createServer } from 'vite' +import { describe, expect, it } from 'vitest' +import { tanstackRouter } from '../src/vite' + +// Loads a route file through the Vite dev pipeline with automatic code +// splitting on, then hands the module to the assertions while the server is +// still up, so lazy route options can import their split chunks. +async function withSplitRoute( + routeSource: string, + assert: (routeModule: Record) => Promise | void, +) { + const root = await mkdtemp(path.join(__dirname, '.split-exports-')) + let server: Awaited> | undefined + try { + await mkdir(path.join(root, 'routes')) + await writeFile( + path.join(root, 'routes/__root.tsx'), + `import { createRootRoute } from '@tanstack/react-router' +export const Route = createRootRoute({})`, + ) + await writeFile(path.join(root, 'routes/page.tsx'), routeSource) + server = await createServer({ + root, + configFile: false, + logLevel: 'silent', + plugins: [ + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + codeSplittingOptions: { + defaultBehavior: [['component'], ['loader']], + }, + routesDirectory: './routes', + generatedRouteTree: './routeTree.gen.ts', + }), + ], + server: { middlewareMode: true }, + appType: 'custom', + }) + await assert(await server.ssrLoadModule('/routes/page.tsx')) + } finally { + await server?.close() + await rm(root, { recursive: true, force: true }) + } +} + +describe('route exports under automatic code splitting', () => { + it('keeps a shared function with overload signatures importable and aliased', async () => { + await withSplitRoute( + ` +import { createFileRoute } from '@tanstack/react-router' +export function helper(value: string): string +export function helper(value: number): number +export function helper(value: string | number) { return value } +export { helper as helperAlias } +export const Route = createFileRoute('/page')({ + component: () => null, + beforeLoad: () => helper('before'), + loader: () => helper(42), +})`, + async (routeModule) => { + expect(routeModule.helper).toBeTypeOf('function') + expect(routeModule.helperAlias).toBe(routeModule.helper) + expect(routeModule.Route.options.beforeLoad()).toBe('before') + expect(await routeModule.Route.options.loader()).toBe(42) + }, + ) + }, 30_000) + + it('retains an exported class component', async () => { + await withSplitRoute( + ` +import { createFileRoute } from '@tanstack/react-router' +export class Layout { render() { return null } } +export const Route = createFileRoute('/page')({ + component: Layout, +})`, + async (routeModule) => { + expect(routeModule.Layout).toBeTypeOf('function') + expect(routeModule.Route.options.component).toBe(routeModule.Layout) + }, + ) + }, 30_000) + + it('retains route options exported under an alias', async () => { + await withSplitRoute( + ` +import { createFileRoute } from '@tanstack/react-router' +function Layout() { return null } +function loadLayout() { return 'loaded' } +export { Layout as View, loadLayout as load } +export const Route = createFileRoute('/page')({ + component: Layout, + loader: loadLayout, +})`, + async (routeModule) => { + expect(routeModule.View).toBeTypeOf('function') + expect(routeModule.Route.options.component).toBe(routeModule.View) + expect(routeModule.load).toBeTypeOf('function') + expect(routeModule.Route.options.loader).toBe(routeModule.load) + expect(routeModule.load()).toBe('loaded') + }, + ) + }, 30_000) +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias.tsx new file mode 100644 index 00000000000..195f1a03d10 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +function Layout() { + return ( +
+ +
+ ) +} + +function loadLayout() { + return { title: 'layout' } +} + +export { Layout as View, loadLayout as load } + +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: loadLayout, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@component.tsx new file mode 100644 index 00000000000..5f67a6fff1b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@component.tsx @@ -0,0 +1 @@ +import * as React from 'react'; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@errorComponent.tsx new file mode 100644 index 00000000000..5f67a6fff1b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@errorComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react'; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@notFoundComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@notFoundComponent.tsx new file mode 100644 index 00000000000..5f67a6fff1b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@notFoundComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react'; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class.tsx new file mode 100644 index 00000000000..f7c5b2f1fa4 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class.tsx @@ -0,0 +1,16 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +export class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} + +export const Route = createFileRoute('/_layout')({ + component: Layout, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@component.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@errorComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@notFoundComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@notFoundComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads.tsx new file mode 100644 index 00000000000..d89c54811eb --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads.tsx @@ -0,0 +1,10 @@ +import { format } from "shared-function-overloads.tsx?tsr-shared=1"; +const $$splitComponentImporter = () => import('shared-function-overloads.tsx?tsr-split=component'); +import { lazyRouteComponent } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +export { format as formatValue }; +export const Route = createFileRoute('/overloads')({ + loader: () => format(42), + component: lazyRouteComponent($$splitComponentImporter, 'component') +}); +export { format } from "shared-function-overloads.tsx?tsr-shared=1"; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@component.tsx new file mode 100644 index 00000000000..12ebb7602ab --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@component.tsx @@ -0,0 +1,4 @@ +import { format } from "shared-function-overloads.tsx?tsr-shared=1"; +export { format as formatValue }; +const SplitComponent = () =>
{format('label')}
; +export { SplitComponent as component }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@errorComponent.tsx new file mode 100644 index 00000000000..0d2e23d298f --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@errorComponent.tsx @@ -0,0 +1,2 @@ +import { format } from "shared-function-overloads.tsx?tsr-shared=1"; +export { format as formatValue }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@notFoundComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@notFoundComponent.tsx new file mode 100644 index 00000000000..0d2e23d298f --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@notFoundComponent.tsx @@ -0,0 +1,2 @@ +import { format } from "shared-function-overloads.tsx?tsr-shared=1"; +export { format as formatValue }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx new file mode 100644 index 00000000000..348363c90f1 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx @@ -0,0 +1,4 @@ +function format(value: string | number) { + return String(value); +} +export { format }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime.tsx index c230b4d37f8..96e0bf0dc9a 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime.tsx @@ -1,4 +1,4 @@ -import { state, createState, createSeed, increment } from "shared-runtime.tsx?tsr-shared=1"; +import { state, createSeed, increment } from "shared-runtime.tsx?tsr-shared=1"; const $$splitComponentImporter = () => import('shared-runtime.tsx?tsr-split=component'); import { lazyRouteComponent } from '@tanstack/react-router'; import { createFileRoute } from '@tanstack/react-router'; @@ -11,13 +11,5 @@ export const Route = createFileRoute('/shared-runtime')({ }, component: lazyRouteComponent($$splitComponentImporter, 'component') }); -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; export { state as firstState, state as secondState, state as 'odd-name' }; export default createSeed; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@component.tsx index fbccf6c90bc..530ba73e48f 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@component.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@component.tsx @@ -1,13 +1,5 @@ -import { state, createState, createSeed, Labels } from "shared-runtime.tsx?tsr-shared=1"; +import { state, createSeed, Labels } from "shared-runtime.tsx?tsr-shared=1"; const label = createComponentLabel(); -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; function createComponentLabel() { console.log('shared-runtime:component'); return Labels.component; diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@errorComponent.tsx index d890a562c94..fddc95ab5ff 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@errorComponent.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@errorComponent.tsx @@ -1,11 +1,3 @@ -import { state, createState, createSeed } from "shared-runtime.tsx?tsr-shared=1"; -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; +import { state, createSeed } from "shared-runtime.tsx?tsr-shared=1"; export { state as firstState, state as secondState, state as 'odd-name' }; export default createSeed; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@notFoundComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@notFoundComponent.tsx index d890a562c94..fddc95ab5ff 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@notFoundComponent.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@notFoundComponent.tsx @@ -1,11 +1,3 @@ -import { state, createState, createSeed } from "shared-runtime.tsx?tsr-shared=1"; -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; +import { state, createSeed } from "shared-runtime.tsx?tsr-shared=1"; export { state as firstState, state as secondState, state as 'odd-name' }; export default createSeed; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias.tsx new file mode 100644 index 00000000000..195f1a03d10 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +function Layout() { + return ( +
+ +
+ ) +} + +function loadLayout() { + return { title: 'layout' } +} + +export { Layout as View, loadLayout as load } + +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: loadLayout, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@component---errorComponent---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@component---errorComponent---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..5f67a6fff1b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@component---errorComponent---notFoundComponent---pendingComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react'; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@loader.tsx new file mode 100644 index 00000000000..5f67a6fff1b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@loader.tsx @@ -0,0 +1 @@ +import * as React from 'react'; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class.tsx new file mode 100644 index 00000000000..f7c5b2f1fa4 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class.tsx @@ -0,0 +1,16 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +export class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} + +export const Route = createFileRoute('/_layout')({ + component: Layout, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@component---errorComponent---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@component---errorComponent---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@loader.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads.tsx new file mode 100644 index 00000000000..ff5b3f0f046 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads.tsx @@ -0,0 +1,12 @@ +import { format } from "shared-function-overloads.tsx?tsr-shared=1"; +const $$splitComponentImporter = () => import('shared-function-overloads.tsx?tsr-split=component---errorComponent---notFoundComponent---pendingComponent'); +import { lazyRouteComponent } from '@tanstack/react-router'; +const $$splitLoaderImporter = () => import('shared-function-overloads.tsx?tsr-split=loader'); +import { lazyFn } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +export { format as formatValue }; +export const Route = createFileRoute('/overloads')({ + loader: lazyFn($$splitLoaderImporter, 'loader'), + component: lazyRouteComponent($$splitComponentImporter, 'component') +}); +export { format } from "shared-function-overloads.tsx?tsr-shared=1"; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@component---errorComponent---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@component---errorComponent---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..12ebb7602ab --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@component---errorComponent---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,4 @@ +import { format } from "shared-function-overloads.tsx?tsr-shared=1"; +export { format as formatValue }; +const SplitComponent = () =>
{format('label')}
; +export { SplitComponent as component }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@loader.tsx new file mode 100644 index 00000000000..ea2457d0d1d --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@loader.tsx @@ -0,0 +1,4 @@ +import { format } from "shared-function-overloads.tsx?tsr-shared=1"; +export { format as formatValue }; +const SplitLoader = () => format(42); +export { SplitLoader as loader }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx new file mode 100644 index 00000000000..348363c90f1 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx @@ -0,0 +1,4 @@ +function format(value: string | number) { + return String(value); +} +export { format }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime.tsx index bceb12d39a4..20cf43caa14 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime.tsx @@ -1,4 +1,4 @@ -import { state, createState, createSeed } from "shared-runtime.tsx?tsr-shared=1"; +import { state, createSeed } from "shared-runtime.tsx?tsr-shared=1"; const $$splitComponentImporter = () => import('shared-runtime.tsx?tsr-split=component---errorComponent---notFoundComponent---pendingComponent'); import { lazyRouteComponent } from '@tanstack/react-router'; const $$splitLoaderImporter = () => import('shared-runtime.tsx?tsr-split=loader'); @@ -10,13 +10,5 @@ export const Route = createFileRoute('/shared-runtime')({ loader: lazyFn($$splitLoaderImporter, 'loader'), component: lazyRouteComponent($$splitComponentImporter, 'component') }); -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; export { state as firstState, state as secondState, state as 'odd-name' }; export default createSeed; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@component---errorComponent---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@component---errorComponent---notFoundComponent---pendingComponent.tsx index fbccf6c90bc..530ba73e48f 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@component---errorComponent---notFoundComponent---pendingComponent.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@component---errorComponent---notFoundComponent---pendingComponent.tsx @@ -1,13 +1,5 @@ -import { state, createState, createSeed, Labels } from "shared-runtime.tsx?tsr-shared=1"; +import { state, createSeed, Labels } from "shared-runtime.tsx?tsr-shared=1"; const label = createComponentLabel(); -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; function createComponentLabel() { console.log('shared-runtime:component'); return Labels.component; diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@loader.tsx index 6c797c0226f..2b356cdcd36 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@loader.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@loader.tsx @@ -1,12 +1,4 @@ -import { state, createState, createSeed, increment } from "shared-runtime.tsx?tsr-shared=1"; -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; +import { state, createSeed, increment } from "shared-runtime.tsx?tsr-shared=1"; export { state as firstState, state as secondState, state as 'odd-name' }; export default createSeed; const SplitLoader = () => { diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias.tsx new file mode 100644 index 00000000000..195f1a03d10 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +function Layout() { + return ( +
+ +
+ ) +} + +function loadLayout() { + return { title: 'layout' } +} + +export { Layout as View, loadLayout as load } + +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: loadLayout, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@component---loader---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@component---loader---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..5f67a6fff1b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@component---loader---notFoundComponent---pendingComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react'; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@errorComponent.tsx new file mode 100644 index 00000000000..5f67a6fff1b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@errorComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react'; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class.tsx new file mode 100644 index 00000000000..f7c5b2f1fa4 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class.tsx @@ -0,0 +1,16 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +export class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} + +export const Route = createFileRoute('/_layout')({ + component: Layout, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@component---loader---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@component---loader---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@errorComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads.tsx new file mode 100644 index 00000000000..fe7f5271e05 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads.tsx @@ -0,0 +1,15 @@ +const $$splitComponentImporter = () => import('shared-function-overloads.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent'); +import { lazyRouteComponent } from '@tanstack/react-router'; +const $$splitLoaderImporter = () => import('shared-function-overloads.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent'); +import { lazyFn } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; +export function format(value: string): string; +export function format(value: number): string; +export function format(value: string | number) { + return String(value); +} +export { format as formatValue }; +export const Route = createFileRoute('/overloads')({ + loader: lazyFn($$splitLoaderImporter, 'loader'), + component: lazyRouteComponent($$splitComponentImporter, 'component') +}); \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@component---loader---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@component---loader---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..c36619e873d --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@component---loader---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,10 @@ +export function format(value: string): string; +export function format(value: number): string; +export function format(value: string | number) { + return String(value); +} +export { format as formatValue }; +const SplitLoader = () => format(42); +export { SplitLoader as loader }; +const SplitComponent = () =>
{format('label')}
; +export { SplitComponent as component }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@errorComponent.tsx new file mode 100644 index 00000000000..39071fb4595 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@errorComponent.tsx @@ -0,0 +1,6 @@ +export function format(value: string): string; +export function format(value: number): string; +export function format(value: string | number) { + return String(value); +} +export { format as formatValue }; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime.tsx index d88eff9d9b9..9719865f21e 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime.tsx @@ -1,4 +1,4 @@ -import { state, createState, createSeed } from "shared-runtime.tsx?tsr-shared=1"; +import { state, createSeed } from "shared-runtime.tsx?tsr-shared=1"; const $$splitComponentImporter = () => import('shared-runtime.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent'); import { lazyRouteComponent } from '@tanstack/react-router'; const $$splitLoaderImporter = () => import('shared-runtime.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent'); @@ -10,13 +10,5 @@ export const Route = createFileRoute('/shared-runtime')({ loader: lazyFn($$splitLoaderImporter, 'loader'), component: lazyRouteComponent($$splitComponentImporter, 'component') }); -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; export { state as firstState, state as secondState, state as 'odd-name' }; export default createSeed; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@component---loader---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@component---loader---notFoundComponent---pendingComponent.tsx index 0af87c21c51..53ffb918a32 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@component---loader---notFoundComponent---pendingComponent.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@component---loader---notFoundComponent---pendingComponent.tsx @@ -1,13 +1,5 @@ -import { state, createState, createSeed, Labels, increment } from "shared-runtime.tsx?tsr-shared=1"; +import { state, createSeed, Labels, increment } from "shared-runtime.tsx?tsr-shared=1"; const label = createComponentLabel(); -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; function createComponentLabel() { console.log('shared-runtime:component'); return Labels.component; diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@errorComponent.tsx index d890a562c94..fddc95ab5ff 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@errorComponent.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@errorComponent.tsx @@ -1,11 +1,3 @@ -import { state, createState, createSeed } from "shared-runtime.tsx?tsr-shared=1"; -function createState(initialState: { - count: number; -}): { - state: { - count: number; - }; - increment: () => number; -}; +import { state, createSeed } from "shared-runtime.tsx?tsr-shared=1"; export { state as firstState, state as secondState, state as 'odd-name' }; export default createSeed; \ No newline at end of file diff --git a/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-alias.tsx b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-alias.tsx new file mode 100644 index 00000000000..195f1a03d10 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-alias.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +function Layout() { + return ( +
+ +
+ ) +} + +function loadLayout() { + return { title: 'layout' } +} + +export { Layout as View, loadLayout as load } + +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: loadLayout, +}) diff --git a/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class.tsx b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class.tsx new file mode 100644 index 00000000000..f7c5b2f1fa4 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class.tsx @@ -0,0 +1,16 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +export class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} + +export const Route = createFileRoute('/_layout')({ + component: Layout, +}) diff --git a/packages/router-plugin/tests/code-splitter/test-files/react/shared-function-overloads.tsx b/packages/router-plugin/tests/code-splitter/test-files/react/shared-function-overloads.tsx new file mode 100644 index 00000000000..ae7fdd3c730 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/test-files/react/shared-function-overloads.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +export function format(value: string): string +export function format(value: number): string +export function format(value: string | number) { + return String(value) +} + +export { format as formatValue } + +export const Route = createFileRoute('/overloads')({ + loader: () => format(42), + component: () =>
{format('label')}
, +}) diff --git a/packages/router-utils/src/compiler-helpers.ts b/packages/router-utils/src/compiler-helpers.ts index 5460c349141..35f4b024c39 100644 --- a/packages/router-utils/src/compiler-helpers.ts +++ b/packages/router-utils/src/compiler-helpers.ts @@ -459,6 +459,17 @@ export function collectLocalBindingsFromStatement( } } +/** + * `export { a as b } from './x'` and `export * as ns from './x'` do not create + * a local binding, so the binding that stands in for them is keyed by the + * exported name, which is unique within a module. Keying by the imported name + * would let two re-exports of the same name from different sources overwrite + * each other. The prefix keeps the key from clashing with a real identifier. + */ +function getReExportBindingKey(exported: string): string { + return `re-export:${exported}` +} + export function extractModuleInfoFromAst(ast: t.File): ExtractedModuleInfo { const bindings = new Map() const exportMap = new Map() @@ -507,27 +518,29 @@ export function extractModuleInfoFromAst(ast: t.File): ExtractedModuleInfo { } for (const specifier of node.specifiers) { - if (t.isExportNamespaceSpecifier(specifier)) { + if (t.isExportNamespaceSpecifier(specifier) && node.source) { const exported = getModuleExportName(specifier.exported) - exportMap.set(exported, exported) - if (node.source) { - bindings.set(exported, { - type: 'import', - source: node.source.value, - importedName: '*', - }) - } + const key = getReExportBindingKey(exported) + bindings.set(key, { + type: 'import', + source: node.source.value, + importedName: '*', + }) + exportMap.set(exported, key) } else if (t.isExportSpecifier(specifier)) { const local = getModuleExportName(specifier.local) const exported = getModuleExportName(specifier.exported) - exportMap.set(exported, local) if (node.source) { - bindings.set(local, { + const key = getReExportBindingKey(exported) + bindings.set(key, { type: 'import', source: node.source.value, importedName: local, }) + exportMap.set(exported, key) + } else { + exportMap.set(exported, local) } } } diff --git a/packages/router-utils/tests/compiler-helpers.test.ts b/packages/router-utils/tests/compiler-helpers.test.ts index 00b75d67e51..cd6d2ba956d 100644 --- a/packages/router-utils/tests/compiler-helpers.test.ts +++ b/packages/router-utils/tests/compiler-helpers.test.ts @@ -218,7 +218,7 @@ describe('extractModuleInfoFromAst', () => { "pkg-ns:*", ], [ - "remote", + "re-export:renamed", "./remote:remote", ], ], @@ -237,7 +237,7 @@ describe('extractModuleInfoFromAst', () => { ], [ "renamed", - "remote", + "re-export:renamed", ], ], "reExportAllSources": [ @@ -246,4 +246,98 @@ describe('extractModuleInfoFromAst', () => { } `) }) + + test('keeps re-exports of the same name from different sources apart', () => { + expect( + collectModuleInfoSnapshot(` + export { wrap as allowed } from './server' + export { wrap as denied } from './client' + export { default as ServerPage } from './server-page' + export { default as ClientPage } from './client-page' + export * as helpers from './helpers' + `), + ).toMatchInlineSnapshot(` + { + "bindings": [ + [ + "re-export:ClientPage", + "./client-page:default", + ], + [ + "re-export:ServerPage", + "./server-page:default", + ], + [ + "re-export:allowed", + "./server:wrap", + ], + [ + "re-export:denied", + "./client:wrap", + ], + [ + "re-export:helpers", + "./helpers:*", + ], + ], + "exports": [ + [ + "ClientPage", + "re-export:ClientPage", + ], + [ + "ServerPage", + "re-export:ServerPage", + ], + [ + "allowed", + "re-export:allowed", + ], + [ + "denied", + "re-export:denied", + ], + [ + "helpers", + "re-export:helpers", + ], + ], + "reExportAllSources": [], + } + `) + }) + + test('does not let a re-export shadow a local binding of the same name', () => { + expect( + collectModuleInfoSnapshot(` + const allowed = 1 + export { wrap as allowed } from './server' + export { allowed as local } + `), + ).toMatchInlineSnapshot(` + { + "bindings": [ + [ + "allowed", + "NumericLiteral", + ], + [ + "re-export:allowed", + "./server:wrap", + ], + ], + "exports": [ + [ + "allowed", + "re-export:allowed", + ], + [ + "local", + "allowed", + ], + ], + "reExportAllSources": [], + } + `) + }) }) diff --git a/packages/start-plugin-core/tests/compiler.test.ts b/packages/start-plugin-core/tests/compiler.test.ts index c3ea245facf..7b38f4b7893 100644 --- a/packages/start-plugin-core/tests/compiler.test.ts +++ b/packages/start-plugin-core/tests/compiler.test.ts @@ -1054,6 +1054,58 @@ describe('re-export chain resolution', () => { expect(result).toBeNull() }) + test('keeps re-exports apart when they share an imported name', async () => { + // Two modules each re-export a different factory under the same name. + // A barrel re-exports both under distinct aliases, so the aliases must + // resolve to their own sources instead of collapsing onto one of them. + const virtualModules: Record = { + './server': ` + export { createServerOnlyFn as wrap } from '@tanstack/start-fn-stubs' + `, + './client': ` + export { createClientOnlyFn as wrap } from '@tanstack/start-fn-stubs' + `, + './barrel': ` + export { wrap as allowed } from './server' + export { wrap as denied } from './client' + `, + } + + const compiler: StartCompiler = new StartCompiler({ + env: 'server', + envName: 'ssr', + root: '/test', + framework: 'react' as const, + providerEnvName: 'ssr', + lookupKinds: new Set(['ServerOnlyFn', 'ClientOnlyFn']), + lookupConfigurations: [], + getKnownServerFns: () => ({}), + loadModule: async (id) => { + const code = virtualModules[id] + if (code) { + compiler.ingestModule({ code, id }) + } + }, + resolveId: async (id) => { + return virtualModules[id] ? id : null + }, + }) + + const result = await compiler.compile({ + id: 'distinct-alias-test.ts', + code: ` + import { allowed, denied } from './barrel' + export const a = allowed(() => 'server-value') + export const b = denied(() => 'client-value') + `, + }) + + expect(result).not.toBeNull() + expect(result!.code).toContain('server-value') + expect(result!.code).toContain('throw new Error') + expect(result!.code).not.toContain('client-value') + }) + test('ingestModule populates module metadata for later resolution', async () => { const compiler: StartCompiler = new StartCompiler({ env: 'server', From e5f02a00db0a67d47bc8461328ffe834e54e3574 Mon Sep 17 00:00:00 2001 From: hungateJoseph Date: Mon, 28 Sep 2026 06:13:10 -0700 Subject: [PATCH 2/3] fix(router-utils): keep overload signatures with a function moved into the shared module The shared module dropped the TypeScript overload signatures of a shared function and exported only its implementation signature. Keep them next to the implementation so the shared module exports the same signature set as the source. Importing modules still drop them, where they would shadow the import. --- .../react/1-default/shared-function-overloads@shared.tsx | 2 ++ .../snapshots/react/1-default/shared-runtime@shared.tsx | 8 ++++++++ .../shared-function-overloads@shared.tsx | 2 ++ .../shared-runtime@shared.tsx | 8 ++++++++ .../shared-runtime@shared.tsx | 8 ++++++++ packages/router-utils/src/compiler-helpers.ts | 8 +++++++- 6 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx index 348363c90f1..8b46e690bd8 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx @@ -1,3 +1,5 @@ +function format(value: string): string; +function format(value: number): string; function format(value: string | number) { return String(value); } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@shared.tsx index 54239b6cc59..99cad8eae00 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@shared.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-runtime@shared.tsx @@ -16,6 +16,14 @@ function createSeed() { label: Labels.component }; } +function createState(initialState: { + count: number; +}): { + state: { + count: number; + }; + increment: () => number; +}; function createState(initialState: { count: number; }) { diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx index 348363c90f1..8b46e690bd8 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx @@ -1,3 +1,5 @@ +function format(value: string): string; +function format(value: number): string; function format(value: string | number) { return String(value); } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@shared.tsx index 54239b6cc59..99cad8eae00 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@shared.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-runtime@shared.tsx @@ -16,6 +16,14 @@ function createSeed() { label: Labels.component }; } +function createState(initialState: { + count: number; +}): { + state: { + count: number; + }; + increment: () => number; +}; function createState(initialState: { count: number; }) { diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@shared.tsx index 54239b6cc59..99cad8eae00 100644 --- a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@shared.tsx +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-runtime@shared.tsx @@ -16,6 +16,14 @@ function createSeed() { label: Labels.component }; } +function createState(initialState: { + count: number; +}): { + state: { + count: number; + }; + increment: () => number; +}; function createState(initialState: { count: number; }) { diff --git a/packages/router-utils/src/compiler-helpers.ts b/packages/router-utils/src/compiler-helpers.ts index 35f4b024c39..eebba01d8f2 100644 --- a/packages/router-utils/src/compiler-helpers.ts +++ b/packages/router-utils/src/compiler-helpers.ts @@ -849,7 +849,13 @@ export function retainModuleLevelDeclarations( return declaration.declarations.length > 0 } - if (t.isFunctionDeclaration(declaration) && declaration.id) { + // Overload signatures travel with the function they describe, so that + // the shared module exports the same signature set as the source. + if ( + (t.isFunctionDeclaration(declaration) || + t.isTSDeclareFunction(declaration)) && + declaration.id + ) { return bindingsToKeep.has(declaration.id.name) } From b890f6501d74da771b7a722f669f420eae876728 Mon Sep 17 00:00:00 2001 From: hungateJoseph Date: Wed, 28 Oct 2026 12:33:57 -0700 Subject: [PATCH 3/3] fix(router-plugin): keep split code working when it refers to an exported component A split node that shares a group with an exported component (for example `loader: () => Layout.name` next to `component: Layout`) lost its reference: the exported declaration is removed from the virtual module and nothing imported it back. The virtual module now imports such bindings from the reference file. An exported class component that is also a shared binding made the virtual compiler throw "Unexpected splitNode type: ClassDeclaration". Class declarations are now handled like function declarations. --- .../src/core/code-splitter/compilers.ts | 58 +++++++++++++++++++ .../retain-exports-class-shared-group.tsx | 15 +++++ ...n-exports-class-shared-group@component.tsx | 2 + ...orts-class-shared-group@errorComponent.tsx | 0 ...s-class-shared-group@notFoundComponent.tsx | 0 ...tain-exports-class-shared-group@shared.tsx | 12 ++++ .../retain-exports-function-shared-group.tsx | 13 +++++ ...xports-function-shared-group@component.tsx | 3 + ...s-function-shared-group@errorComponent.tsx | 1 + ...unction-shared-group@notFoundComponent.tsx | 1 + ...n-exports-function-shared-group@shared.tsx | 10 ++++ .../retain-exports-class-shared-group.tsx | 13 +++++ ...--notFoundComponent---pendingComponent.tsx | 2 + ...tain-exports-class-shared-group@loader.tsx | 3 + ...tain-exports-class-shared-group@shared.tsx | 12 ++++ .../retain-exports-function-shared-group.tsx | 14 +++++ ...--notFoundComponent---pendingComponent.tsx | 3 + ...n-exports-function-shared-group@loader.tsx | 4 ++ ...n-exports-function-shared-group@shared.tsx | 10 ++++ .../retain-exports-class-shared-group.tsx | 21 +++++++ ...--notFoundComponent---pendingComponent.tsx | 3 + ...orts-class-shared-group@errorComponent.tsx | 0 ...tain-exports-class-shared-group@shared.tsx | 0 .../retain-exports-function-shared-group.tsx | 19 ++++++ ...--notFoundComponent---pendingComponent.tsx | 4 ++ ...s-function-shared-group@errorComponent.tsx | 1 + ...n-exports-function-shared-group@shared.tsx | 0 .../retain-exports-class-shared-group.tsx | 17 ++++++ .../retain-exports-function-shared-group.tsx | 15 +++++ 29 files changed, 256 insertions(+) create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@component.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@notFoundComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@component.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@notFoundComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@loader.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@loader.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@component---loader---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@component---loader---notFoundComponent---pendingComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@errorComponent.tsx create mode 100644 packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@shared.tsx create mode 100644 packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class-shared-group.tsx create mode 100644 packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-function-shared-group.tsx diff --git a/packages/router-plugin/src/core/code-splitter/compilers.ts b/packages/router-plugin/src/core/code-splitter/compilers.ts index d2461726280..7fc5f249078 100644 --- a/packages/router-plugin/src/core/code-splitter/compilers.ts +++ b/packages/router-plugin/src/core/code-splitter/compilers.ts @@ -1039,6 +1039,18 @@ export function compileCodeSplitVirtualRoute( ) } + splitMeta.shouldRemoveNode = false + splitMeta.localExporterIdent = splitNode.id.name + } else if (t.isClassDeclaration(splitNode)) { + // declared as `class Layout extends React.Component {}`; like a + // function declaration it stays in place and is exported under + // the split name + if (!splitNode.id) { + throw new Error( + `Class declaration for "${SPLIT_TYPE}" must have an identifier.`, + ) + } + splitMeta.shouldRemoveNode = false splitMeta.localExporterIdent = splitNode.id.name } else if ( @@ -1172,6 +1184,52 @@ export function compileCodeSplitVirtualRoute( ]) }) + // Exported split values stay in the reference file, so `removeExports` + // dropped their declarations from this module. Another split node in + // the same group may still refer to one of them (`loader: () => + // Layout.name` next to an exported `Layout` component), so import those + // from the reference file. + if (knownExportedIdents.size > 0) { + programPath.scope.crawl() + const unresolvedExportedIdents = new Set() + programPath.traverse({ + ReferencedIdentifier(identPath) { + const { name } = identPath.node + if ( + knownExportedIdents.has(name) && + !identPath.scope.getBinding(name) + ) { + unresolvedExportedIdents.add(name) + } + }, + }) + + if (unresolvedExportedIdents.size > 0) { + const [importPath] = programPath.unshiftContainer( + 'body', + t.importDeclaration( + [...unresolvedExportedIdents].map((name) => + t.importSpecifier(t.identifier(name), t.identifier(name)), + ), + t.stringLiteral( + removeSplitSearchParamFromFilename(opts.filename), + ), + ), + ) + + importPath.traverse({ + Identifier(identPath) { + if ( + identPath.parentPath.isImportSpecifier() && + identPath.key === 'local' + ) { + refIdents.add(identPath) + } + }, + }) + } + } + // convert exports to imports from the original file programPath.traverse({ ExportNamedDeclaration(path) { diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group.tsx new file mode 100644 index 00000000000..44733121857 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' +export class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: () => Layout.name, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@component.tsx new file mode 100644 index 00000000000..a59c7afcf7c --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@component.tsx @@ -0,0 +1,2 @@ +import { Layout } from 'retain-exports-class-shared-group.tsx?tsr-shared=1' +export { Layout as component } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@errorComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@notFoundComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@notFoundComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@shared.tsx new file mode 100644 index 00000000000..292a043019f --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class-shared-group@shared.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' +import { Outlet } from '@tanstack/react-router' +class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} +export { Layout } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group.tsx new file mode 100644 index 00000000000..f43fc165129 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group.tsx @@ -0,0 +1,13 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' +export function Layout() { + return ( +
+ +
+ ) +} +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: () => Layout.name, +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@component.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@component.tsx new file mode 100644 index 00000000000..c823341c352 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@component.tsx @@ -0,0 +1,3 @@ +import { Layout } from 'retain-exports-function-shared-group.tsx?tsr-shared=1' +import * as React from 'react' +export { Layout as component } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@errorComponent.tsx new file mode 100644 index 00000000000..371dc658689 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@errorComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react' diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@notFoundComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@notFoundComponent.tsx new file mode 100644 index 00000000000..371dc658689 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@notFoundComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react' diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@shared.tsx new file mode 100644 index 00000000000..1d79a2edaf1 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-function-shared-group@shared.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' +import { Outlet } from '@tanstack/react-router' +function Layout() { + return ( +
+ +
+ ) +} +export { Layout } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group.tsx new file mode 100644 index 00000000000..eb2f9c7b9fc --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group.tsx @@ -0,0 +1,13 @@ +console.warn( + '[tanstack-router] These exports from "retain-exports-class-shared-group.tsx" will not be code-split and will increase your bundle size:\n- Layout\nFor the best optimization, these items should either have their export statements removed, or be imported from another location that is not a route file.', +) +import { Layout } from 'retain-exports-class-shared-group.tsx?tsr-shared=1' +const $$splitLoaderImporter = () => + import('retain-exports-class-shared-group.tsx?tsr-split=loader') +import { lazyFn } from '@tanstack/react-router' +import { createFileRoute } from '@tanstack/react-router' +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: lazyFn($$splitLoaderImporter, 'loader'), +}) +export { Layout } from 'retain-exports-class-shared-group.tsx?tsr-shared=1' diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..a59c7afcf7c --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,2 @@ +import { Layout } from 'retain-exports-class-shared-group.tsx?tsr-shared=1' +export { Layout as component } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@loader.tsx new file mode 100644 index 00000000000..59a41a16be6 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@loader.tsx @@ -0,0 +1,3 @@ +import { Layout } from 'retain-exports-class-shared-group.tsx?tsr-shared=1' +const SplitLoader = () => Layout.name +export { SplitLoader as loader } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@shared.tsx new file mode 100644 index 00000000000..292a043019f --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class-shared-group@shared.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' +import { Outlet } from '@tanstack/react-router' +class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} +export { Layout } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group.tsx new file mode 100644 index 00000000000..532f04615a1 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group.tsx @@ -0,0 +1,14 @@ +console.warn( + '[tanstack-router] These exports from "retain-exports-function-shared-group.tsx" will not be code-split and will increase your bundle size:\n- Layout\nFor the best optimization, these items should either have their export statements removed, or be imported from another location that is not a route file.', +) +import { Layout } from 'retain-exports-function-shared-group.tsx?tsr-shared=1' +const $$splitLoaderImporter = () => + import('retain-exports-function-shared-group.tsx?tsr-split=loader') +import { lazyFn } from '@tanstack/react-router' +import * as React from 'react' +import { createFileRoute } from '@tanstack/react-router' +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: lazyFn($$splitLoaderImporter, 'loader'), +}) +export { Layout } from 'retain-exports-function-shared-group.tsx?tsr-shared=1' diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..c823341c352 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@component---errorComponent---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,3 @@ +import { Layout } from 'retain-exports-function-shared-group.tsx?tsr-shared=1' +import * as React from 'react' +export { Layout as component } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@loader.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@loader.tsx new file mode 100644 index 00000000000..30d3809d104 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@loader.tsx @@ -0,0 +1,4 @@ +import { Layout } from 'retain-exports-function-shared-group.tsx?tsr-shared=1' +import * as React from 'react' +const SplitLoader = () => Layout.name +export { SplitLoader as loader } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@shared.tsx new file mode 100644 index 00000000000..1d79a2edaf1 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-function-shared-group@shared.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' +import { Outlet } from '@tanstack/react-router' +function Layout() { + return ( +
+ +
+ ) +} +export { Layout } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group.tsx new file mode 100644 index 00000000000..61164bb9af2 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group.tsx @@ -0,0 +1,21 @@ +console.warn( + '[tanstack-router] These exports from "retain-exports-class-shared-group.tsx" will not be code-split and will increase your bundle size:\n- Layout\nFor the best optimization, these items should either have their export statements removed, or be imported from another location that is not a route file.', +) +const $$splitLoaderImporter = () => + import('retain-exports-class-shared-group.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent') +import { lazyFn } from '@tanstack/react-router' +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' +export class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: lazyFn($$splitLoaderImporter, 'loader'), +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@component---loader---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@component---loader---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..4aa132a784b --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@component---loader---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,3 @@ +import { Layout } from 'retain-exports-class-shared-group.tsx' +const SplitLoader = () => Layout.name +export { SplitLoader as loader } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@errorComponent.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class-shared-group@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group.tsx new file mode 100644 index 00000000000..29531d88adb --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group.tsx @@ -0,0 +1,19 @@ +console.warn( + '[tanstack-router] These exports from "retain-exports-function-shared-group.tsx" will not be code-split and will increase your bundle size:\n- Layout\nFor the best optimization, these items should either have their export statements removed, or be imported from another location that is not a route file.', +) +const $$splitLoaderImporter = () => + import('retain-exports-function-shared-group.tsx?tsr-split=component---loader---notFoundComponent---pendingComponent') +import { lazyFn } from '@tanstack/react-router' +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' +export function Layout() { + return ( +
+ +
+ ) +} +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: lazyFn($$splitLoaderImporter, 'loader'), +}) diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@component---loader---notFoundComponent---pendingComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@component---loader---notFoundComponent---pendingComponent.tsx new file mode 100644 index 00000000000..c9ca2f3d545 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@component---loader---notFoundComponent---pendingComponent.tsx @@ -0,0 +1,4 @@ +import { Layout } from 'retain-exports-function-shared-group.tsx' +import * as React from 'react' +const SplitLoader = () => Layout.name +export { SplitLoader as loader } diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@errorComponent.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@errorComponent.tsx new file mode 100644 index 00000000000..371dc658689 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@errorComponent.tsx @@ -0,0 +1 @@ +import * as React from 'react' diff --git a/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@shared.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-function-shared-group@shared.tsx new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class-shared-group.tsx b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class-shared-group.tsx new file mode 100644 index 00000000000..6a48fb3ae19 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-class-shared-group.tsx @@ -0,0 +1,17 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +export class Layout extends React.Component { + render() { + return ( +
+ +
+ ) + } +} + +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: () => Layout.name, +}) diff --git a/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-function-shared-group.tsx b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-function-shared-group.tsx new file mode 100644 index 00000000000..3e2b0e3d86d --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/test-files/react/retain-exports-function-shared-group.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' +import { createFileRoute, Outlet } from '@tanstack/react-router' + +export function Layout() { + return ( +
+ +
+ ) +} + +export const Route = createFileRoute('/_layout')({ + component: Layout, + loader: () => Layout.name, +})