Skip to content

Commit 7be3776

Browse files
dake3601autofix-ci[bot]AlemTuzlak
authored
refactor(devtools-vite): migrate from Babel to oxc-parser + MagicString (#397)
* refactor(devtools-vite): migrate from Babel to oxc-parser + MagicString * changeset+knip fix * docs update * ci: apply automated fixes * refactor(devtools-vite): address CodeRabbit feedback on oxc migration - Extract shared TANSTACK_DEVTOOLS_PACKAGES constant; remove-devtools now recognizes preact/vue/svelte/angular packages (previously only react/solid/devtools were stripped). - Escape displayName/packageName via JSON.stringify when generating plugin injection code so quotes/backslashes do not break the output. - Skip appending a duplicate import when the plugin symbol is already imported from the same package. - collectJsx no longer crosses function boundaries; inner components keep their own propsName context and aren't annotated with an outer function's data-tsd-source. - Drop the redundant VariableDeclaration branch in visitFunctions; arrow/function expressions are reached via forEachChild, so the prior code walked them twice. - Add regression tests covering non-ASCII source positions (emoji, CJK) — oxc-parser napi bindings already return UTF-16 code-unit indices, so no offset conversion is needed. - Sync the devtools-production SKILL.md package list with the implementation. * chore(deps): bump oxc-parser to ^0.120.0 Verified empirically that the napi bindings continue to return UTF-16 code-unit indices (matching the assumption in inject-source.ts and the non-ASCII regression tests). All 167 devtools-vite tests pass on the new version; lint, typecheck, build, and knip clean. * ci: apply automated fixes * fix(devtools-vite): emit correct source positions for enhanced logs console-pipe-transform prepends a multi-line IIFE to root entry files before better-console-logs runs. Because both plugins are enforce:'pre' and execute in array order, enhanceConsoleLog was parsing the post-prepend source and reporting line numbers shifted past the end of the user's file -- 'Go to Source' links pointed nowhere. Swap the two plugin entries so enhanceConsoleLog sees the original source. Add a regression test asserting better-console-logs is registered before console-pipe-transform. * fix(devtools): snapshot data-tsd-source before mutating click state The click handler in SourceInspector called `setDisabledAfterClick(true)` before reading `highlightState.dataSource`. That signal write flipped `isActive` to false, triggering the highlight effect's `resetHighlight()` synchronously enough (via the chained `createStore` reactions) to clear `dataSource` before the subsequent reads. Result: the open-source fetch was issued with `?source=` empty and clipboard writes wrote an empty string. Capture `highlightState.dataSource` into a local before any state mutations, then use the captured value for both the clipboard path and the URL construction. * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>
1 parent 0ec5292 commit 7be3776

25 files changed

Lines changed: 1444 additions & 1134 deletions

.changeset/common-bottles-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools-vite': minor
3+
---
4+
5+
migrate from Babel to oxc-parser + MagicString

_artifacts/domain_map.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ skills:
231231

232232
- mistake: 'Source injection on spread props elements'
233233
mechanism: >
234-
The Babel transform skips elements with {...props} spread to avoid
234+
The AST transform skips elements with {...props} spread to avoid
235235
overwriting dynamic attributes. Agent might not realize source
236236
inspector wont work on those elements.
237237
source: 'packages/devtools-vite/src/inject-source.ts'

docs/architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ Adapters do not re-implement the devtools UI, manage settings, handle events, or
194194
`@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:
195195

196196
### Source injection (`@tanstack/devtools:inject-source`)
197-
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.
197+
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.
198198

199199
### Server event bus (`@tanstack/devtools:custom-server`)
200200
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).

docs/source-inspector.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ The feature only works in development. In production builds, source attributes a
1818

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

27-
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`.
27+
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`.
2828

2929
## Activating the Inspector
3030

docs/vite-plugin.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ The Vite plugin is composed of several sub-plugins, each handling a specific con
209209
```mermaid
210210
graph TD
211211
vite["@tanstack/devtools-vite"]
212-
vite --> source["Source Injection<br/><i>Babel → data-tsd-source attrs</i>"]
212+
vite --> source["Source Injection<br/><i>AST → data-tsd-source attrs</i>"]
213213
vite --> server["Server Event Bus<br/><i>WebSocket + SSE transport</i>"]
214214
vite --> strip["Production Stripping<br/><i>Remove devtools on build</i>"]
215215
vite --> pipe["Console Piping<br/><i>Client ↔ Server logs</i>"]
@@ -220,7 +220,7 @@ graph TD
220220

221221
### Go to Source
222222

223-
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.
223+
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.
224224

225225
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.
226226

packages/devtools-vite/package.json

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,15 @@
5656
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
5757
},
5858
"dependencies": {
59-
"@babel/core": "^7.28.4",
60-
"@babel/generator": "^7.28.3",
61-
"@babel/parser": "^7.28.4",
62-
"@babel/traverse": "^7.28.4",
63-
"@babel/types": "^7.28.4",
6459
"@tanstack/devtools-client": "workspace:*",
6560
"@tanstack/devtools-event-bus": "workspace:*",
6661
"chalk": "^5.6.2",
6762
"launch-editor": "^2.11.1",
63+
"magic-string": "^0.30.0",
64+
"oxc-parser": "^0.120.0",
6865
"picomatch": "^4.0.3"
6966
},
7067
"devDependencies": {
71-
"@types/babel__core": "^7.20.5",
72-
"@types/babel__generator": "^7.27.0",
73-
"@types/babel__traverse": "^7.28.0",
7468
"@types/picomatch": "^4.0.2",
7569
"happy-dom": "^20.0.0"
7670
}

packages/devtools-vite/skills/devtools-vite-plugin/SKILL.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,21 @@ From `packages/devtools-vite/src/index.ts`:
6767

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

8080
## Subsystem Details
8181

8282
### Source Injection
8383

84-
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.
84+
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.
8585

8686
**Key behaviors:**
8787

@@ -137,7 +137,7 @@ devtools({
137137

138138
### Enhanced Logging
139139

140-
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.
140+
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.
141141

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

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

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

264-
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.
264+
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.
265265

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

303303
- `packages/devtools-vite/src/plugin.ts` -- Main plugin factory with all sub-plugins and config type
304-
- `packages/devtools-vite/src/inject-source.ts` -- Babel transform for data-tsd-source injection
305-
- `packages/devtools-vite/src/enhance-logs.ts` -- Babel transform for enhanced console logs
304+
- `packages/devtools-vite/src/inject-source.ts` -- AST transform for data-tsd-source injection
305+
- `packages/devtools-vite/src/enhance-logs.ts` -- AST transform for enhanced console logs
306306
- `packages/devtools-vite/src/remove-devtools.ts` -- Production stripping transform
307307
- `packages/devtools-vite/src/virtual-console.ts` -- Console pipe runtime code generator
308308
- `packages/devtools-vite/src/editor.ts` -- Editor config type and launch-editor integration

packages/devtools-vite/skills/devtools-vite-plugin/references/vite-options.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ declare function defineDevtoolsConfig(
4343

4444
## `injectSource`
4545

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

4848
| Field | Type | Default | Description |
4949
| ------------------- | ------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -117,7 +117,7 @@ devtools({
117117

118118
## `enhancedLogs`
119119

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

122122
| Field | Type | Default | Description |
123123
| --------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { Node } from 'oxc-parser'
2+
3+
/**
4+
* Cache of keys that hold child nodes (objects/arrays) per AST node type.
5+
* Since oxc-parser produces AST via JSON.parse, every instance of a given
6+
* node type has the same set of keys, so we only need to discover them once.
7+
*/
8+
const childKeysCache = new Map<string, Array<string>>()
9+
10+
function getChildKeys(node: Node): Array<string> {
11+
let keys = childKeysCache.get(node.type)
12+
if (keys) return keys
13+
14+
keys = []
15+
for (const key in node) {
16+
if (key === 'type' || key === 'start' || key === 'end') continue
17+
// typeof null === 'object', so nullable node fields get cached too
18+
if (typeof (node as any)[key] === 'object') {
19+
keys.push(key)
20+
}
21+
}
22+
childKeysCache.set(node.type, keys)
23+
return keys
24+
}
25+
26+
/**
27+
* Iterate over the direct child nodes of an AST node.
28+
* Uses a per-type cache of which keys hold child nodes to avoid
29+
* allocating Object.entries() arrays on every call.
30+
*/
31+
export function forEachChild(node: Node, callback: (child: Node) => void) {
32+
const keys = getChildKeys(node)
33+
for (const key of keys) {
34+
const value = (node as any)[key]
35+
if (value === null) continue
36+
if (Array.isArray(value)) {
37+
for (const item of value) {
38+
if (typeof item === 'object' && item !== null && 'type' in item) {
39+
callback(item)
40+
}
41+
}
42+
} else if ('type' in value) {
43+
callback(value as Node)
44+
}
45+
}
46+
}
47+
48+
/**
49+
* Recursively walk AST nodes, calling `visitor` for each node with a `type`.
50+
*/
51+
export function walk(node: Node, visitor: (node: Node) => void) {
52+
visitor(node)
53+
forEachChild(node, (child) => walk(child, visitor))
54+
}

packages/devtools-vite/src/babel.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)