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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions e2e/vue-router/basic-esbuild-file-based/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import js from '@eslint/js'
import typescript from '@typescript-eslint/eslint-plugin'
import typescriptParser from '@typescript-eslint/parser'
import vue from 'eslint-plugin-vue'
import vueParser from 'vue-eslint-parser'

export default [
js.configs.recommended,
...vue.configs['flat/recommended'],
{
files: ['**/*.{js,jsx,ts,tsx,vue}'],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: typescriptParser,
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescript,
vue,
},
rules: {
// Vue specific rules
'vue/multi-word-component-names': 'off',
'vue/no-unused-vars': 'error',

// TypeScript rules
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',

// General rules
'no-unused-vars': 'off', // Let TypeScript handle this
},
},
{
files: ['**/*.vue'],
languageOptions: {
parser: vueParser,
parserOptions: {
parser: typescriptParser,
},
},
},
]
Comment on lines +1 to +49
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

Add router ESLint plugin configuration.

As per coding guidelines, files matching **/*.{js,ts,tsx} should implement ESLint rules for router best practices using the ESLint plugin router. This ESLint configuration is missing the TanStack router ESLint plugin.

Based on coding guidelines, the ESLint plugin for TanStack Router should be added:

  1. First, ensure the router ESLint plugin package is added to package.json devDependencies (see comment on package.json).

  2. Then, import and configure it in this file:

 import js from '@eslint/js'
 import typescript from '@typescript-eslint/eslint-plugin'
 import typescriptParser from '@typescript-eslint/parser'
 import vue from 'eslint-plugin-vue'
 import vueParser from 'vue-eslint-parser'
+import routerPlugin from '@tanstack/eslint-plugin-router'

 export default [
   js.configs.recommended,
   ...vue.configs['flat/recommended'],
+  ...routerPlugin.configs['flat/recommended'],
   {
🤖 Prompt for AI Agents
In e2e/vue-router/basic-esbuild-file-based/eslint.config.js lines 1-49, add the
TanStack Router ESLint plugin: import the router plugin at the top (after other
imports), ensure the router package is present in devDependencies, then include
it in the appropriate config block for files matching **/*.{js,jsx,ts,tsx,vue}
(or at least **/*.{js,ts,tsx}) by adding it to the plugins object and extending
or spreading its recommended/flat config (e.g., router.configs.recommended or
router.configs['flat/recommended']) so router best-practice rules are enabled
alongside existing JS/Vue/TypeScript rules.

24 changes: 24 additions & 0 deletions e2e/vue-router/basic-esbuild-file-based/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
html {
color-scheme: light dark;
}
* {
@apply border-gray-200 dark:border-gray-800;
}
body {
@apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="/dist/main.js"></script>
</body>
</html>
35 changes: 35 additions & 0 deletions e2e/vue-router/basic-esbuild-file-based/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "tanstack-router-e2e-vue-basic-esbuild-file-based",
"private": true,
"type": "module",
"scripts": {
"dev": "node src/esbuild.run.js dev --port 5601",
"build": "node src/esbuild.run.js build && vue-tsc --noEmit",
"preview": "node src/esbuild.run.js preview",
"start": "pnpm dev",
"test:e2e": "rm -rf port*.txt; playwright test --project=chromium"
},
"dependencies": {
"@tanstack/router-plugin": "workspace:^",
"@tanstack/vue-router": "workspace:^",
"@tanstack/vue-router-devtools": "workspace:^",
"@tanstack/zod-adapter": "workspace:^",
Comment on lines +13 to +16
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:* protocol for internal dependencies.

As per coding guidelines, internal dependencies should use the workspace:* protocol instead of workspace:^.

Apply this diff to align with the coding guidelines:

   "dependencies": {
-    "@tanstack/router-plugin": "workspace:^",
-    "@tanstack/vue-router": "workspace:^",
-    "@tanstack/vue-router-devtools": "workspace:^",
-    "@tanstack/zod-adapter": "workspace:^",
+    "@tanstack/router-plugin": "workspace:*",
+    "@tanstack/vue-router": "workspace:*",
+    "@tanstack/vue-router-devtools": "workspace:*",
+    "@tanstack/zod-adapter": "workspace:*",
     "postcss": "^8.5.1",
     "redaxios": "^0.5.1",
     "vue": "^3.5.16",
     "zod": "^3.24.2"
   },
   "devDependencies": {
     "@eslint/js": "^9.36.0",
     "@playwright/test": "^1.50.1",
-    "@tanstack/router-e2e-utils": "workspace:^",
+    "@tanstack/router-e2e-utils": "workspace:*",

Also applies to: 25-25

🤖 Prompt for AI Agents
In e2e/vue-router/basic-esbuild-file-based/package.json around lines 13-16 and
line 25, internal dependencies are using the incorrect protocol "workspace:^";
replace all occurrences of "workspace:^" with the "workspace:*" protocol for the
listed internal packages (e.g., @tanstack/router-plugin, @tanstack/vue-router,
@tanstack/vue-router-devtools, @tanstack/zod-adapter and any other internal
package at line 25) and save the file so package.json reflects workspace:* for
internal workspace references.

"postcss": "^8.5.1",
"redaxios": "^0.5.1",
"vue": "^3.5.16",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@playwright/test": "^1.50.1",
"@tanstack/router-e2e-utils": "workspace:^",
"@typescript-eslint/eslint-plugin": "^8.44.1",
"@typescript-eslint/parser": "^8.44.1",
"esbuild": "^0.25.0",
"esbuild-plugin-vue3": "^0.5.1",
"eslint-plugin-vue": "^9.33.0",
"typescript": "^5.8.3",
"vue-eslint-parser": "^9.4.3",
"vue-tsc": "^3.1.5"
}
Comment on lines +22 to +34
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

Add router ESLint plugin to devDependencies.

As per coding guidelines, ESLint rules for router best practices should be implemented using the ESLint plugin router. The corresponding package needs to be added to devDependencies.

Add the router ESLint plugin to devDependencies:

   "devDependencies": {
     "@eslint/js": "^9.36.0",
     "@playwright/test": "^1.50.1",
     "@tanstack/router-e2e-utils": "workspace:*",
+    "@tanstack/eslint-plugin-router": "workspace:*",
     "@typescript-eslint/eslint-plugin": "^8.44.1",

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

🤖 Prompt for AI Agents
In e2e/vue-router/basic-esbuild-file-based/package.json around lines 22 to 34,
devDependencies are missing the router ESLint plugin required by our coding
guidelines; add the package "eslint-plugin-vue-router" to devDependencies (match
project versioning policy), run install to update lockfile, and ensure the
ESLint config (eslintrc) later references "vue-router" in the plugins array so
router rules are enabled.

}
41 changes: 41 additions & 0 deletions e2e/vue-router/basic-esbuild-file-based/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test'
import {
getDummyServerPort,
getTestServerPort,
} from '@tanstack/router-e2e-utils'
import packageJson from './package.json' with { type: 'json' }

const PORT = await getTestServerPort(packageJson.name)
const EXTERNAL_PORT = await getDummyServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}`
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
workers: 1,

reporter: [['line']],

globalSetup: './tests/setup/global.setup.ts',
globalTeardown: './tests/setup/global.teardown.ts',

use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL,
},

webServer: {
command: `VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm build && VITE_NODE_ENV="test" VITE_EXTERNAL_PORT=${EXTERNAL_PORT} VITE_SERVER_PORT=${PORT} pnpm preview --port ${PORT}`,
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
},

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
})
5 changes: 5 additions & 0 deletions e2e/vue-router/basic-esbuild-file-based/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ref, defineComponent } from 'vue'
import { useBlocker, useNavigate } from '@tanstack/vue-router'

export const EditingAComponent = defineComponent({
setup() {
const navigate = useNavigate()
const input = ref('')

const blocker = useBlocker({
shouldBlockFn: ({ next }) => {
if (next.fullPath === '/editing-b' && input.value.length > 0) {
return true
}
return false
},
withResolver: true,
})

return () => (
<div>
<h1>Editing A</h1>
<label>
Enter your name:
<input
name="input"
value={input.value}
onInput={(e) =>
(input.value = (e.target as HTMLInputElement).value)
}
/>
</label>
<button onClick={() => navigate({ to: '/editing-b' })}>
Go to next step
</button>
{blocker.value.status === 'blocked' && (
<button onClick={() => blocker.value.proceed?.()}>Proceed</button>
)}
</div>
)
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ref, toValue, defineComponent } from 'vue'
import { useBlocker, useNavigate } from '@tanstack/vue-router'

export const EditingBComponent = defineComponent({
setup() {
const navigate = useNavigate()
const input = ref('')

const blocker = useBlocker({
shouldBlockFn: () => !!toValue(input),
withResolver: true,
})

return () => (
<div>
<h1>Editing B</h1>
<label>
Enter your name:
<input
name="input"
value={input.value}
onInput={(e) =>
(input.value = (e.target as HTMLInputElement).value)
}
/>
</label>
<button onClick={() => navigate({ to: '/editing-a' })}>Go back</button>
{blocker.value.status === 'blocked' && (
<button onClick={() => blocker.value.proceed?.()}>Proceed</button>
)}
</div>
)
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { Link } from '@tanstack/vue-router'
</script>

<template>
<div>
<p>This is the notFoundComponent configured on root route</p>
<Link to="/">Start Over</Link>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ref, onMounted, defineComponent } from 'vue'
import { useSearch, useNavigate } from '@tanstack/vue-router'

export const NotRemountDepsComponent = defineComponent({
setup() {
// Component-scoped ref - will be recreated on component remount
const mounts = ref(0)
const search = useSearch({ from: '/notRemountDeps' })
const navigate = useNavigate()

onMounted(() => {
mounts.value++
})

return () => (
<div class="p-2">
<button
onClick={() =>
navigate({
to: '/notRemountDeps',
search: {
searchParam: Math.random().toString(36).substring(2, 8),
},
})
}
>
Regenerate search param
</button>

<div>Search: {search.value.searchParam}</div>
<div data-testid="component-mounts">
Page component mounts: {mounts.value}
</div>
</div>
)
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { ErrorComponent } from '@tanstack/vue-router'
defineProps<{
error: any
}>()
Comment on lines +4 to +6
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 a more specific type instead of any.

The error: any type bypasses TypeScript's type safety, which violates the strict mode requirement. Consider using Error or unknown for better type safety.

As per coding guidelines, TypeScript strict mode with extensive type safety should be used.

Apply this diff:

 defineProps<{
-  error: any
+  error: Error | unknown
 }>()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defineProps<{
error: any
}>()
defineProps<{
error: Error | unknown
}>()
🤖 Prompt for AI Agents
In e2e/vue-router/basic-esbuild-file-based/src/components/PostErrorComponent.vue
around lines 4 to 6, the props definition uses error: any which breaks strict
TypeScript safety; change the prop type to a more specific type (e.g., Error or
unknown) and update the component to perform runtime narrowing if you choose
unknown (check instanceof Error or inspect properties) before accessing error
fields; update defineProps generic accordingly and adjust any downstream usage
in this component to satisfy the selected type (type guard or explicit cast).

</script>

<template>
<ErrorComponent :error="error" />
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ref, onMounted, defineComponent } from 'vue'
import { useSearch, useNavigate } from '@tanstack/vue-router'

// Module-scoped ref to persist across component remounts
const mounts = ref(0)

export const RemountDepsComponent = defineComponent({
setup() {
const search = useSearch({ from: '/remountDeps' })
const navigate = useNavigate()

onMounted(() => {
mounts.value++
})

return () => (
<div class="p-2">
<button
onClick={() =>
navigate({
to: '/remountDeps',
search: {
searchParam: Math.random().toString(36).substring(2, 8),
},
})
}
>
Regenerate search param
</button>

<div>Search: {search.value.searchParam}</div>
<div data-testid="component-mounts">
Page component mounts: {mounts.value}
</div>
</div>
)
},
})
Loading
Loading