diff --git a/.changeset/plenty-lions-camp.md b/.changeset/plenty-lions-camp.md
new file mode 100644
index 00000000..4ceb1dc8
--- /dev/null
+++ b/.changeset/plenty-lions-camp.md
@@ -0,0 +1,6 @@
+---
+'@tanstack/devtools-vite': patch
+'@tanstack/devtools': patch
+---
+
+improve devtools removal and fix issues with css
diff --git a/examples/react/start/vite.config.ts b/examples/react/start/vite.config.ts
index b79af319..35cd6a92 100644
--- a/examples/react/start/vite.config.ts
+++ b/examples/react/start/vite.config.ts
@@ -22,9 +22,7 @@ const config = defineConfig({
}),
Inspect(),
tailwindcss(),
- tanstackStart({
- customViteReactPlugin: true,
- }),
+ tanstackStart({}),
viteReact(),
],
})
diff --git a/packages/devtools-vite/src/remove-devtools.test.ts b/packages/devtools-vite/src/remove-devtools.test.ts
index 474b3698..925b0a4f 100644
--- a/packages/devtools-vite/src/remove-devtools.test.ts
+++ b/packages/devtools-vite/src/remove-devtools.test.ts
@@ -57,22 +57,20 @@ export default function DevtoolsExample() {
)
expect(output).toBe(
removeEmptySpace(`
- import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
- import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
- import {
+ import {
Link,
Outlet,
RouterProvider,
createRootRoute,
createRoute,
createRouter
- } from '@tanstack/react-router';
+ } from '@tanstack/react-router';
export default function DevtoolsExample() {
- return <>
+ return (<>
- >;
+ >);
}
@@ -131,9 +129,7 @@ export default function DevtoolsExample() {
)
expect(output).toBe(
removeEmptySpace(`
- import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
- import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
- import {
+ import {
Link,
Outlet,
RouterProvider,
@@ -144,9 +140,9 @@ export default function DevtoolsExample() {
export default function DevtoolsExample() {
- return <>
+ return ( <>
- >;
+ >);
}
@@ -205,9 +201,7 @@ export default function DevtoolsExample() {
)
expect(output).toBe(
removeEmptySpace(`
- import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
- import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
- import {
+ import {
Link,
Outlet,
RouterProvider,
@@ -218,13 +212,350 @@ export default function DevtoolsExample() {
export default function DevtoolsExample() {
- return <>
+ return (<>
- >;
+ >);
}
`),
)
})
+
+ test('it removes devtools and all possible variations of the plugins', () => {
+ const output = removeEmptySpace(
+ removeDevtools(
+ `
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
+import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
+import {
+ Link,
+ Outlet,
+ RouterProvider,
+ createRootRoute,
+ createRoute,
+ createRouter,
+} from '@tanstack/react-router'
+import * as Tools from '@tanstack/react-devtools'
+
+
+
+export default function DevtoolsExample() {
+ return (
+ <>
+ ,
+ },
+ {
+ name: 'TanStack Query',
+ render: () => ,
+ },
+ {
+ name: 'TanStack Router',
+ render: TanStackRouterDevtoolsPanel,
+ },
+ some()
+ ]}
+ />
+
+ >
+ )
+}`,
+ 'test.jsx',
+ ).code,
+ )
+
+ expect(output).toBe(
+ removeEmptySpace(`
+import {
+ Link,
+ Outlet,
+ RouterProvider,
+ createRootRoute,
+ createRoute,
+ createRouter
+} from '@tanstack/react-router' ;
+
+
+
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ >
+ );
+}
+ `),
+ )
+ })
+
+ describe('removing plugin imports', () => {
+ test('it removes the plugin import from the import array if multiple import identifiers exist', () => {
+ const output = removeEmptySpace(
+ removeDevtools(
+ `
+ import { ReactQueryDevtoolsPanel, test } from '@tanstack/react-query-devtools'
+
+import * as Tools from '@tanstack/react-devtools'
+
+
+
+export default function DevtoolsExample() {
+ return (
+ <>
+ ,
+ }
+ ]}
+ />
+
+ >
+ )
+}`,
+ 'test.jsx',
+ ).code,
+ )
+
+ expect(output).toBe(
+ removeEmptySpace(`
+ import { test } from '@tanstack/react-query-devtools';
+
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ >
+ );
+}
+ `),
+ )
+ })
+
+ test("it doesn't remove the whole import if imported with * as", () => {
+ const output = removeEmptySpace(
+ removeDevtools(
+ `
+ import * as Stuff from '@tanstack/react-query-devtools'
+
+import * as Tools from '@tanstack/react-devtools'
+
+
+
+export default function DevtoolsExample() {
+ return (
+ <>
+ ,
+ }
+ ]}
+ />
+
+ >
+ )
+}`,
+ 'test.jsx',
+ ).code,
+ )
+
+ expect(output).toBe(
+ removeEmptySpace(`
+ import * as Stuff from '@tanstack/react-query-devtools';
+
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ >
+ );
+}
+ `),
+ )
+ })
+
+ test('it removes the import completely if nothing is left', () => {
+ const output = removeEmptySpace(
+ removeDevtools(
+ `
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
+import * as Tools from '@tanstack/react-devtools'
+
+export default function DevtoolsExample() {
+ return (
+ <>
+ ,
+ }
+ ]}
+ />
+
+ >
+ )
+}`,
+ 'test.jsx',
+ ).code,
+ )
+
+ expect(output).toBe(
+ removeEmptySpace(`
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ >
+ );
+}
+ `),
+ )
+ })
+
+ test('it removes the import completely even if used as a function instead of jsx', () => {
+ const output = removeEmptySpace(
+ removeDevtools(
+ `
+ import { plugin } from '@tanstack/react-query-devtools'
+import * as Tools from '@tanstack/react-devtools'
+
+export default function DevtoolsExample() {
+ return (
+ <>
+
+
+ >
+ )
+}`,
+ 'test.jsx',
+ ).code,
+ )
+
+ expect(output).toBe(
+ removeEmptySpace(`
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ >
+ );
+}
+ `),
+ )
+ })
+
+ test('it removes the import completely even if used as a function inside of render', () => {
+ const output = removeEmptySpace(
+ removeDevtools(
+ `
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
+import * as Tools from '@tanstack/react-devtools'
+
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ }
+ ]}
+ />
+
+ >
+ )
+}`,
+ 'test.jsx',
+ ).code,
+ )
+
+ expect(output).toBe(
+ removeEmptySpace(`
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ >
+ );
+}
+ `),
+ )
+ })
+
+ test('it removes the import completely even if used as a reference inside of render', () => {
+ const output = removeEmptySpace(
+ removeDevtools(
+ `
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
+import * as Tools from '@tanstack/react-devtools'
+
+export default function DevtoolsExample() {
+ return (
+ <>
+
+
+ >
+ )
+}`,
+ 'test.jsx',
+ ).code,
+ )
+
+ expect(output).toBe(
+ removeEmptySpace(`
+export default function DevtoolsExample() {
+ return (
+ <>
+
+ >
+ );
+}
+ `),
+ )
+ })
+ })
})
diff --git a/packages/devtools-vite/src/remove-devtools.ts b/packages/devtools-vite/src/remove-devtools.ts
index 4efae6da..30c32b4c 100644
--- a/packages/devtools-vite/src/remove-devtools.ts
+++ b/packages/devtools-vite/src/remove-devtools.ts
@@ -1,6 +1,6 @@
import { gen, parse, trav } from './babel'
import type { t } from './babel'
-import type { types as Babel } from '@babel/core'
+import type { types as Babel, NodePath } from '@babel/core'
import type { ParseResult } from '@babel/parser'
const isTanStackDevtoolsImport = (source: string) =>
@@ -12,9 +12,92 @@ const getImportedNames = (importDecl: t.ImportDeclaration) => {
return importDecl.specifiers.map((spec) => spec.local.name)
}
+const getLeftoverImports = (node: NodePath) => {
+ const finalReferences: Array = []
+ node.traverse({
+ JSXAttribute(path) {
+ const node = path.node
+ const propName =
+ typeof node.name.name === 'string'
+ ? node.name.name
+ : node.name.name.name
+
+ if (
+ propName === 'plugins' &&
+ node.value?.type === 'JSXExpressionContainer' &&
+ node.value.expression.type === 'ArrayExpression'
+ ) {
+ const elements = node.value.expression.elements
+
+ elements.forEach((el) => {
+ if (el?.type === 'ObjectExpression') {
+ // { name: "something", render: ()=> }
+ const props = el.properties
+ const referencesToRemove = props
+ .map((prop) => {
+ if (
+ prop.type === 'ObjectProperty' &&
+ prop.key.type === 'Identifier' &&
+ prop.key.name === 'render'
+ ) {
+ const value = prop.value
+ // handle
+ if (
+ value.type === 'JSXElement' &&
+ value.openingElement.name.type === 'JSXIdentifier'
+ ) {
+ const elementName = value.openingElement.name.name
+ return elementName
+ }
+ // handle () => or function() { return }
+ if (
+ value.type === 'ArrowFunctionExpression' ||
+ value.type === 'FunctionExpression'
+ ) {
+ const body = value.body
+ if (
+ body.type === 'JSXElement' &&
+ body.openingElement.name.type === 'JSXIdentifier'
+ ) {
+ const elementName = body.openingElement.name.name
+ return elementName
+ }
+ }
+ // handle render: SomeComponent
+ if (value.type === 'Identifier') {
+ const elementName = value.name
+ return elementName
+ }
+
+ // handle render: someFunction()
+ if (
+ value.type === 'CallExpression' &&
+ value.callee.type === 'Identifier'
+ ) {
+ const elementName = value.callee.name
+ return elementName
+ }
+
+ return ''
+ }
+ return ''
+ })
+ .filter(Boolean)
+ finalReferences.push(...referencesToRemove)
+ }
+ })
+ }
+ },
+ })
+ return finalReferences
+}
+
const transform = (ast: ParseResult) => {
let didTransform = false
const devtoolsComponentNames = new Set()
+ const finalReferences: Array = []
+
+ const transformations: Array<() => void> = []
trav(ast, {
ImportDeclaration(path) {
@@ -23,7 +106,11 @@ const transform = (ast: ParseResult) => {
getImportedNames(path.node).forEach((name) =>
devtoolsComponentNames.add(name),
)
- path.remove()
+
+ transformations.push(() => {
+ path.remove()
+ })
+
didTransform = true
}
},
@@ -33,21 +120,53 @@ const transform = (ast: ParseResult) => {
opening.name.type === 'JSXIdentifier' &&
devtoolsComponentNames.has(opening.name.name)
) {
- path.remove()
+ const refs = getLeftoverImports(path)
+
+ finalReferences.push(...refs)
+ transformations.push(() => {
+ path.remove()
+ })
didTransform = true
}
-
if (
opening.name.type === 'JSXMemberExpression' &&
opening.name.object.type === 'JSXIdentifier' &&
devtoolsComponentNames.has(opening.name.object.name)
) {
- path.remove()
+ const refs = getLeftoverImports(path)
+ finalReferences.push(...refs)
+ transformations.push(() => {
+ path.remove()
+ })
didTransform = true
}
},
})
+ trav(ast, {
+ ImportDeclaration(path) {
+ const imports = path.node.specifiers
+ for (const imported of imports) {
+ if (imported.type === 'ImportSpecifier') {
+ if (finalReferences.includes(imported.local.name)) {
+ transformations.push(() => {
+ // remove the specifier
+ path.node.specifiers = path.node.specifiers.filter(
+ (spec) => spec !== imported,
+ )
+ // remove whole import if nothing is left
+ if (path.node.specifiers.length === 0) {
+ path.remove()
+ }
+ })
+ }
+ }
+ }
+ },
+ })
+
+ transformations.forEach((fn) => fn())
+
return didTransform
}
@@ -65,6 +184,7 @@ export function removeDevtools(code: string, id: string) {
}
return gen(ast, {
sourceMaps: true,
+ retainLines: true,
filename: id,
sourceFileName: filePath,
})