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

migrate from Babel to oxc-parser + MagicString
2 changes: 1 addition & 1 deletion _artifacts/domain_map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ skills:

- mistake: 'Source injection on spread props elements'
mechanism: >
The Babel transform skips elements with {...props} spread to avoid
The AST transform skips elements with {...props} spread to avoid
overwriting dynamic attributes. Agent might not realize source
inspector wont work on those elements.
source: 'packages/devtools-vite/src/inject-source.ts'
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Adapters do not re-implement the devtools UI, manage settings, handle events, or
`@tanstack/devtools-vite` is a collection of Vite plugins that enhance the development experience and clean up production builds. It returns an array of Vite plugins, each handling a specific concern:

### Source injection (`@tanstack/devtools:inject-source`)
Uses Babel to parse JSX/TSX files and injects `data-tsd-source` attributes on every JSX element. These attributes encode the file path, line number, and column number of each element in source code, which the source inspector feature uses to implement click-to-open-in-editor.
Parses JSX/TSX files with oxc-parser and injects `data-tsd-source` attributes on every JSX element via MagicString. These attributes encode the file path, line number, and column number of each element in source code, which the source inspector feature uses to implement click-to-open-in-editor.

### Server event bus (`@tanstack/devtools:custom-server`)
Starts a `ServerEventBus` on the Vite dev server. Also sets up middleware for the go-to-source editor integration and bidirectional console piping (client logs appear in the terminal, server logs appear in the browser).
Expand Down
4 changes: 2 additions & 2 deletions docs/source-inspector.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ The feature only works in development. In production builds, source attributes a

```mermaid
flowchart LR
A["Your JSX/TSX files"] -- "Babel transform" --> B["data-tsd-source<br/>attributes injected"]
A["Your JSX/TSX files"] -- "AST transform" --> B["data-tsd-source<br/>attributes injected"]
B -- "Hold inspect hotkey<br/>+ click element" --> C["Devtools reads<br/>data-tsd-source"]
C -- "HTTP request" --> D["Vite dev server"]
D -- "launch-editor" --> E["Opens file in editor<br/>at exact line"]
```

The Vite plugin uses Babel to parse your JSX/TSX files during development. It adds a `data-tsd-source="filepath:line:column"` attribute to every JSX element. When you activate the source inspector and click an element, the devtools reads this attribute and sends a request to the Vite dev server. The server then launches your editor at the specified file and line using `launch-editor`.
The Vite plugin uses oxc-parser to parse your JSX/TSX files during development. It adds a `data-tsd-source="filepath:line:column"` attribute to every JSX element via MagicString. When you activate the source inspector and click an element, the devtools reads this attribute and sends a request to the Vite dev server. The server then launches your editor at the specified file and line using `launch-editor`.

## Activating the Inspector

Expand Down
4 changes: 2 additions & 2 deletions docs/vite-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ The Vite plugin is composed of several sub-plugins, each handling a specific con
```mermaid
graph TD
vite["@tanstack/devtools-vite"]
vite --> source["Source Injection<br/><i>Babel → data-tsd-source attrs</i>"]
vite --> source["Source Injection<br/><i>AST → data-tsd-source attrs</i>"]
vite --> server["Server Event Bus<br/><i>WebSocket + SSE transport</i>"]
vite --> strip["Production Stripping<br/><i>Remove devtools on build</i>"]
vite --> pipe["Console Piping<br/><i>Client ↔ Server logs</i>"]
Expand All @@ -220,7 +220,7 @@ graph TD

### Go to Source

The "Go to Source" feature lets you click on any element in your browser and open its source file in your editor at the exact line where it's defined. It works by injecting `data-tsd-source` attributes into your components via a Babel transformation during development. These attributes encode the file path and line number of each element.
The "Go to Source" feature lets you click on any element in your browser and open its source file in your editor at the exact line where it's defined. It works by injecting `data-tsd-source` attributes into your components via an AST transformation during development. These attributes encode the file path and line number of each element.

To use it, activate the source inspector by holding the inspect hotkey (default: Shift+Alt+Ctrl/Meta). An overlay will highlight elements under your cursor and display their source location. Clicking on a highlighted element opens the corresponding file in your editor at the exact line, powered by `launch-editor` under the hood.

Expand Down
10 changes: 2 additions & 8 deletions packages/devtools-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,15 @@
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"dependencies": {
"@babel/core": "^7.28.4",
"@babel/generator": "^7.28.3",
"@babel/parser": "^7.28.4",
"@babel/traverse": "^7.28.4",
"@babel/types": "^7.28.4",
"@tanstack/devtools-client": "workspace:*",
"@tanstack/devtools-event-bus": "workspace:*",
"chalk": "^5.6.2",
"launch-editor": "^2.11.1",
"magic-string": "^0.30.0",
"oxc-parser": "^0.72.0",
Comment on lines +63 to +64
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check latest versions and security advisories for new dependencies

echo "=== Checking magic-string ==="
curl -s https://registry.npmjs.org/magic-string/latest | jq '.version'

echo "=== Checking oxc-parser ==="
curl -s https://registry.npmjs.org/oxc-parser/latest | jq '.version'

# Check if 0.72.0 exists for oxc-parser
echo "=== Checking oxc-parser@0.72.0 exists ==="
curl -s https://registry.npmjs.org/oxc-parser/0.72.0 | jq '.version // "NOT FOUND"'

Repository: TanStack/devtools

Length of output: 189


Consider updating oxc-parser to a more recent version.

While magic-string@^0.30.0 is current (latest is 0.30.21), oxc-parser@0.72.0 is significantly outdated; the latest version is 0.120.0. Check the changelog between 0.72.0 and a more recent release to ensure no critical fixes, breaking changes, or performance improvements are missed. If 0.72.0 is intentionally pinned for compatibility, document the rationale.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools-vite/package.json` around lines 63 - 64, The dependency
oxc-parser is pinned at ^0.72.0 and should be reviewed and updated; open
packages/devtools-vite/package.json, evaluate compatibility of oxc-parser
(symbol: "oxc-parser") against the current latest (≈0.120.0), run the package
upgrade (e.g., npm/yarn/pnpm) to a newer safe version, run the test suite and
local build to catch breaking changes, and if you must keep 0.72.0 document the
compatibility rationale beside the dependency (or in CHANGELOG/README) so future
reviewers know why it is pinned; also consider aligning or noting magic-string
(symbol: "magic-string") remains current.

"picomatch": "^4.0.3"
},
"devDependencies": {
"@types/babel__core": "^7.20.5",
"@types/babel__generator": "^7.27.0",
"@types/babel__traverse": "^7.28.0",
"@types/picomatch": "^4.0.2",
"happy-dom": "^18.0.1"
}
Expand Down
14 changes: 7 additions & 7 deletions packages/devtools-vite/skills/devtools-vite-plugin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,21 @@ From `packages/devtools-vite/src/index.ts`:

| Sub-plugin name | What it does | When active |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| `@tanstack/devtools:inject-source` | Babel transform adding `data-tsd-source` attrs to JSX | dev mode + `injectSource.enabled` |
| `@tanstack/devtools:inject-source` | AST transform adding `data-tsd-source` attrs to JSX | dev mode + `injectSource.enabled` |
| `@tanstack/devtools:config` | Reserved for future config modifications | serve command only |
| `@tanstack/devtools:custom-server` | Starts ServerEventBus, registers middleware for open-source/console-pipe endpoints | dev mode |
| `@tanstack/devtools:remove-devtools-on-build` | Strips devtools imports/JSX from production bundles | build command or production mode + `removeDevtoolsOnBuild` |
| `@tanstack/devtools:event-client-setup` | Marketplace: listens for install/add-plugin events via devtoolsEventClient | dev mode + serve + not CI |
| `@tanstack/devtools:console-pipe-transform` | Injects runtime console-pipe code into entry files | dev mode + serve + `consolePiping.enabled` |
| `@tanstack/devtools:better-console-logs` | Babel transform prepending source location to `console.log`/`console.error` | dev mode + `enhancedLogs.enabled` |
| `@tanstack/devtools:better-console-logs` | AST transform prepending source location to `console.log`/`console.error` | dev mode + `enhancedLogs.enabled` |
| `@tanstack/devtools:inject-plugin` | Detects which file imports TanStackDevtools (for marketplace injection) | dev mode + serve |
| `@tanstack/devtools:connection-injection` | Replaces `__TANSTACK_DEVTOOLS_PORT__`, `__TANSTACK_DEVTOOLS_HOST__`, `__TANSTACK_DEVTOOLS_PROTOCOL__` placeholders | dev mode + serve |

## Subsystem Details

### Source Injection

Adds `data-tsd-source="<relative-path>:<line>:<column>"` attributes to every JSX opening element via Babel. This powers the "Go to Source" feature -- hold the inspect hotkey (default: Shift+Alt+Ctrl/Meta), hover over elements, click to open in editor.
Adds `data-tsd-source="<relative-path>:<line>:<column>"` attributes to every JSX opening element via oxc-parser + MagicString. This powers the "Go to Source" feature -- hold the inspect hotkey (default: Shift+Alt+Ctrl/Meta), hover over elements, click to open in editor.

**Key behaviors:**

Expand Down Expand Up @@ -137,7 +137,7 @@ devtools({

### Enhanced Logging

Babel transform that prepends source location info to `console.log()` and `console.error()` calls. In the browser, this renders as a clickable "Go to Source" link. On the server, it shows `LOG <path>:<line>:<column>` in chalk colors.
AST transform that prepends source location info to `console.log()` and `console.error()` calls. In the browser, this renders as a clickable "Go to Source" link. On the server, it shows `LOG <path>:<line>:<column>` in chalk colors.

The transform inserts a spread of a conditional expression: `...(typeof window === 'undefined' ? serverLogMessage : browserLogMessage)` as the first argument of the console call.

Expand Down Expand Up @@ -261,7 +261,7 @@ Source injection, console piping, enhanced logging, the server event bus, and th

### 4. Source injection on spread-props elements (MEDIUM)

The Babel transform in `inject-source.ts` explicitly skips any JSX element that has a `{...props}` spread where `props` is the component's parameter name. This is intentional -- the spread would overwrite the injected `data-tsd-source` attribute. If source inspection doesn't work for a specific component, check if it spreads its props parameter.
The AST transform in `inject-source.ts` explicitly skips any JSX element that has a `{...props}` spread where `props` is the component's parameter name. This is intentional -- the spread would overwrite the injected `data-tsd-source` attribute. If source inspection doesn't work for a specific component, check if it spreads its props parameter.

```tsx
// data-tsd-source will NOT be injected on <div> here
Expand Down Expand Up @@ -301,8 +301,8 @@ These are registered on the Vite dev server (not the event bus server):
## Key Source Files

- `packages/devtools-vite/src/plugin.ts` -- Main plugin factory with all sub-plugins and config type
- `packages/devtools-vite/src/inject-source.ts` -- Babel transform for data-tsd-source injection
- `packages/devtools-vite/src/enhance-logs.ts` -- Babel transform for enhanced console logs
- `packages/devtools-vite/src/inject-source.ts` -- AST transform for data-tsd-source injection
- `packages/devtools-vite/src/enhance-logs.ts` -- AST transform for enhanced console logs
- `packages/devtools-vite/src/remove-devtools.ts` -- Production stripping transform
- `packages/devtools-vite/src/virtual-console.ts` -- Console pipe runtime code generator
- `packages/devtools-vite/src/editor.ts` -- Editor config type and launch-editor integration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ declare function defineDevtoolsConfig(

## `injectSource`

Controls source injection -- the Babel transform that adds `data-tsd-source` attributes to JSX elements for the "Go to Source" feature.
Controls source injection -- the AST transform that adds `data-tsd-source` attributes to JSX elements for the "Go to Source" feature.

| Field | Type | Default | Description |
| ------------------- | ------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down Expand Up @@ -117,7 +117,7 @@ devtools({

## `enhancedLogs`

Controls the Babel transform that prepends source location information to `console.log()` and `console.error()` calls.
Controls the AST transform that prepends source location information to `console.log()` and `console.error()` calls.

| Field | Type | Default | Description |
| --------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
54 changes: 54 additions & 0 deletions packages/devtools-vite/src/ast-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { Node } from 'oxc-parser'

/**
* Cache of keys that hold child nodes (objects/arrays) per AST node type.
* Since oxc-parser produces AST via JSON.parse, every instance of a given
* node type has the same set of keys, so we only need to discover them once.
*/
const childKeysCache = new Map<string, Array<string>>()

function getChildKeys(node: Node): Array<string> {
let keys = childKeysCache.get(node.type)
if (keys) return keys

keys = []
for (const key in node) {
if (key === 'type' || key === 'start' || key === 'end') continue
// typeof null === 'object', so nullable node fields get cached too
if (typeof (node as any)[key] === 'object') {
keys.push(key)
}
}
childKeysCache.set(node.type, keys)
return keys
}

/**
* Iterate over the direct child nodes of an AST node.
* Uses a per-type cache of which keys hold child nodes to avoid
* allocating Object.entries() arrays on every call.
*/
export function forEachChild(node: Node, callback: (child: Node) => void) {
const keys = getChildKeys(node)
for (const key of keys) {
const value = (node as any)[key]
if (value === null) continue
if (Array.isArray(value)) {
for (const item of value) {
if (typeof item === 'object' && item !== null && 'type' in item) {
callback(item)
}
}
} else if ('type' in value) {
callback(value as Node)
}
}
}

/**
* Recursively walk AST nodes, calling `visitor` for each node with a `type`.
*/
export function walk(node: Node, visitor: (node: Node) => void) {
visitor(node)
forEachChild(node, (child) => walk(child, visitor))
}
18 changes: 0 additions & 18 deletions packages/devtools-vite/src/babel.ts

This file was deleted.

Loading