Skip to content

Commit

Permalink
feat: Display custom values added with registerGraph() in the inspe…
Browse files Browse the repository at this point in the history
…ctor
  • Loading branch information
thetarnav committed Jan 2, 2025
1 parent ac09ddd commit 4e32e04
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 221 deletions.
6 changes: 6 additions & 0 deletions .changeset/moody-ants-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@solid-devtools/debugger": minor
"@solid-devtools/frontend": minor
---

Display custom values added with `registerGraph()` in the inspector
3 changes: 3 additions & 0 deletions examples/sandbox/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ const createComponent = (content: () => s.JSX.Element) => {
}

const App: s.Component = () => {

s.DEV?.registerGraph({value: {foo: 123}, name: 'my_custom_value'})

const [count, setCount] = s.createSignal(0)
const [showEven, setShowEven] = s.createSignal(false)
const fnSig = s.createSignal({fn: () => {}}, {equals: (a, b) => a.fn === b.fn})
Expand Down
54 changes: 25 additions & 29 deletions packages/debugger/src/inspector/inspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,38 +164,35 @@ let PropsMap: ObservedPropsMap

const $INSPECTOR = Symbol('inspector')

const typeToObjectTypeMap = {
[NodeType.Signal]: ObjectType.Signal,
[NodeType.Memo]: ObjectType.Owner,
[NodeType.Store]: ObjectType.Store,
}

function mapSourceValue(
node: Solid.SourceMapValue | Solid.Memo | Solid.Store,
node: Solid.SourceMapValue | Solid.Computation,
handler: (nodeId: NodeID, value: unknown) => void,
isMemo: boolean,
): Mapped.Signal | null {
const type = isMemo
? NodeType.Memo
: utils.isSolidStore(node)
? NodeType.Store
: utils.isSolidSignal(node)
? NodeType.Signal
: null

if (!type) return null

const {value} = node,
id = getSdtId(node, typeToObjectTypeMap[type])
): Mapped.SourceValue | null {

let type = utils.getNodeType(node)
let {value} = node
let id: NodeID

// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (type) {
case NodeType.Memo: id = getSdtId(node as Solid.Memo, ObjectType.Owner) ;break
case NodeType.Signal: id = getSdtId(node as Solid.Signal, ObjectType.Signal) ;break
case NodeType.Store: id = getSdtId(node as Solid.Store, ObjectType.Store) ;break
case NodeType.CustomValue: id = getSdtId(node as Solid.SourceMapValue, ObjectType.CustomValue) ;break
default:
return null
}

ValueMap.add(`${ValueItemType.Signal}:${id}`, () => node.value)

if (type !== NodeType.Store) observeValueUpdate(node, v => handler(id, v), $INSPECTOR)
if (type === NodeType.Memo || type === NodeType.Signal) {
observeValueUpdate(node, v => handler(id, v), $INSPECTOR)
}

return {
type,
name: utils.getNodeName(node),
id,
type: type,
name: utils.getNodeName(node),
id: id,
value: encodeValue(value, false),
}
}
Expand Down Expand Up @@ -299,7 +296,7 @@ export const collectOwnerDetails = /*#__PURE__*/ untrackedCallback(function (
// marge component with refresh memo
const refresh = utils.getComponentRefreshNode(owner)
if (refresh) {

sourceMap = refresh.sourceMap
owned = refresh.owned
getValue = () => refresh.value
Expand Down Expand Up @@ -334,16 +331,15 @@ export const collectOwnerDetails = /*#__PURE__*/ untrackedCallback(function (
// map signals
if (sourceMap) {
for (const signal of sourceMap) {
const mapped = mapSourceValue(signal, onSignalUpdate, false)
const mapped = mapSourceValue(signal, onSignalUpdate)
mapped && details.signals.push(mapped)
}
}

// map memos
if (owned) {
for (const node of owned) {
if (!utils.isSolidMemo(node)) continue
const mapped = mapSourceValue(node, onSignalUpdate, true)
const mapped = mapSourceValue(node, onSignalUpdate)
mapped && details.signals.push(mapped)
}
}
Expand Down
91 changes: 47 additions & 44 deletions packages/debugger/src/inspector/test/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import '../../setup.ts'

import {
createComputed,
createMemo,
createRenderEffect,
createRoot,
createSignal,
JSX,
} from 'solid-js'
import * as s from 'solid-js'
import {beforeEach, describe, expect, it, vi} from 'vitest'
import {getObjectById, getSdtId, ObjectType} from '../../main/id.ts'
import setup from '../../main/setup.ts'
Expand All @@ -22,33 +15,36 @@ vi.mock('../../main/get-id', () => ({getNewSdtId: () => '#' + mockLAST_ID++}))

describe('collectOwnerDetails', () => {
it('collects focused owner details', () => {
createRoot(dispose => {
const [s] = createSignal(0, {name: 'source'})
s.createRoot(dispose => {
const [source] = s.createSignal(0, {name: 'source'})

let memo!: Solid.Owner
const div = document.createElement('div')

createComputed(
() => {
const focused = createMemo(
() => {
memo = setup.solid.getOwner()!
s()
createSignal(div, {name: 'element'})
const m = createMemo(() => 0, undefined, {name: 'memo'})
createRenderEffect(m, undefined, {name: 'render'})
return 'value'
},
undefined,
{name: 'focused'},
)
focused()
},
undefined,
{name: 'WRAPPER'},
)
s.createComputed(() => {

const focused = s.createMemo(() => {

memo = setup.solid.getOwner()!

source()

s.DEV!.registerGraph({
value: {foo: 123},
name: 'custom value',
})
s.createSignal(div, {name: 'element'})
const m = s.createMemo(() => 0, undefined, {name: 'memo'})
s.createRenderEffect(m, undefined, {name: 'render'})

return 'value'
}, undefined, {name: 'focused'})

const [signalB] = memo.sourceMap as [Solid.Signal]
focused()

}, undefined, {name: 'WRAPPER'})

const [customValue, signalB] = memo.sourceMap as [Solid.SourceMapValue, Solid.Signal]
const [innerMemo] = memo.owned as [Solid.Memo, Solid.Computation]

const {details, valueMap} = collectOwnerDetails(memo, {
Expand All @@ -67,11 +63,17 @@ describe('collectOwnerDetails', () => {
type: NodeType.Memo,
value: [[ValueType.String, 'value']],
signals: [
{
type: NodeType.CustomValue,
id: getSdtId(customValue, ObjectType.CustomValue),
name: 'custom value',
value: [[ValueType.Object, 1]],
},
{
type: NodeType.Signal,
id: getSdtId(signalB, ObjectType.Signal),
name: 'element',
value: [[ValueType.Element, '#3:div']],
value: [[ValueType.Element, '#4:div']],
},
{
type: NodeType.Memo,
Expand All @@ -82,27 +84,28 @@ describe('collectOwnerDetails', () => {
],
} satisfies Mapped.OwnerDetails)

expect(valueMap.get(`signal:${getSdtId(customValue, ObjectType.CustomValue)}`)).toBeTruthy()
expect(valueMap.get(`signal:${getSdtId(signalB, ObjectType.Signal)}`)).toBeTruthy()
expect(valueMap.get(`signal:${getSdtId(innerMemo, ObjectType.Owner)}`)).toBeTruthy()

expect(getObjectById('#3', ObjectType.Element)).toBe(div)
expect(getObjectById('#4', ObjectType.Element)).toBe(div)

dispose()
})
})

it('component props', () => {
createRoot(dispose => {
s.createRoot(dispose => {
let owner!: Solid.Owner
const TestComponent = (props: {
count: number
children: JSX.Element
children: s.JSX.Element
nested: {foo: number; bar: string}
}) => {
owner = setup.solid.getOwner()!
return <div>{props.children}</div>
}
createRenderEffect(() => (
s.createRenderEffect(() => (
<TestComponent count={123} nested={{foo: 1, bar: '2'}}>
<button>Click me</button>
</TestComponent>
Expand Down Expand Up @@ -150,13 +153,13 @@ describe('collectOwnerDetails', () => {
})

it('dynamic component props', () => {
createRoot(dispose => {
s.createRoot(dispose => {
let owner!: Solid.Owner
const Button = (props: JSX.ButtonHTMLAttributes<HTMLButtonElement>) => {
const Button = (props: s.JSX.ButtonHTMLAttributes<HTMLButtonElement>) => {
owner = setup.solid.getOwner()!
return <button {...props}>Click me</button>
}
createRenderEffect(() => {
s.createRenderEffect(() => {
const props = () =>
({
onClick: () => {
Expand Down Expand Up @@ -205,11 +208,11 @@ describe('collectOwnerDetails', () => {
})

it('listens to value updates', () => {
createRoot(dispose => {
s.createRoot(dispose => {
let owner!: Solid.Owner

const [count, setCount] = createSignal(0)
createMemo(() => {
const [count, setCount] = s.createSignal(0)
s.createMemo(() => {
owner = setup.solid.getOwner()!
return count()
})
Expand Down Expand Up @@ -241,10 +244,10 @@ describe('collectOwnerDetails', () => {
})

it('listens to signal updates', () => {
createRoot(dispose => {
s.createRoot(dispose => {
const owner = setup.solid.getOwner()!
const [, setCount] = createSignal(0) // id: "0"
const [, setCount2] = createSignal(0) // id: "1"
const [, setCount] = s.createSignal(0) // id: "0"
const [, setCount2] = s.createSignal(0) // id: "1"

const onValueUpdate = vi.fn()
collectOwnerDetails(owner, {
Expand Down
52 changes: 27 additions & 25 deletions packages/debugger/src/main/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,41 @@ export enum TreeWalkerMode {
export const DEFAULT_WALKER_MODE = TreeWalkerMode.Components

export enum NodeType {
Root = 'root',
Component = 'component',
Element = 'element',
Effect = 'effect',
Render = 'render',
Memo = 'memo',
Computation = 'computation',
Refresh = 'refresh',
Context = 'context',
CatchError = 'catchError',
Signal = 'signal',
Store = 'store',
Root = 'ROOT',
Component = 'COMPONENT',
Element = 'ELEMENT',
Effect = 'EFFECT',
Render = 'RENDER',
Memo = 'MEMO',
Computation = 'COMPUTATION',
Refresh = 'REFRESH',
Context = 'CONTEXT',
CatchError = 'CATCH_ERROR',
Signal = 'SIGNAL',
Store = 'STORE',
CustomValue = 'CUSTOM_VALUE',
}

export const NODE_TYPE_NAMES: Readonly<Record<NodeType, string>> = {
[NodeType.Root]: 'Root',
[NodeType.Component]: 'Component',
[NodeType.Element]: 'Element',
[NodeType.Effect]: 'Effect',
[NodeType.Render]: 'Render Effect',
[NodeType.Memo]: 'Memo',
[NodeType.Root]: 'Root',
[NodeType.Component]: 'Component',
[NodeType.Element]: 'Element',
[NodeType.Effect]: 'Effect',
[NodeType.Render]: 'Render Effect',
[NodeType.Memo]: 'Memo',
[NodeType.Computation]: 'Computation',
[NodeType.Refresh]: 'Refresh',
[NodeType.Context]: 'Context',
[NodeType.CatchError]: 'CatchError',
[NodeType.Signal]: 'Signal',
[NodeType.Store]: 'Store',
[NodeType.Refresh]: 'Refresh',
[NodeType.Context]: 'Context',
[NodeType.CatchError]: 'CatchError',
[NodeType.Signal]: 'Signal',
[NodeType.Store]: 'Store',
[NodeType.CustomValue]: 'Custom Value',
}

export enum ValueItemType {
Signal = 'signal',
Prop = 'prop',
Value = 'value',
Prop = 'prop',
Value = 'value',
}

export const UNKNOWN = 'unknown'
Expand Down
33 changes: 18 additions & 15 deletions packages/debugger/src/main/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,34 @@ import {getNewSdtId} from './get-id.ts'
import {type NodeID, type Solid} from './types.ts'

export const enum ObjectType {
Owner = 'owner',
Element = 'element',
Signal = 'signal',
Store = 'store',
StoreNode = 'store-node',
Owner = 'OWNER',
Element = 'ELEMENT',
Signal = 'SIGNAL',
Store = 'STORE',
StoreNode = 'STORE_NODE',
CustomValue = 'CUSTOM_VALUE',
}

type ValueMap = {
[ObjectType.Owner]: Solid.Owner
[ObjectType.Element]: Element
[ObjectType.Signal]: Solid.Signal
[ObjectType.Store]: Solid.Store
[ObjectType.StoreNode]: Solid.StoreNode
[ObjectType.Owner]: Solid.Owner
[ObjectType.Element]: Element
[ObjectType.Signal]: Solid.Signal
[ObjectType.Store]: Solid.Store
[ObjectType.StoreNode]: Solid.StoreNode
[ObjectType.CustomValue]: Solid.SourceMapValue
}

const WeakIdMap = new WeakMap<ValueMap[ObjectType], NodeID>()

const RefMapMap: {
readonly [T in ObjectType]: Map<NodeID, WeakRef<ValueMap[T]>>
} = {
[ObjectType.Owner]: new Map(),
[ObjectType.Element]: new Map(),
[ObjectType.Signal]: new Map(),
[ObjectType.Store]: new Map(),
[ObjectType.StoreNode]: new Map(),
[ObjectType.Owner]: new Map(),
[ObjectType.Element]: new Map(),
[ObjectType.Signal]: new Map(),
[ObjectType.Store]: new Map(),
[ObjectType.StoreNode]: new Map(),
[ObjectType.CustomValue]: new Map(),
}

const CleanupRegistry = new FinalizationRegistry((data: {map: ObjectType; id: NodeID}) => {
Expand Down
Loading

0 comments on commit 4e32e04

Please sign in to comment.