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..7fc5f249078 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 @@ -1033,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 ( @@ -1166,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) { @@ -1622,14 +1686,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 +1712,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 +1729,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 +1778,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 +1790,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 +1817,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..d83ed11be3a --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-alias.tsx @@ -0,0 +1,17 @@ +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..371dc658689 --- /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' 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..371dc658689 --- /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' 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..371dc658689 --- /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' 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-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-class.tsx b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class.tsx new file mode 100644 index 00000000000..c1ce23ab537 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/retain-exports-class.tsx @@ -0,0 +1,12 @@ +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/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/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..d78aa7e48ab --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads.tsx @@ -0,0 +1,11 @@ +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' 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..c25822c759e --- /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 } 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..97410b7cdcf --- /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 } 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..97410b7cdcf --- /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 } 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..2de16944822 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/1-default/shared-function-overloads@shared.tsx @@ -0,0 +1,6 @@ +function format(value: string): string +function format(value: number): string +function format(value: string | number) { + return String(value) +} +export { format } 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 17d70ca9bda..ff367d446e1 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,9 +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' @@ -17,9 +12,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 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 5245f6d9eb8..1bb8fee6693 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,14 +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 03cacac5b39..4bfbd4ac664 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,7 +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 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 03cacac5b39..4bfbd4ac664 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,7 +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 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 c53a7e15682..c79c1e70bfb 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 @@ -6,6 +6,10 @@ function createSeed() { console.log('shared-runtime:seed') return { count: Count.Initial, label: Labels.component } } +function createState(initialState: { count: number }): { + state: { count: number } + increment: () => number +} function createState(initialState: { count: number }) { console.log('shared-runtime:state') return { state: initialState, increment: () => initialState.count++ } 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..d83ed11be3a --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-alias.tsx @@ -0,0 +1,17 @@ +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..371dc658689 --- /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' 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..371dc658689 --- /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' 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-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-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..c1ce23ab537 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/retain-exports-class.tsx @@ -0,0 +1,12 @@ +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/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/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..83dc596c222 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads.tsx @@ -0,0 +1,14 @@ +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' 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..c25822c759e --- /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 } 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..11c7f4642ee --- /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 } 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..2de16944822 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/shared-function-overloads@shared.tsx @@ -0,0 +1,6 @@ +function format(value: string): string +function format(value: number): string +function format(value: string | number) { + return String(value) +} +export { format } 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 944c0b8ca87..c9bb5e394a8 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' @@ -12,9 +12,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 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 5245f6d9eb8..1bb8fee6693 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,14 +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 6c0c88cab72..693fbae22ff 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,13 +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/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 c53a7e15682..c79c1e70bfb 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 @@ -6,6 +6,10 @@ function createSeed() { console.log('shared-runtime:seed') return { count: Count.Initial, label: Labels.component } } +function createState(initialState: { count: number }): { + state: { count: number } + increment: () => number +} function createState(initialState: { count: number }) { console.log('shared-runtime:state') return { state: initialState, increment: () => initialState.count++ } 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..d83ed11be3a --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-alias.tsx @@ -0,0 +1,17 @@ +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..371dc658689 --- /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' 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..371dc658689 --- /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' 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-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-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..c1ce23ab537 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/retain-exports-class.tsx @@ -0,0 +1,12 @@ +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/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/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..5de27802114 --- /dev/null +++ b/packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/shared-function-overloads.tsx @@ -0,0 +1,17 @@ +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'), +}) 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..1562c9026d6 --- /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 } 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..538299461dc --- /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 } 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 fdc18fa37b8..84b1570f7ce 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' @@ -12,9 +12,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 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 053cf24d5aa..ec5a84579d1 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,15 +1,10 @@ import { state, - createState, 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 03cacac5b39..4bfbd4ac664 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,7 +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 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 c53a7e15682..c79c1e70bfb 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 @@ -6,6 +6,10 @@ function createSeed() { console.log('shared-runtime:seed') return { count: Count.Initial, label: Labels.component } } +function createState(initialState: { count: number }): { + state: { count: number } + increment: () => number +} function createState(initialState: { count: number }) { console.log('shared-runtime:state') return { state: initialState, increment: () => initialState.count++ } 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-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-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/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, +}) 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..eebba01d8f2 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) } } } @@ -836,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) } 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 68e193204fc..67502dad802 100644 --- a/packages/start-plugin-core/tests/compiler.test.ts +++ b/packages/start-plugin-core/tests/compiler.test.ts @@ -1136,6 +1136,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',