Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
3f13190
feat(bundler-core): scaffold @tanstack/devtools-bundler-core package
AlemTuzlak Jul 14, 2026
e208dc0
feat(bundler-core): move framework-agnostic logic out of devtools-vite
AlemTuzlak Jul 14, 2026
1d51006
feat(bundler-core): add dev-state singleton for loader/plugin sharing
AlemTuzlak Jul 14, 2026
fcb4f8f
refactor(devtools-vite): consume @tanstack/devtools-bundler-core
AlemTuzlak Jul 14, 2026
2517593
test(bundler-core): relocate handleDevToolsRequest routing tests from…
AlemTuzlak Jul 14, 2026
feb8b7c
feat(devtools-rspack): scaffold package with skeleton exports
AlemTuzlak Jul 14, 2026
f0ca7d8
feat(devtools-rspack): transform pipeline loader
AlemTuzlak Jul 14, 2026
1ee17d9
feat(devtools-rspack): plugin (loader injection, event bus, dev middl…
AlemTuzlak Jul 14, 2026
4333469
chore(devtools): fix core import ordering + guard rspack package-mana…
AlemTuzlak Jul 14, 2026
04f546e
test(devtools-rspack): entry factory test
AlemTuzlak Jul 14, 2026
ac33c7f
test(devtools-rspack): react rspack example + e2e smoke
AlemTuzlak Jul 14, 2026
3d643ae
chore: changesets for devtools-bundler-core + devtools-rspack
AlemTuzlak Jul 14, 2026
960284a
fix(devtools-rspack): process @tanstack node_modules for connection p…
AlemTuzlak Jul 14, 2026
6bfceee
fix(devtools-rspack): eager event-bus connection, gate dep-refresh on…
AlemTuzlak Jul 14, 2026
3242451
fix(devtools-rspack): emit package-json-read on package.json change (…
AlemTuzlak Jul 14, 2026
f389d2b
chore(devtools): formatting + lint fixes
AlemTuzlak Jul 14, 2026
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/devtools-bundler-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools-bundler-core': minor
---

Introduce `@tanstack/devtools-bundler-core`, the framework-agnostic core (transforms, editor integration, package-manager, dev-state) shared by the TanStack devtools bundler plugins.
5 changes: 5 additions & 0 deletions .changeset/devtools-vite-bundler-core-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools-vite': patch
---

Internal refactor: consume `@tanstack/devtools-bundler-core` for shared logic. No public API or behavior change.
5 changes: 5 additions & 0 deletions .changeset/rspack-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools-rspack': minor
---

Add `@tanstack/devtools-rspack` β€” Rspack plugin with 1:1 feature parity to `@tanstack/devtools-vite` (source injection, enhanced logs, console piping, devtools removal on build, editor integration, event bus, package manager).
12 changes: 12 additions & 0 deletions examples/react-rspack/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React Rspack Example - TanStack Devtools</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
22 changes: 22 additions & 0 deletions examples/react-rspack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "react-rspack-example",
"private": true,
"scripts": {
"dev": "rspack serve",
"build": "rspack build --mode=production"
},
"dependencies": {
"@tanstack/devtools-rspack": "workspace:*",
"@tanstack/react-devtools": "workspace:*",
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@rspack/cli": "^1.5.0",
"@rspack/core": "^1.5.0",
"@rspack/dev-server": "^1.1.4",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"typescript": "^5.9.2"
}
}
48 changes: 48 additions & 0 deletions examples/react-rspack/rspack.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// @ts-check
import { HtmlRspackPlugin } from '@rspack/core'
import { devtools } from '@tanstack/devtools-rspack'

// NOTE: `@tanstack/devtools-rspack` ships as an ESM-only package, so this
// config is authored as ESM (`rspack.config.mjs`). A CommonJS config would
// need `const { devtools } = await import('@tanstack/devtools-rspack')`.

/** @type {(env: unknown, argv: { mode?: string }) => import('@rspack/core').Configuration} */
export default (_env, argv) => ({
mode: argv.mode === 'production' ? 'production' : 'development',
entry: './src/main.tsx',
// No source maps in production so the devtools-strip check is unambiguous
// (a source map would embed the original, un-stripped `TanStackDevtools` source).
devtool: argv.mode === 'production' ? false : 'eval-source-map',
plugins: [devtools(), new HtmlRspackPlugin({ template: './index.html' })],
devServer: {
port: 3100,
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
transform: {
react: {
runtime: 'automatic',
development: true,
},
},
},
},
},
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js'],
},
})
16 changes: 16 additions & 0 deletions examples/react-rspack/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useState } from 'react'

export default function App() {
const [count, setCount] = useState(0)

// enhanced-logs has something to transform
console.log('React Rspack example mounted, count =', count)

return (
<div style={{ padding: '20px', fontFamily: 'system-ui, sans-serif' }}>
<h1>TanStack Devtools React + Rspack Example</h1>
<p>Current count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
</div>
)
}
15 changes: 15 additions & 0 deletions examples/react-rspack/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createRoot } from 'react-dom/client'
import { TanStackDevtools } from '@tanstack/react-devtools'
import App from './App'

function Root() {
return (
<>
<App />
<TanStackDevtools />
</>
)
}

const root = createRoot(document.getElementById('root')!)
root.render(<Root />)
14 changes: 14 additions & 0 deletions examples/react-rspack/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["src"]
}
10 changes: 10 additions & 0 deletions packages/devtools-bundler-core/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @ts-check

import rootConfig from '../../eslint.config.js'

export default [
...rootConfig,
{
rules: {},
},
]
66 changes: 66 additions & 0 deletions packages/devtools-bundler-core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@tanstack/devtools-bundler-core",
"version": "0.0.1",
"description": "Framework-agnostic core shared by TanStack devtools bundler plugins",
"author": "Tanner Linsley",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/TanStack/devtools.git",
"directory": "packages/devtools-bundler-core"
},
"homepage": "https://tanstack.com/devtools",
"bugs": {
"url": "https://github.com/TanStack/devtools/issues"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"keywords": [
"devtools"
],
"type": "module",
"types": "dist/esm/index.d.ts",
"module": "dist/esm/index.js",
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
}
},
"./package.json": "./package.json"
},
"sideEffects": false,
"engines": {
"node": ">=18"
},
"files": [
"dist/",
"src"
],
"scripts": {
"clean": "premove ./build ./dist",
"lint:fix": "eslint ./src --fix",
"test:eslint": "eslint ./src",
"test:lib": "vitest",
"test:lib:dev": "pnpm test:lib --watch",
"test:types": "tsc",
"test:build": "publint --strict",
"build": "vite build"
},
"dependencies": {
"@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.120.0",
"picomatch": "^4.0.3"
},
"devDependencies": {
"@types/picomatch": "^4.0.2",
"happy-dom": "^20.0.0"
}
}
28 changes: 28 additions & 0 deletions packages/devtools-bundler-core/src/connection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest'
import {
getDevtoolsConnection,
getDevtoolsFileId,
setDevtoolsConnection,
setDevtoolsFileId,
} from './connection'

describe('connection singleton', () => {
it('returns sensible defaults before anything is set', () => {
expect(getDevtoolsConnection()).toEqual({
port: 4206,
host: 'localhost',
protocol: 'http',
})
expect(getDevtoolsFileId()).toBeNull()
})
it('round-trips connection and file id', () => {
setDevtoolsConnection({ port: 5000, host: '0.0.0.0', protocol: 'https' })
expect(getDevtoolsConnection()).toEqual({
port: 5000,
host: '0.0.0.0',
protocol: 'https',
})
setDevtoolsFileId('/app/src/main.tsx')
expect(getDevtoolsFileId()).toBe('/app/src/main.tsx')
})
})
40 changes: 40 additions & 0 deletions packages/devtools-bundler-core/src/connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export type DevtoolsConnection = {
port: number
host: string
protocol: 'http' | 'https'
}

let connection: DevtoolsConnection = {
port: 4206,
host: 'localhost',
protocol: 'http',
}
let devtoolsFileId: string | null = null

export const setDevtoolsConnection = (c: DevtoolsConnection) => {
connection = c
}
export const getDevtoolsConnection = (): DevtoolsConnection => connection
export const setDevtoolsFileId = (id: string | null) => {
devtoolsFileId = id
}
export const getDevtoolsFileId = (): string | null => devtoolsFileId

/**
* Origin of the bundler dev server (e.g. rspack-dev-server). This is where the
* `__tsd/*` middleware endpoints are mounted, and it is DISTINCT from the event
* bus connection above (which the devtools client connects to on port 4206).
* The dev-server origin is used to build the absolute `/__tsd/open-source` URL
* baked into enhanced console logs and the SSR-side `/__tsd/console-pipe/server`
* POST target.
*/
export type DevServerOrigin = {
port: number
host: string
protocol: 'http' | 'https'
}
let devServerOrigin: DevServerOrigin | null = null
export const setDevServerOrigin = (o: DevServerOrigin) => {
devServerOrigin = o
}
export const getDevServerOrigin = (): DevServerOrigin | null => devServerOrigin
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import chalk from 'chalk'
import { normalizePath } from 'vite'
import MagicString from 'magic-string'
import { parseSync } from 'oxc-parser'
import { normalizePath } from './normalize-path'
import { createLocMapper } from './offset-to-loc'
import { walk } from './ast-utils'

Expand Down
16 changes: 16 additions & 0 deletions packages/devtools-bundler-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export * from './normalize-path'
export * from './types'
export * from './ast-utils'
export * from './connection'
export * from './devtools-packages'
export * from './editor'
export * from './enhance-logs'
export * from './inject-plugin'
export * from './inject-source'
export * from './matcher'
export * from './offset-to-loc'
export * from './package-manager'
export * from './remove-devtools'
export * from './runtime-bridge'
export * from './virtual-console'
export * from './utils'
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { normalizePath } from 'vite'
import MagicString from 'magic-string'
import { parseSync } from 'oxc-parser'
import { normalizePath } from './normalize-path'
import { createLocMapper } from './offset-to-loc'
import { matcher } from './matcher'
import { forEachChild } from './ast-utils'
Expand Down
4 changes: 4 additions & 0 deletions packages/devtools-bundler-core/src/normalize-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Matches the backslash -> forward-slash half of Vite's normalizePath. Vite
// additionally runs path.posix.normalize, which we don't need for these
// substring checks.
export const normalizePath = (p: string): string => p.replace(/\\/g, '/')
Loading
Loading