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
5 changes: 5 additions & 0 deletions .changeset/mighty-oranges-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools-vite': patch
---

fix bugs with go to source not passing in column line properly to config
118 changes: 77 additions & 41 deletions docs/vite-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ npm install -D @tanstack/devtools-vite

Then add it as the *FIRST* plugin in your Vite config:

```javascript
```ts
import { devtools } from '@tanstack/devtools-vite'

export default {
Expand All @@ -33,7 +33,7 @@ And you're done!

You can configure the devtools plugin by passing options to the `devtools` function:

```javascript
```ts
import { devtools } from '@tanstack/devtools-vite'

export default {
Expand All @@ -46,63 +46,95 @@ export default {
}
```

- `eventBusConfig` - Configuration for the event bus that the devtools use to communicate with the client
### eventBusConfig

```ts
{
eventBusConfig: {
// port to run the event bus on
port: 1234,
// console log debug logs or not
debug: false
},
}
```
Configuration for the event bus that the devtools use to communicate with the client

- `appDir` - The directory where the react router app is located. Defaults to the "./src" relative to where vite.config is being defined.
```ts
import { devtools } from '@tanstack/devtools-vite'

```javascript
{
appDir: './src',
export default {
plugins: [
devtools({
eventBusConfig: {
// port to run the event bus on
port: 1234,
// console log debug logs or not
debug: false
},
}),
// ... rest of your plugins here
],
}

```

- `editor` - The open in editor configuration which has two fields, `name` and `open`,
`name` is the name of your editor, and `open` is a function that opens the editor with the given file and line number. You can implement your version for your editor as follows:
### editor

> [!IMPORTANT] `editor` is only needed for editors that are NOT VS Code, by default this works OOTB with VS Code.
The open in editor configuration which has two fields, `name` and `open`,
`name` is the name of your editor, and `open` is a function that opens the editor with the given file and line number. You can implement your version for your editor as follows:

```ts
const open = (file: string, line: number) => {
// implement your editor opening logic here
}
```ts
import { devtools } from '@tanstack/devtools-vite'

{
editor: {
name: 'vscode',
open
}
export default {
plugins: [
devtools({
editor: {
name: 'VSCode',
open: async (path, lineNumber, columnNumber) => {
const { exec } = await import('node:child_process')
exec(
// or windsurf/cursor/webstorm
`code -g "${(path).replaceAll('$', '\\$')}${lineNumber ? `:${lineNumber}` : ''}${columnNumber ? `:${columnNumber}` : ''}"`,
)
},
},
}),
// ... rest of your plugins here
],
}

```

> [!IMPORTANT] `editor` is only needed for editors that are NOT VS Code, by default this works OOTB with VS Code.

### enhancedLogs


- `enhancedLogs` - Configuration for enhanced logging. Defaults to enabled.
Configuration for enhanced logging. Defaults to enabled.

```ts
{
enhancedLogs: {
enabled: true
}
import { devtools } from '@tanstack/devtools-vite'

export default {
plugins: [
devtools({
enhancedLogs: {
enabled: true
}
}),
// ... rest of your plugins here
],
}
```

- `injectSource` - Configuration for source injection. Defaults to enabled.
### injectSource

Configuration for source injection. Defaults to enabled.


```ts
{
injectSource: {
enabled: true
}
import { devtools } from '@tanstack/devtools-vite'

export default {
plugins: [
devtools({
injectSource: {
enabled: true
}
}),
// ... rest of your plugins here
],
}
```

Expand All @@ -113,4 +145,8 @@ const open = (file: string, line: number) => {
Allows you to open the source location on anything in your browser by clicking on it.

To trigger this behavior you need the Devtools Vite plugin installed and configured and
the Panel available on the page. Simply click on any element while holding down the Shift and Ctrl (or Meta) keys.
the Panel available on the page. Simply click on any element while holding down the Shift and Ctrl (or Meta) keys.

### Advanced console logs

Allows you to go directly to the console log location directly from the browser/terminal
51 changes: 4 additions & 47 deletions packages/devtools-vite/src/editor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { normalizePath } from 'vite'
import { checkPath } from './utils.js'

type OpenSourceData = {
type: 'open-source'
data: {
Expand Down Expand Up @@ -31,62 +28,22 @@ export const DEFAULT_EDITOR_CONFIG: EditorConfig = {
open: async (path, lineNumber, columnNumber) => {
const { exec } = await import('node:child_process')
exec(
`code -g "${normalizePath(path).replaceAll('$', '\\$')}${lineNumber ? `:${lineNumber}` : ''}${columnNumber ? `:${columnNumber}` : ''}"`,
`code -g "${path.replaceAll('$', '\\$')}${lineNumber ? `:${lineNumber}` : ''}${columnNumber ? `:${columnNumber}` : ''}"`,
)
},
}

export const handleOpenSource = async ({
data,
openInEditor,
appDir,
}: {
data: OpenSourceData
appDir: string
openInEditor: EditorConfig['open']
}) => {
const { source, line, routeID } = data.data
const { source, line, column } = data.data
const lineNum = line ? `${line}` : undefined
const fs = await import('node:fs')
const path = await import('node:path')
const columnNum = column ? `${column}` : undefined
if (source) {
return openInEditor(source, lineNum)
}

if (routeID) {
const routePath = path.join(appDir, routeID)
const checkedPath = await checkPath(routePath)

if (!checkedPath) return
const { type, validPath } = checkedPath

const reactExtensions = ['tsx', 'jsx']
const allExtensions = ['ts', 'js', ...reactExtensions]
const isRoot = routeID === 'root'
const findFileByExtension = (prefix: string, filePaths: Array<string>) => {
const file = filePaths.find((file) =>
allExtensions.some((ext) => file === `${prefix}.${ext}`),
)
return file
}

if (isRoot) {
if (!fs.existsSync(appDir)) return
const filesInReactRouterPath = fs.readdirSync(appDir)
const rootFile = findFileByExtension('root', filesInReactRouterPath)
rootFile && openInEditor(path.join(appDir, rootFile), lineNum)
return
}

// If its not the root route, then we find the file or folder in the routes folder
// We know that the route ID is in the form of "routes/contact" or "routes/user.profile" when is not root
// so the ID already contains the "routes" segment, so we just need to find the file or folder in the routes folder
if (type === 'directory') {
const filesInFolderRoute = fs.readdirSync(validPath)
const routeFile = findFileByExtension('route', filesInFolderRoute)
routeFile && openInEditor(path.join(appDir, routeID, routeFile), lineNum)
return
}
return openInEditor(validPath, lineNum)
return openInEditor(source, lineNum, columnNum)
}
}
13 changes: 5 additions & 8 deletions packages/devtools-vite/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server'
import type { Plugin } from 'vite'

export type TanStackDevtoolsViteConfig = {
/** The directory where the react router app is located. Defaults to the "./src" relative to where vite.config is being defined. */
appDir?: string
/**
* Configuration for the editor integration. Defaults to opening in VS code
*/
Expand Down Expand Up @@ -46,7 +44,6 @@ export const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>

export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
let port = 5173
const appDir = args?.appDir || './src'
const enhancedLogsConfig = args?.enhancedLogs ?? { enabled: true }
const injectSourceConfig = args?.injectSource ?? { enabled: true }
const bus = new ServerEventBus(args?.eventBusConfig)
Expand Down Expand Up @@ -94,14 +91,15 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
})

const editor = args?.editor ?? DEFAULT_EDITOR_CONFIG
const openInEditor = async (
path: string | undefined,
lineNum: string | undefined,
const openInEditor: EditorConfig['open'] = async (
path,
lineNum,
columnNum,
) => {
if (!path) {
return
}
await editor.open(path, lineNum)
await editor.open(path, lineNum, columnNum)
}
server.middlewares.use((req, res, next) =>
handleDevToolsViteRequest(req, res, next, (parsedData) => {
Expand All @@ -110,7 +108,6 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
return handleOpenSource({
data: { type: data.type, data },
openInEditor,
appDir,
})
}
return
Expand Down
33 changes: 6 additions & 27 deletions packages/devtools-vite/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ export const handleDevToolsViteRequest = (
if (req.url?.includes('__tsd/open-source')) {
const searchParams = new URLSearchParams(req.url.split('?')[1])
const source = searchParams.get('source')
const line = searchParams.get('line')
const column = searchParams.get('column')
if (!source) {
return
}
const [file, line, column] = source.split(':')

cb({
type: 'open-source',
routine: 'open-source',
data: {
source: source
? normalizePath(`${process.cwd()}/${source}`)
: undefined,
source: file ? normalizePath(`${process.cwd()}/${file}`) : undefined,
line,
column,
},
Expand All @@ -46,25 +47,3 @@ export const handleDevToolsViteRequest = (
res.write('OK')
})
}

export async function checkPath(
routePath: string,
extensions = ['.tsx', '.jsx', '.ts', '.js'],
) {
const fs = await import('node:fs')
// Check if the path exists as a directory
if (fs.existsSync(routePath) && fs.lstatSync(routePath).isDirectory()) {
return { validPath: routePath, type: 'directory' } as const
}

// Check if the path exists as a file with one of the given extensions
for (const ext of extensions) {
const filePath = `${routePath}${ext}`
if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
return { validPath: filePath, type: 'file' } as const
}
}

// If neither a file nor a directory is found
return null
}