@tanstack/devtools-vite 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -228,7 +228,7 @@ export function addSourceToJsx(code: string, id: string) {
228
228
  })
229
229
  const didTransform = transform(ast, location)
230
230
  if (!didTransform) {
231
- return { code }
231
+ return
232
232
  }
233
233
  return gen(ast, {
234
234
  sourceMaps: true,
@@ -237,6 +237,6 @@ export function addSourceToJsx(code: string, id: string) {
237
237
  sourceFileName: filePath,
238
238
  })
239
239
  } catch (e) {
240
- return { code }
240
+ return
241
241
  }
242
242
  }
package/src/plugin.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { ServerEventBus } from '@tanstack/devtools-event-bus/server'
2
+ import { normalizePath } from 'vite'
3
+ import chalk from 'chalk'
2
4
  import { handleDevToolsViteRequest } from './utils'
3
5
  import { DEFAULT_EDITOR_CONFIG, handleOpenSource } from './editor'
4
6
  import { removeDevtools } from './remove-devtools'
5
7
  import { addSourceToJsx } from './inject-source'
6
8
  import { enhanceConsoleLog } from './enhance-logs'
9
+ import type { Plugin } from 'vite'
7
10
  import type { EditorConfig } from './editor'
8
11
  import type { ServerEventBusConfig } from '@tanstack/devtools-event-bus/server'
9
- import type { Plugin } from 'vite'
10
12
 
11
13
  export type TanStackDevtoolsViteConfig = {
12
14
  /**
@@ -32,6 +34,12 @@ export type TanStackDevtoolsViteConfig = {
32
34
  * @default true
33
35
  */
34
36
  removeDevtoolsOnBuild?: boolean
37
+
38
+ /**
39
+ * Whether to log information to the console.
40
+ * @default true
41
+ */
42
+ logging?: boolean
35
43
  /**
36
44
  * Configuration for source injection.
37
45
  */
@@ -49,6 +57,7 @@ export const defineDevtoolsConfig = (config: TanStackDevtoolsViteConfig) =>
49
57
 
50
58
  export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
51
59
  let port = 5173
60
+ const logging = args?.logging ?? true
52
61
  const enhancedLogsConfig = args?.enhancedLogs ?? { enabled: true }
53
62
  const injectSourceConfig = args?.injectSource ?? { enabled: true }
54
63
  const removeDevtoolsOnBuild = args?.removeDevtoolsOnBuild ?? true
@@ -68,7 +77,7 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
68
77
  id.includes('dist') ||
69
78
  id.includes('build')
70
79
  )
71
- return code
80
+ return
72
81
 
73
82
  return addSourceToJsx(code, id)
74
83
  },
@@ -135,9 +144,15 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
135
144
  id.includes('dist') ||
136
145
  id.includes('build')
137
146
  )
138
- return code
139
-
140
- return removeDevtools(code, id)
147
+ return
148
+ const transform = removeDevtools(code, id)
149
+ if (!transform) return
150
+ if (logging) {
151
+ console.log(
152
+ `\n${chalk.greenBright(`[@tanstack/devtools-vite]`)} Removed devtools code from: ${id.replace(normalizePath(process.cwd()), '')}\n`,
153
+ )
154
+ }
155
+ return transform
141
156
  },
142
157
  },
143
158
  {
@@ -152,13 +167,11 @@ export const devtools = (args?: TanStackDevtoolsViteConfig): Array<Plugin> => {
152
167
  id.includes('node_modules') ||
153
168
  id.includes('?raw') ||
154
169
  id.includes('dist') ||
155
- id.includes('build')
170
+ id.includes('build') ||
171
+ !code.includes('console.')
156
172
  )
157
- return code
173
+ return
158
174
 
159
- if (!code.includes('console.')) {
160
- return code
161
- }
162
175
  return enhanceConsoleLog(code, id, port)
163
176
  },
164
177
  },
@@ -53,26 +53,24 @@ export default function DevtoolsExample() {
53
53
 
54
54
  `,
55
55
  'test.jsx',
56
- ).code,
56
+ )!.code,
57
57
  )
58
58
  expect(output).toBe(
59
59
  removeEmptySpace(`
60
- import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
61
- import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
62
- import {
60
+ import {
63
61
  Link,
64
62
  Outlet,
65
63
  RouterProvider,
66
64
  createRootRoute,
67
65
  createRoute,
68
66
  createRouter
69
- } from '@tanstack/react-router';
67
+ } from '@tanstack/react-router';
70
68
 
71
69
 
72
70
  export default function DevtoolsExample() {
73
- return <>
71
+ return (<>
74
72
  <RouterProvider router={router} />
75
- </>;
73
+ </>);
76
74
 
77
75
  }
78
76
 
@@ -127,13 +125,11 @@ export default function DevtoolsExample() {
127
125
 
128
126
  `,
129
127
  'test.jsx',
130
- ).code,
128
+ )!.code,
131
129
  )
132
130
  expect(output).toBe(
133
131
  removeEmptySpace(`
134
- import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
135
- import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
136
- import {
132
+ import {
137
133
  Link,
138
134
  Outlet,
139
135
  RouterProvider,
@@ -144,9 +140,9 @@ export default function DevtoolsExample() {
144
140
 
145
141
 
146
142
  export default function DevtoolsExample() {
147
- return <>
143
+ return ( <>
148
144
  <RouterProvider router={router} />
149
- </>;
145
+ </>);
150
146
 
151
147
  }
152
148
 
@@ -201,13 +197,11 @@ export default function DevtoolsExample() {
201
197
 
202
198
  `,
203
199
  'test.jsx',
204
- ).code,
200
+ )!.code,
205
201
  )
206
202
  expect(output).toBe(
207
203
  removeEmptySpace(`
208
- import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
209
- import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
210
- import {
204
+ import {
211
205
  Link,
212
206
  Outlet,
213
207
  RouterProvider,
@@ -218,13 +212,350 @@ export default function DevtoolsExample() {
218
212
 
219
213
 
220
214
  export default function DevtoolsExample() {
221
- return <>
215
+ return (<>
222
216
  <RouterProvider router={router} />
223
- </>;
217
+ </>);
224
218
 
225
219
  }
226
220
 
227
221
  `),
228
222
  )
229
223
  })
224
+
225
+ test('it removes devtools and all possible variations of the plugins', () => {
226
+ const output = removeEmptySpace(
227
+ removeDevtools(
228
+ `
229
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
230
+ import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
231
+ import {
232
+ Link,
233
+ Outlet,
234
+ RouterProvider,
235
+ createRootRoute,
236
+ createRoute,
237
+ createRouter,
238
+ } from '@tanstack/react-router'
239
+ import * as Tools from '@tanstack/react-devtools'
240
+
241
+
242
+
243
+ export default function DevtoolsExample() {
244
+ return (
245
+ <>
246
+ <Tools.TanStackDevtools
247
+ eventBusConfig={{
248
+ connectToServerBus: true,
249
+ }}
250
+ plugins={[
251
+ {
252
+ name: 'TanStack Query',
253
+ render: <ReactQueryDevtoolsPanel />,
254
+ },
255
+ {
256
+ name: 'TanStack Query',
257
+ render: () => <ReactQueryDevtoolsPanel />,
258
+ },
259
+ {
260
+ name: 'TanStack Router',
261
+ render: TanStackRouterDevtoolsPanel,
262
+ },
263
+ some()
264
+ ]}
265
+ />
266
+ <RouterProvider router={router} />
267
+ </>
268
+ )
269
+ }`,
270
+ 'test.jsx',
271
+ )!.code,
272
+ )
273
+
274
+ expect(output).toBe(
275
+ removeEmptySpace(`
276
+ import {
277
+ Link,
278
+ Outlet,
279
+ RouterProvider,
280
+ createRootRoute,
281
+ createRoute,
282
+ createRouter
283
+ } from '@tanstack/react-router' ;
284
+
285
+
286
+
287
+ export default function DevtoolsExample() {
288
+ return (
289
+ <>
290
+ <RouterProvider router={router} />
291
+ </>
292
+ );
293
+ }
294
+ `),
295
+ )
296
+ })
297
+
298
+ describe('removing plugin imports', () => {
299
+ test('it removes the plugin import from the import array if multiple import identifiers exist', () => {
300
+ const output = removeEmptySpace(
301
+ removeDevtools(
302
+ `
303
+ import { ReactQueryDevtoolsPanel, test } from '@tanstack/react-query-devtools'
304
+
305
+ import * as Tools from '@tanstack/react-devtools'
306
+
307
+
308
+
309
+ export default function DevtoolsExample() {
310
+ return (
311
+ <>
312
+ <Tools.TanStackDevtools
313
+ eventBusConfig={{
314
+ connectToServerBus: true,
315
+ }}
316
+ plugins={[
317
+ {
318
+ name: 'TanStack Query',
319
+ render: <ReactQueryDevtoolsPanel />,
320
+ }
321
+ ]}
322
+ />
323
+ <RouterProvider router={router} />
324
+ </>
325
+ )
326
+ }`,
327
+ 'test.jsx',
328
+ )!.code,
329
+ )
330
+
331
+ expect(output).toBe(
332
+ removeEmptySpace(`
333
+ import { test } from '@tanstack/react-query-devtools';
334
+
335
+ export default function DevtoolsExample() {
336
+ return (
337
+ <>
338
+ <RouterProvider router={router} />
339
+ </>
340
+ );
341
+ }
342
+ `),
343
+ )
344
+ })
345
+
346
+ test("it doesn't remove the whole import if imported with * as", () => {
347
+ const output = removeEmptySpace(
348
+ removeDevtools(
349
+ `
350
+ import * as Stuff from '@tanstack/react-query-devtools'
351
+
352
+ import * as Tools from '@tanstack/react-devtools'
353
+
354
+
355
+
356
+ export default function DevtoolsExample() {
357
+ return (
358
+ <>
359
+ <Tools.TanStackDevtools
360
+ eventBusConfig={{
361
+ connectToServerBus: true,
362
+ }}
363
+ plugins={[
364
+ {
365
+ name: 'TanStack Query',
366
+ render: <Stuff.ReactQueryDevtoolsPanel />,
367
+ }
368
+ ]}
369
+ />
370
+ <RouterProvider router={router} />
371
+ </>
372
+ )
373
+ }`,
374
+ 'test.jsx',
375
+ )!.code,
376
+ )
377
+
378
+ expect(output).toBe(
379
+ removeEmptySpace(`
380
+ import * as Stuff from '@tanstack/react-query-devtools';
381
+
382
+ export default function DevtoolsExample() {
383
+ return (
384
+ <>
385
+ <RouterProvider router={router} />
386
+ </>
387
+ );
388
+ }
389
+ `),
390
+ )
391
+ })
392
+
393
+ test('it removes the import completely if nothing is left', () => {
394
+ const output = removeEmptySpace(
395
+ removeDevtools(
396
+ `
397
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
398
+ import * as Tools from '@tanstack/react-devtools'
399
+
400
+ export default function DevtoolsExample() {
401
+ return (
402
+ <>
403
+ <Tools.TanStackDevtools
404
+ eventBusConfig={{
405
+ connectToServerBus: true,
406
+ }}
407
+ plugins={[
408
+ {
409
+ name: 'TanStack Query',
410
+ render: <ReactQueryDevtoolsPanel />,
411
+ }
412
+ ]}
413
+ />
414
+ <RouterProvider router={router} />
415
+ </>
416
+ )
417
+ }`,
418
+ 'test.jsx',
419
+ )!.code,
420
+ )
421
+
422
+ expect(output).toBe(
423
+ removeEmptySpace(`
424
+ export default function DevtoolsExample() {
425
+ return (
426
+ <>
427
+ <RouterProvider router={router} />
428
+ </>
429
+ );
430
+ }
431
+ `),
432
+ )
433
+ })
434
+
435
+ test('it removes the import completely even if used as a function instead of jsx', () => {
436
+ const output = removeEmptySpace(
437
+ removeDevtools(
438
+ `
439
+ import { plugin } from '@tanstack/react-query-devtools'
440
+ import * as Tools from '@tanstack/react-devtools'
441
+
442
+ export default function DevtoolsExample() {
443
+ return (
444
+ <>
445
+ <Tools.TanStackDevtools
446
+ eventBusConfig={{
447
+ connectToServerBus: true,
448
+ }}
449
+ plugins={[
450
+ {
451
+ name: 'TanStack Query',
452
+ render: plugin()
453
+ }
454
+ ]}
455
+ />
456
+ <RouterProvider router={router} />
457
+ </>
458
+ )
459
+ }`,
460
+ 'test.jsx',
461
+ )!.code,
462
+ )
463
+
464
+ expect(output).toBe(
465
+ removeEmptySpace(`
466
+ export default function DevtoolsExample() {
467
+ return (
468
+ <>
469
+ <RouterProvider router={router} />
470
+ </>
471
+ );
472
+ }
473
+ `),
474
+ )
475
+ })
476
+
477
+ test('it removes the import completely even if used as a function inside of render', () => {
478
+ const output = removeEmptySpace(
479
+ removeDevtools(
480
+ `
481
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
482
+ import * as Tools from '@tanstack/react-devtools'
483
+
484
+ export default function DevtoolsExample() {
485
+ return (
486
+ <>
487
+ <Tools.TanStackDevtools
488
+ eventBusConfig={{
489
+ connectToServerBus: true,
490
+ }}
491
+ plugins={[
492
+ {
493
+ name: 'TanStack Query',
494
+ render: () => <ReactQueryDevtoolsPanel />
495
+ }
496
+ ]}
497
+ />
498
+ <RouterProvider router={router} />
499
+ </>
500
+ )
501
+ }`,
502
+ 'test.jsx',
503
+ )!.code,
504
+ )
505
+
506
+ expect(output).toBe(
507
+ removeEmptySpace(`
508
+ export default function DevtoolsExample() {
509
+ return (
510
+ <>
511
+ <RouterProvider router={router} />
512
+ </>
513
+ );
514
+ }
515
+ `),
516
+ )
517
+ })
518
+
519
+ test('it removes the import completely even if used as a reference inside of render', () => {
520
+ const output = removeEmptySpace(
521
+ removeDevtools(
522
+ `
523
+ import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
524
+ import * as Tools from '@tanstack/react-devtools'
525
+
526
+ export default function DevtoolsExample() {
527
+ return (
528
+ <>
529
+ <Tools.TanStackDevtools
530
+ eventBusConfig={{
531
+ connectToServerBus: true,
532
+ }}
533
+ plugins={[
534
+ {
535
+ name: 'TanStack Query',
536
+ render: ReactQueryDevtoolsPanel
537
+ }
538
+ ]}
539
+ />
540
+ <RouterProvider router={router} />
541
+ </>
542
+ )
543
+ }`,
544
+ 'test.jsx',
545
+ )!.code,
546
+ )
547
+
548
+ expect(output).toBe(
549
+ removeEmptySpace(`
550
+ export default function DevtoolsExample() {
551
+ return (
552
+ <>
553
+ <RouterProvider router={router} />
554
+ </>
555
+ );
556
+ }
557
+ `),
558
+ )
559
+ })
560
+ })
230
561
  })
@@ -1,6 +1,6 @@
1
1
  import { gen, parse, trav } from './babel'
2
2
  import type { t } from './babel'
3
- import type { types as Babel } from '@babel/core'
3
+ import type { types as Babel, NodePath } from '@babel/core'
4
4
  import type { ParseResult } from '@babel/parser'
5
5
 
6
6
  const isTanStackDevtoolsImport = (source: string) =>
@@ -12,9 +12,92 @@ const getImportedNames = (importDecl: t.ImportDeclaration) => {
12
12
  return importDecl.specifiers.map((spec) => spec.local.name)
13
13
  }
14
14
 
15
+ const getLeftoverImports = (node: NodePath<t.JSXElement>) => {
16
+ const finalReferences: Array<string> = []
17
+ node.traverse({
18
+ JSXAttribute(path) {
19
+ const node = path.node
20
+ const propName =
21
+ typeof node.name.name === 'string'
22
+ ? node.name.name
23
+ : node.name.name.name
24
+
25
+ if (
26
+ propName === 'plugins' &&
27
+ node.value?.type === 'JSXExpressionContainer' &&
28
+ node.value.expression.type === 'ArrayExpression'
29
+ ) {
30
+ const elements = node.value.expression.elements
31
+
32
+ elements.forEach((el) => {
33
+ if (el?.type === 'ObjectExpression') {
34
+ // { name: "something", render: ()=> <Component /> }
35
+ const props = el.properties
36
+ const referencesToRemove = props
37
+ .map((prop) => {
38
+ if (
39
+ prop.type === 'ObjectProperty' &&
40
+ prop.key.type === 'Identifier' &&
41
+ prop.key.name === 'render'
42
+ ) {
43
+ const value = prop.value
44
+ // handle <ReactRouterPanel />
45
+ if (
46
+ value.type === 'JSXElement' &&
47
+ value.openingElement.name.type === 'JSXIdentifier'
48
+ ) {
49
+ const elementName = value.openingElement.name.name
50
+ return elementName
51
+ }
52
+ // handle () => <ReactRouterPanel /> or function() { return <ReactRouterPanel /> }
53
+ if (
54
+ value.type === 'ArrowFunctionExpression' ||
55
+ value.type === 'FunctionExpression'
56
+ ) {
57
+ const body = value.body
58
+ if (
59
+ body.type === 'JSXElement' &&
60
+ body.openingElement.name.type === 'JSXIdentifier'
61
+ ) {
62
+ const elementName = body.openingElement.name.name
63
+ return elementName
64
+ }
65
+ }
66
+ // handle render: SomeComponent
67
+ if (value.type === 'Identifier') {
68
+ const elementName = value.name
69
+ return elementName
70
+ }
71
+
72
+ // handle render: someFunction()
73
+ if (
74
+ value.type === 'CallExpression' &&
75
+ value.callee.type === 'Identifier'
76
+ ) {
77
+ const elementName = value.callee.name
78
+ return elementName
79
+ }
80
+
81
+ return ''
82
+ }
83
+ return ''
84
+ })
85
+ .filter(Boolean)
86
+ finalReferences.push(...referencesToRemove)
87
+ }
88
+ })
89
+ }
90
+ },
91
+ })
92
+ return finalReferences
93
+ }
94
+
15
95
  const transform = (ast: ParseResult<Babel.File>) => {
16
96
  let didTransform = false
17
97
  const devtoolsComponentNames = new Set()
98
+ const finalReferences: Array<string> = []
99
+
100
+ const transformations: Array<() => void> = []
18
101
 
19
102
  trav(ast, {
20
103
  ImportDeclaration(path) {
@@ -23,7 +106,11 @@ const transform = (ast: ParseResult<Babel.File>) => {
23
106
  getImportedNames(path.node).forEach((name) =>
24
107
  devtoolsComponentNames.add(name),
25
108
  )
26
- path.remove()
109
+
110
+ transformations.push(() => {
111
+ path.remove()
112
+ })
113
+
27
114
  didTransform = true
28
115
  }
29
116
  },
@@ -33,21 +120,53 @@ const transform = (ast: ParseResult<Babel.File>) => {
33
120
  opening.name.type === 'JSXIdentifier' &&
34
121
  devtoolsComponentNames.has(opening.name.name)
35
122
  ) {
36
- path.remove()
123
+ const refs = getLeftoverImports(path)
124
+
125
+ finalReferences.push(...refs)
126
+ transformations.push(() => {
127
+ path.remove()
128
+ })
37
129
  didTransform = true
38
130
  }
39
-
40
131
  if (
41
132
  opening.name.type === 'JSXMemberExpression' &&
42
133
  opening.name.object.type === 'JSXIdentifier' &&
43
134
  devtoolsComponentNames.has(opening.name.object.name)
44
135
  ) {
45
- path.remove()
136
+ const refs = getLeftoverImports(path)
137
+ finalReferences.push(...refs)
138
+ transformations.push(() => {
139
+ path.remove()
140
+ })
46
141
  didTransform = true
47
142
  }
48
143
  },
49
144
  })
50
145
 
146
+ trav(ast, {
147
+ ImportDeclaration(path) {
148
+ const imports = path.node.specifiers
149
+ for (const imported of imports) {
150
+ if (imported.type === 'ImportSpecifier') {
151
+ if (finalReferences.includes(imported.local.name)) {
152
+ transformations.push(() => {
153
+ // remove the specifier
154
+ path.node.specifiers = path.node.specifiers.filter(
155
+ (spec) => spec !== imported,
156
+ )
157
+ // remove whole import if nothing is left
158
+ if (path.node.specifiers.length === 0) {
159
+ path.remove()
160
+ }
161
+ })
162
+ }
163
+ }
164
+ }
165
+ },
166
+ })
167
+
168
+ transformations.forEach((fn) => fn())
169
+
51
170
  return didTransform
52
171
  }
53
172
 
@@ -61,14 +180,15 @@ export function removeDevtools(code: string, id: string) {
61
180
  })
62
181
  const didTransform = transform(ast)
63
182
  if (!didTransform) {
64
- return { code }
183
+ return
65
184
  }
66
185
  return gen(ast, {
67
186
  sourceMaps: true,
187
+ retainLines: true,
68
188
  filename: id,
69
189
  sourceFileName: filePath,
70
190
  })
71
191
  } catch (e) {
72
- return { code }
192
+ return
73
193
  }
74
194
  }