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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const localFnPOST = localFnFactory({ method: 'POST' })
export const fakeFn = createFakeFn().handler(async () => {
return {
name: 'fakeFn',
window,
window: typeof window !== 'undefined' ? window : 'no window object',
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const functions = {
type: 'localFn',
expected: {
name: 'fakeFn',
window,
window: typeof window !== 'undefined' ? window : 'no window object',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Comparing window objects with deepEqual is unreliable.

When window is defined, the test attempts to compare the actual window object using deepEqual (line 144). The window object contains circular references, non-enumerable properties, and thousands of properties, which can cause:

  • Unreliable comparison results
  • Performance degradation
  • Potential failures in the equality check

Consider either:

  1. Exclude the window property from the comparison when it's defined
  2. Compare only a sentinel value (e.g., always use 'window-exists' instead of the actual object)
  3. Use a custom comparison that checks window !== undefined rather than deep equality

For example, you could change the test to:

  fakeFn: {
    fn: fakeFn,
    type: 'localFn',
    expected: {
      name: 'fakeFn',
-      window: typeof window !== 'undefined' ? window : 'no window object',
+      window: typeof window !== 'undefined' ? 'window-exists' : 'no window object',
    },
  },

And update the implementation to return a sentinel value instead of the actual window object.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In e2e/react-start/server-functions/src/routes/factory/index.tsx around line
130, the test currently exposes/compares the actual window object which makes
deepEqual unreliable; change the implementation to return a sentinel when window
exists (e.g., 'window-exists' or boolean true) instead of the real window object
and update the test to assert that sentinel (or assert window existence via !==
undefined) rather than deep-equal the full window, or alternatively exclude the
window property from the deep comparison; ensure both implementation and test
use the same sentinel/existence-check convention.

},
},
} satisfies Record<string, TestCase>
Expand Down
5 changes: 5 additions & 0 deletions e2e/react-start/split-exports/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
.output
.vite
*.local
2 changes: 2 additions & 0 deletions e2e/react-start/split-exports/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pnpm-lock.yaml
routeTree.gen.ts
38 changes: 38 additions & 0 deletions e2e/react-start/split-exports/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "tanstack-react-start-e2e-split-exports",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "vite dev --port ${PORT:-3000}",
"dev:e2e": "vite dev",
"build": "vite build && tsc --noEmit",
"preview": "vite preview",
"start": "pnpx srvx --prod -s ../client dist/server/server.js",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium",
"test:e2e:hmr": "rm -rf port*.txt; playwright test --config=playwright-hmr.config.ts"
},
"dependencies": {
"@tanstack/react-router": "workspace:^",
"@tanstack/react-router-devtools": "workspace:^",
"@tanstack/react-start": "workspace:^",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^2.6.0"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tailwindcss/postcss": "^4.1.15",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
"postcss": "^8.5.1",
"srvx": "^0.8.6",
"tailwindcss": "^4.1.17",
"typescript": "^5.7.2",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
}
Comment on lines +15 to +37
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use workspace:* for internal TanStack packages per repo guidelines

Per the workspace guideline, internal packages should use workspace:* instead of workspace:^. Updating these keeps version alignment explicit and consistent.

   "dependencies": {
-    "@tanstack/react-router": "workspace:^",
-    "@tanstack/react-router-devtools": "workspace:^",
-    "@tanstack/react-start": "workspace:^",
+    "@tanstack/react-router": "workspace:*",
+    "@tanstack/react-router-devtools": "workspace:*",
+    "@tanstack/react-start": "workspace:*",
@@
   "devDependencies": {
@@
-    "@tanstack/router-e2e-utils": "workspace:^",
+    "@tanstack/router-e2e-utils": "workspace:*",
🤖 Prompt for AI Agents
In e2e/react-start/split-exports/package.json around lines 15 to 37, the
internal TanStack workspace dependencies use "workspace:^" which violates the
repo guideline; update all internal workspace package versions
("@tanstack/react-router", "@tanstack/react-router-devtools",
"@tanstack/react-start", "@tanstack/router-e2e-utils") to use "workspace:*"
instead of "workspace:^" so they consistently reference the workspace range.

}
59 changes: 59 additions & 0 deletions e2e/react-start/split-exports/playwright-hmr.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { defineConfig, devices } from '@playwright/test'
import { getTestServerPort } from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }

/**
* Playwright configuration for HMR tests.
*
* This config runs the dev server instead of a production build,
* allowing tests to verify Hot Module Replacement works correctly
* with the split-exports plugin.
*
* Run with: npx playwright test --config=playwright-hmr.config.ts
*/

// Use a different port for HMR tests to avoid conflicts
const PORT = (await getTestServerPort(packageJson.name)) + 1
const baseURL = `http://localhost:${PORT}`

export default defineConfig({
testDir: './tests',
// Only run HMR test files
testMatch: '*-hmr.spec.ts',
// HMR tests can be slower due to file system operations
timeout: 60000,
// Run serially to avoid file system conflicts
workers: 1,
reporter: [['line']],

use: {
baseURL,
// Longer timeouts for HMR operations
actionTimeout: 10000,
navigationTimeout: 30000,
},

webServer: {
// Use dev server instead of build
command: `pnpm dev`,
url: baseURL,
// Always start fresh for HMR tests
reuseExistingServer: false,
stdout: 'pipe',
stderr: 'pipe',
// Dev server may take longer to start
timeout: 60000,
env: {
PORT: String(PORT),
},
},

projects: [
{
name: 'chromium-hmr',
use: {
...devices['Desktop Chrome'],
},
},
],
})
37 changes: 37 additions & 0 deletions e2e/react-start/split-exports/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { defineConfig, devices } from '@playwright/test'
import { getTestServerPort } from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }

const PORT = await getTestServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}`

export default defineConfig({
testDir: './tests',
// Exclude HMR tests - they require dev server (use playwright-hmr.config.ts)
testIgnore: '*-hmr.spec.ts',
workers: 1,
reporter: [['line']],

use: {
baseURL,
},

webServer: {
command: `pnpm build && pnpm start`,
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
env: {
PORT: String(PORT),
},
},

projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
})
5 changes: 5 additions & 0 deletions e2e/react-start/split-exports/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
156 changes: 156 additions & 0 deletions e2e/react-start/split-exports/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as ServerRequestImportRouteImport } from './routes/server-request-import'
import { Route as ReexportImportRouteImport } from './routes/reexport-import'
import { Route as DirectImportRouteImport } from './routes/direct-import'
import { Route as AliasImportRouteImport } from './routes/alias-import'
import { Route as IndexRouteImport } from './routes/index'

const ServerRequestImportRoute = ServerRequestImportRouteImport.update({
id: '/server-request-import',
path: '/server-request-import',
getParentRoute: () => rootRouteImport,
} as any)
const ReexportImportRoute = ReexportImportRouteImport.update({
id: '/reexport-import',
path: '/reexport-import',
getParentRoute: () => rootRouteImport,
} as any)
const DirectImportRoute = DirectImportRouteImport.update({
id: '/direct-import',
path: '/direct-import',
getParentRoute: () => rootRouteImport,
} as any)
const AliasImportRoute = AliasImportRouteImport.update({
id: '/alias-import',
path: '/alias-import',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/alias-import': typeof AliasImportRoute
'/direct-import': typeof DirectImportRoute
'/reexport-import': typeof ReexportImportRoute
'/server-request-import': typeof ServerRequestImportRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/alias-import': typeof AliasImportRoute
'/direct-import': typeof DirectImportRoute
'/reexport-import': typeof ReexportImportRoute
'/server-request-import': typeof ServerRequestImportRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/alias-import': typeof AliasImportRoute
'/direct-import': typeof DirectImportRoute
'/reexport-import': typeof ReexportImportRoute
'/server-request-import': typeof ServerRequestImportRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/alias-import'
| '/direct-import'
| '/reexport-import'
| '/server-request-import'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/alias-import'
| '/direct-import'
| '/reexport-import'
| '/server-request-import'
id:
| '__root__'
| '/'
| '/alias-import'
| '/direct-import'
| '/reexport-import'
| '/server-request-import'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AliasImportRoute: typeof AliasImportRoute
DirectImportRoute: typeof DirectImportRoute
ReexportImportRoute: typeof ReexportImportRoute
ServerRequestImportRoute: typeof ServerRequestImportRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/server-request-import': {
id: '/server-request-import'
path: '/server-request-import'
fullPath: '/server-request-import'
preLoaderRoute: typeof ServerRequestImportRouteImport
parentRoute: typeof rootRouteImport
}
'/reexport-import': {
id: '/reexport-import'
path: '/reexport-import'
fullPath: '/reexport-import'
preLoaderRoute: typeof ReexportImportRouteImport
parentRoute: typeof rootRouteImport
}
'/direct-import': {
id: '/direct-import'
path: '/direct-import'
fullPath: '/direct-import'
preLoaderRoute: typeof DirectImportRouteImport
parentRoute: typeof rootRouteImport
}
'/alias-import': {
id: '/alias-import'
path: '/alias-import'
fullPath: '/alias-import'
preLoaderRoute: typeof AliasImportRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AliasImportRoute: AliasImportRoute,
DirectImportRoute: DirectImportRoute,
ReexportImportRoute: ReexportImportRoute,
ServerRequestImportRoute: ServerRequestImportRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
12 changes: 12 additions & 0 deletions e2e/react-start/split-exports/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function getRouter() {
const router = createRouter({
routeTree,
scrollRestoration: true,
defaultPreload: 'intent',
})

return router
}
Loading
Loading