Skip to content

Commit ea51bd0

Browse files
authored
Add UNOWNED Root with top-level signals (#330)
2 parents 4e32e04 + ce98e83 commit ea51bd0

22 files changed

Lines changed: 440 additions & 362 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@solid-devtools/debugger": minor
3+
"@solid-devtools/frontend": minor
4+
"@solid-devtools/shared": minor
5+
---
6+
7+
Add UNOWNED Root with top-level signals
8+
-> Show signals created outside of reactive context (Closes #209)

examples/sandbox/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ const createComponent = (content: () => s.JSX.Element) => {
119119
return Content
120120
}
121121

122+
const [someUnusedTopLevelSignal, setSomeUnusedTopLevelSignal] = s.createSignal(123)
123+
122124
const App: s.Component = () => {
123125

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

packages/debugger/src/dependency/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import {defer} from '@solid-primitives/utils'
44
import {msg} from '@solid-devtools/shared/utils'
55
import {DevtoolsMainView, NodeType} from '../main/constants.ts'
66
import {ObjectType, getObjectById} from '../main/id.ts'
7-
import {type NodeID, type Solid} from '../main/types.ts'
7+
import {type InspectedState, type NodeID, type OutputEmit, type Solid} from '../main/types.ts'
88
import {getNodeType} from '../main/utils.ts'
99
import {type OnNodeUpdate, type SerializedDGraph, collectDependencyGraph} from './collect.ts'
10-
import {type OutputEmit, type InspectedState} from '../main/index.ts'
1110

1211
export {type SerializedDGraph} from './collect.ts'
1312

packages/debugger/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export {
66
observeValueUpdate,
77
removeValueUpdateObserver,
88
} from './main/observe.ts'
9-
export {attachDebugger, unobserveAllRoots} from './main/roots.ts'
9+
export {attachDebugger} from './main/roots.ts'
1010
export {
1111
getNodeName,
1212
getNodeType,

packages/debugger/src/inspector/index.ts

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import {msg, warn} from '@solid-devtools/shared/utils'
1+
import * as s from 'solid-js'
22
import {scheduleIdle, throttle} from '@solid-primitives/scheduled'
3-
import {type Accessor, createEffect, onCleanup} from 'solid-js'
4-
import {type OutputEmit} from '../main/index.ts'
3+
import {msg, warn} from '@solid-devtools/shared/utils'
54
import {ObjectType, getObjectById} from '../main/id.ts'
65
import {addSolidUpdateListener} from '../main/observe.ts'
7-
import {type Mapped, type NodeID, type Solid, type ValueItemID} from '../main/types.ts'
6+
import {type InspectedState, type Mapped, type NodeID, type OutputEmit, type Solid, type ValueItemID} from '../main/types.ts'
87
import {onOwnerDispose} from '../main/utils.ts'
8+
import setup from '../main/setup.ts'
9+
import {UNOWNED_ROOT} from '../main/roots.ts'
910
import {type ObservedPropsMap, ValueNodeMap, clearOwnerObservers, collectOwnerDetails} from './inspector.ts'
1011
import {encodeValue} from './serialize.ts'
1112
import {type StoreNodeProperty, type StoreUpdateData, observeStoreNode, setOnStoreNodeUpdate} from './store.ts'
@@ -19,8 +20,8 @@ export type ToggleInspectedValueData = {id: ValueItemID; selected: boolean}
1920
* Plugin module
2021
*/
2122
export function createInspector(props: {
22-
inspectedOwnerId: Accessor<NodeID | null>
23-
enabled: Accessor<boolean>
23+
inspectedState: s.Accessor<InspectedState>
24+
enabled: s.Accessor<boolean>
2425
resetInspectedNode: VoidFunction
2526
emit: OutputEmit
2627
}) {
@@ -146,46 +147,63 @@ export function createInspector(props: {
146147

147148
let clearPrevDisposeListener: VoidFunction | undefined
148149

149-
createEffect(() => {
150-
if (!props.enabled()) return
151-
const id = props.inspectedOwnerId()
152-
153-
queueMicrotask(() => {
154-
const owner = id && getObjectById(id, ObjectType.Owner)
155-
inspectedOwner && clearOwnerObservers(inspectedOwner, propsMap)
156-
inspectedOwner = owner
157-
valueMap.reset()
158-
clearUpdates()
159-
160-
if (owner) {
161-
const result = collectOwnerDetails(owner, {
162-
onValueUpdate: pushValueUpdate,
163-
onPropStateChange: pushPropState,
164-
observedPropsMap: propsMap,
165-
})
166-
167-
props.emit(msg('InspectedNodeDetails', result.details))
168-
169-
valueMap = result.valueMap
170-
lastDetails = result.details
171-
checkProxyProps = result.checkProxyProps || null
172-
} else {
173-
lastDetails = undefined
174-
checkProxyProps = null
175-
}
150+
151+
152+
function inspectOwnerId(id: NodeID | null): void {
153+
154+
const owner = id && getObjectById(id, ObjectType.Owner)
155+
if (inspectedOwner) clearOwnerObservers(inspectedOwner, propsMap)
156+
inspectedOwner = owner
157+
158+
valueMap.reset()
159+
clearUpdates()
160+
161+
if (owner) {
162+
const result = collectOwnerDetails(owner, {
163+
onValueUpdate: pushValueUpdate,
164+
onPropStateChange: pushPropState,
165+
observedPropsMap: propsMap,
166+
})
167+
168+
props.emit(msg('InspectedNodeDetails', result.details))
169+
170+
valueMap = result.valueMap
171+
lastDetails = result.details
172+
checkProxyProps = result.checkProxyProps || null
173+
} else {
174+
lastDetails = undefined
175+
checkProxyProps = null
176+
}
177+
178+
clearPrevDisposeListener?.()
179+
clearPrevDisposeListener = owner
180+
? onOwnerDispose(owner, props.resetInspectedNode)
181+
: undefined
182+
}
176183

177-
clearPrevDisposeListener?.()
178-
clearPrevDisposeListener = owner
179-
? onOwnerDispose(owner, props.resetInspectedNode)
180-
: undefined
181-
})
184+
const inspectedOwnerId = s.createMemo(
185+
() => props.enabled()
186+
? props.inspectedState().ownerId
187+
: null
188+
)
189+
s.createEffect(() => {
190+
let id = inspectedOwnerId()
191+
s.untrack(() => inspectOwnerId(id))
182192
})
183193

184-
createEffect(() => {
194+
function onUnownedRootChange() {
195+
if (inspectedOwner === UNOWNED_ROOT) {
196+
inspectOwnerId(inspectedOwnerId())
197+
}
198+
}
199+
setup.unowned.onSignalAdded = onUnownedRootChange
200+
setup.unowned.onSignalRemoved = onUnownedRootChange
201+
202+
s.createEffect(() => {
185203
if (!props.enabled()) return
186204

187205
// Check if proxy props have changed keys after each update queue
188-
onCleanup(addSolidUpdateListener(() => checkProxyProps && triggerPropsCheck()))
206+
s.onCleanup(addSolidUpdateListener(() => checkProxyProps && triggerPropsCheck()))
189207
})
190208

191209
return {

packages/debugger/src/inspector/inspector.ts

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {misc} from '@nothing-but/utils'
2-
import {untrackedCallback} from '@solid-devtools/shared/primitives'
32
import {parseLocationString, type SourceLocation} from '../locator/index.ts'
43
import {NodeType, ValueItemType} from '../main/constants.ts'
54
import {ObjectType, getSdtId} from '../main/id.ts'
65
import {observeValueUpdate, removeValueUpdateObserver} from '../main/observe.ts'
76
import setup from '../main/setup.ts'
87
import type {Mapped, NodeID, Solid, ValueItemID} from '../main/types.ts'
98
import * as utils from '../main/utils.ts'
9+
import {UNOWNED_ROOT} from '../main/roots.ts'
1010
import {encodeValue} from './serialize.ts'
1111
import {type InspectorUpdateMap, PropGetterState} from './types.ts'
1212

@@ -250,14 +250,17 @@ function mapProps(props: Solid.Component['props']) {
250250
return {props: {proxy: isProxy, record}, checkProxyProps}
251251
}
252252

253-
export const collectOwnerDetails = /*#__PURE__*/ untrackedCallback(function (
254-
owner: Solid.Owner,
255-
config: {
256-
onPropStateChange: Inspector.OnPropStateChange
257-
onValueUpdate: Inspector.OnValueUpdate
258-
observedPropsMap: ObservedPropsMap
259-
},
253+
export type CollectDetailsConfig = {
254+
onPropStateChange: Inspector.OnPropStateChange
255+
onValueUpdate: Inspector.OnValueUpdate
256+
observedPropsMap: ObservedPropsMap
257+
}
258+
259+
export function collectOwnerDetails(
260+
owner: Solid.Owner,
261+
config: CollectDetailsConfig,
260262
) {
263+
261264
const {onValueUpdate} = config
262265

263266
// Set globals
@@ -279,8 +282,8 @@ export const collectOwnerDetails = /*#__PURE__*/ untrackedCallback(function (
279282
owned = null
280283
const symbols = Object.getOwnPropertySymbols(owner.context)
281284
/*
282-
since 1.8 context keys from parent are cloned to child context
283-
the last key should be the added value
285+
since 1.8 context keys from parent are cloned to child context
286+
the last key should be the added value
284287
*/
285288
const context_value = owner.context[symbols[symbols.length - 1]!]
286289
getValue = () => context_value
@@ -329,18 +332,28 @@ export const collectOwnerDetails = /*#__PURE__*/ untrackedCallback(function (
329332
onValueUpdate(`${ValueItemType.Signal}:${signalId}`)
330333

331334
// map signals
332-
if (sourceMap) {
333-
for (const signal of sourceMap) {
334-
const mapped = mapSourceValue(signal, onSignalUpdate)
335-
mapped && details.signals.push(mapped)
336-
}
335+
if (sourceMap) for (let signal of sourceMap) {
336+
let mapped = mapSourceValue(signal, onSignalUpdate)
337+
if (mapped) details.signals.push(mapped)
337338
}
338339

339340
// map memos
340-
if (owned) {
341-
for (const node of owned) {
342-
const mapped = mapSourceValue(node, onSignalUpdate)
343-
mapped && details.signals.push(mapped)
341+
if (owned) for (let node of owned) {
342+
let mapped = mapSourceValue(node, onSignalUpdate)
343+
if (mapped) details.signals.push(mapped)
344+
}
345+
346+
/* Handle the fake unowned root */
347+
if (owner === UNOWNED_ROOT) {
348+
for (let signal_ref of setup.unowned.signals) {
349+
350+
let signal = signal_ref.deref()
351+
if (signal == null) continue
352+
353+
let mapped = mapSourceValue(signal, onSignalUpdate)
354+
if (mapped == null) continue
355+
356+
details.signals.push(mapped)
344357
}
345358
}
346359

@@ -356,4 +369,4 @@ export const collectOwnerDetails = /*#__PURE__*/ untrackedCallback(function (
356369
ValueMap = OnValueUpdate = OnPropStateChange = PropsMap = undefined!
357370

358371
return result
359-
})
372+
}

packages/debugger/src/inspector/test/index.test.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('collectOwnerDetails', () => {
7373
type: NodeType.Signal,
7474
id: getSdtId(signalB, ObjectType.Signal),
7575
name: 'element',
76-
value: [[ValueType.Element, '#4:div']],
76+
value: [[ValueType.Element, '#3:div']],
7777
},
7878
{
7979
type: NodeType.Memo,
@@ -88,7 +88,7 @@ describe('collectOwnerDetails', () => {
8888
expect(valueMap.get(`signal:${getSdtId(signalB, ObjectType.Signal)}`)).toBeTruthy()
8989
expect(valueMap.get(`signal:${getSdtId(innerMemo, ObjectType.Owner)}`)).toBeTruthy()
9090

91-
expect(getObjectById('#4', ObjectType.Element)).toBe(div)
91+
expect(getObjectById('#3', ObjectType.Element)).toBe(div)
9292

9393
dispose()
9494
})
@@ -124,11 +124,11 @@ describe('collectOwnerDetails', () => {
124124
dispose()
125125

126126
expect(details).toEqual({
127-
id: '#1',
127+
id: '#0',
128128
name: 'TestComponent',
129129
type: NodeType.Component,
130130
signals: [],
131-
value: [[ValueType.Element, '#2:div']],
131+
value: [[ValueType.Element, '#1:div']],
132132
props: {
133133
proxy: false,
134134
record: {
@@ -148,7 +148,7 @@ describe('collectOwnerDetails', () => {
148148
},
149149
} satisfies Mapped.OwnerDetails)
150150

151-
expect(getObjectById('#2', ObjectType.Element)).toBeInstanceOf(HTMLDivElement)
151+
expect(getObjectById('#1', ObjectType.Element)).toBeInstanceOf(HTMLDivElement)
152152
})
153153
})
154154

@@ -181,11 +181,11 @@ describe('collectOwnerDetails', () => {
181181
})
182182

183183
expect(details).toEqual({
184-
id: '#1',
184+
id: '#0',
185185
name: 'Button',
186186
type: NodeType.Component,
187187
signals: [],
188-
value: [[ValueType.Element, '#2:button']],
188+
value: [[ValueType.Element, '#1:button']],
189189
props: {
190190
proxy: true,
191191
record: {
@@ -201,7 +201,7 @@ describe('collectOwnerDetails', () => {
201201
},
202202
} satisfies Mapped.OwnerDetails)
203203

204-
expect(getObjectById('#2', ObjectType.Element)).toBeInstanceOf(HTMLButtonElement)
204+
expect(getObjectById('#1', ObjectType.Element)).toBeInstanceOf(HTMLButtonElement)
205205

206206
dispose()
207207
})

packages/debugger/src/locator/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import {createKeyHold} from '@solid-primitives/keyboard'
55
import {scheduleIdle} from '@solid-primitives/scheduled'
66
import {makeHoverElementListener} from '@solid-devtools/shared/primitives'
77
import {msg, warn} from '@solid-devtools/shared/utils'
8-
import {type OutputEmit} from '../main/index.ts'
98
import * as registry from '../main/component-registry.ts'
109
import {ObjectType, getObjectById} from '../main/id.ts'
1110
import SolidAPI from '../main/setup.ts'
12-
import {type NodeID} from '../main/types.ts'
11+
import {type NodeID, type OutputEmit} from '../main/types.ts'
1312
import {createElementsOverlay} from './element-overlay.tsx'
1413
import {
1514
type LocatorComponent,

0 commit comments

Comments
 (0)