Skip to content
Merged
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/hip-moons-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/solid-devtools': patch
---

Fixed issue where solid devtools didn't work with solid start
4 changes: 2 additions & 2 deletions examples/solid/start/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"dependencies": {
"@solidjs/start": "^1.1.0",
"@tanstack/solid-devtools": "^0.2.2",
"solid-js": "^1.9.5",
"@tanstack/solid-devtools": "^0.3.0",
"solid-js": "^1.9.7",
"vinxi": "^0.5.7"
},
"engines": {
Expand Down
4 changes: 2 additions & 2 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"ignoreDependencies": ["@size-limit/preset-small-lib", "@faker-js/faker"],
"ignoreWorkspaces": ["examples/**"],
"workspaces": {
"packages/react-devtools": {
"ignore": []
"packages/solid-devtools": {
"ignore": ["**/core.tsx"]
}
}
}
54 changes: 54 additions & 0 deletions packages/solid-devtools/src/client-only.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
createMemo,
createSignal,
onMount,
sharedConfig,
splitProps,
untrack,
} from 'solid-js'
import { isServer } from 'solid-js/web'
import type { Component, ComponentProps, JSX, Setter } from 'solid-js'

/**
*
* Read more: https://docs.solidjs.com/solid-start/reference/client/client-only
*/
// not using Suspense
export default function clientOnly<T extends Component<any>>(
fn: () => Promise<{
default: T
}>,
options: { lazy?: boolean } = {},
) {
if (isServer)
return (props: ComponentProps<T> & { fallback?: JSX.Element }) =>
props.fallback

const [comp, setComp] = createSignal<T>()
!options.lazy && load(fn, setComp)
return (props: ComponentProps<T>) => {
let Comp: T | undefined
let m: boolean
const [, rest] = splitProps(props, ['fallback'])
options.lazy && load(fn, setComp)
if ((Comp = comp()) && !sharedConfig.context) return Comp(rest)
const [mounted, setMounted] = createSignal(!sharedConfig.context)
onMount(() => setMounted(true))
return createMemo(
() => (
(Comp = comp()),
(m = mounted()),
untrack(() => (Comp && m ? Comp(rest) : props.fallback))
),
)
}
}

function load<T>(
fn: () => Promise<{
default: T
}>,
setComp: Setter<T>,
) {
fn().then((m) => setComp(() => m.default))
}
130 changes: 130 additions & 0 deletions packages/solid-devtools/src/core.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { TanStackDevtoolsCore } from '@tanstack/devtools'
import { createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { Portal } from 'solid-js/web'
import type { JSX } from 'solid-js'
import type {
ClientEventBusConfig,
TanStackDevtoolsConfig,
TanStackDevtoolsPlugin,
} from '@tanstack/devtools'

type SolidPluginRender = JSX.Element | (() => JSX.Element)
const convertRender = (
el: HTMLDivElement | HTMLHeadingElement,
Component: SolidPluginRender,
) => (
<Portal mount={el}>
{typeof Component === 'function' ? <Component /> : Component}
</Portal>
)

export type TanStackDevtoolsSolidPlugin = Omit<
TanStackDevtoolsPlugin,
'render' | 'name'
> & {
/**
* The render function can be a SolidJS element or a function that returns a SolidJS element.
* If it's a function, it will be called to render the plugin, otherwise it will be rendered directly.
*
* Example:
* ```ts
* {
* render: () => <CustomPluginComponent />,
* }
* ```
* or
* ```ts
* {
* render: <CustomPluginComponent />,
* }
* ```
*/
render: SolidPluginRender
/**
* Name to be displayed in the devtools UI.
* If a string, it will be used as the plugin name.
* If a function, it will be called with the mount element.
*
* Example:
* ```ts
* {
* name: "Your Plugin",
* render: () => <CustomPluginComponent />,
* }
* ```
* or
* ```ts
* {
* name: <h1>Your Plugin title</h1>,
* render: () => <CustomPluginComponent />,
* }
* ```
*/
name: string | SolidPluginRender
}
export interface TanstackDevtoolsInit {
/**
* Array of plugins to be used in the devtools.
* Each plugin should have a `render` function that returns a React element or a function
*
* Example:
* ```jsx
* <TanstackDevtools
* plugins={[
* {
* id: "your-plugin-id",
* name: "Your Plugin",
* render: <CustomPluginComponent />,
* }
* ]}
* />
* ```
*/
plugins?: Array<TanStackDevtoolsSolidPlugin>
/**
* Configuration for the devtools shell. These configuration options are used to set the
* initial state of the devtools when it is started for the first time. Afterwards,
* the settings are persisted in local storage and changed through the settings panel.
*/
config?: Partial<TanStackDevtoolsConfig>
/**
* Configuration for the TanStack Devtools client event bus.
*/
eventBusConfig?: ClientEventBusConfig
}

export default function SolidDevtoolsCore({
config,
plugins,
eventBusConfig,
}: TanstackDevtoolsInit) {
const [devtools] = createSignal(
new TanStackDevtoolsCore({
config,
eventBusConfig,
plugins: plugins?.map((plugin) => ({
...plugin,
name:
typeof plugin.name === 'string'
? plugin.name
: // The check above confirms that `plugin.name` is of Render type
(el) => convertRender(el, plugin.name as SolidPluginRender),
render: (el: HTMLDivElement) => convertRender(el, plugin.render),
})),
}),
)
let devToolRef: HTMLDivElement | undefined
createEffect(() => {
devtools().setConfig({ config })
})
onMount(() => {
if (devToolRef) {
devtools().mount(devToolRef)

onCleanup(() => {
devtools().unmount()
})
}
})
return <div style={{ height: '100%' }} ref={devToolRef} />
}
131 changes: 3 additions & 128 deletions packages/solid-devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
@@ -1,130 +1,5 @@
import { TanStackDevtoolsCore } from '@tanstack/devtools'
import { createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { Portal } from 'solid-js/web'
import type { JSX } from 'solid-js'
import type {
ClientEventBusConfig,
TanStackDevtoolsConfig,
TanStackDevtoolsPlugin,
} from '@tanstack/devtools'
import clientOnly from './client-only'

type SolidPluginRender = JSX.Element | (() => JSX.Element)
const convertRender = (
el: HTMLDivElement | HTMLHeadingElement,
Component: SolidPluginRender,
) => (
<Portal mount={el}>
{typeof Component === 'function' ? <Component /> : Component}
</Portal>
export const TanstackDevtools = clientOnly(() =>
import('./core').then((m) => m),
)

export type TanStackDevtoolsSolidPlugin = Omit<
TanStackDevtoolsPlugin,
'render' | 'name'
> & {
/**
* The render function can be a SolidJS element or a function that returns a SolidJS element.
* If it's a function, it will be called to render the plugin, otherwise it will be rendered directly.
*
* Example:
* ```ts
* {
* render: () => <CustomPluginComponent />,
* }
* ```
* or
* ```ts
* {
* render: <CustomPluginComponent />,
* }
* ```
*/
render: SolidPluginRender
/**
* Name to be displayed in the devtools UI.
* If a string, it will be used as the plugin name.
* If a function, it will be called with the mount element.
*
* Example:
* ```ts
* {
* name: "Your Plugin",
* render: () => <CustomPluginComponent />,
* }
* ```
* or
* ```ts
* {
* name: <h1>Your Plugin title</h1>,
* render: () => <CustomPluginComponent />,
* }
* ```
*/
name: string | SolidPluginRender
}
interface TanstackDevtoolsInit {
/**
* Array of plugins to be used in the devtools.
* Each plugin should have a `render` function that returns a React element or a function
*
* Example:
* ```jsx
* <TanstackDevtools
* plugins={[
* {
* id: "your-plugin-id",
* name: "Your Plugin",
* render: <CustomPluginComponent />,
* }
* ]}
* />
* ```
*/
plugins?: Array<TanStackDevtoolsSolidPlugin>
/**
* Configuration for the devtools shell. These configuration options are used to set the
* initial state of the devtools when it is started for the first time. Afterwards,
* the settings are persisted in local storage and changed through the settings panel.
*/
config?: Partial<TanStackDevtoolsConfig>
/**
* Configuration for the TanStack Devtools client event bus.
*/
eventBusConfig?: ClientEventBusConfig
}

export const TanstackDevtools = ({
config,
plugins,
eventBusConfig,
}: TanstackDevtoolsInit) => {
const [devtools] = createSignal(
new TanStackDevtoolsCore({
config,
eventBusConfig,
plugins: plugins?.map((plugin) => ({
...plugin,
name:
typeof plugin.name === 'string'
? plugin.name
: // The check above confirms that `plugin.name` is of Render type
(el) => convertRender(el, plugin.name as SolidPluginRender),
render: (el: HTMLDivElement) => convertRender(el, plugin.render),
})),
}),
)
let devToolRef: HTMLDivElement | undefined
createEffect(() => {
devtools().setConfig({ config })
})
onMount(() => {
if (devToolRef) {
devtools().mount(devToolRef)

onCleanup(() => {
devtools().unmount()
})
}
})
return <div style={{ height: '100%' }} ref={devToolRef} />
}
Loading