Skip to content

Latest commit

 

History

History
149 lines (86 loc) · 7.27 KB

solid-usage.md

File metadata and controls

149 lines (86 loc) · 7.27 KB

Usage of Solid internals and dev-apis in solid-devtools

Current usage

Detecting Solid

  • This one cannot use internals nor dev-only apis because the goal is to detect solid on any page. — source code

Issues

  • The test is complicated, brittle, might have false-positives and doesn't tell you the solid version.

Node types

By node I mean any solid internal object, like owners, signals, roots, store-nodes, and anything user registers with solid.DEV.registerGraph().

  • There are many internal owner properties that are inspected to determine kind of node. — source code

Issues

  • This is super brittle, some properties are set some time after the owner is created, most of the properties accessed are not used for anything else.

Node names

  • owner.name or signal.name or owner.component.name or owner.component.displayName

Issues

  • No one good way to give custom names to components.

    This works but it's a side-effect.

    Bar.displayName = "Foo.Bar"

    While this is ugly

    if (isDev) {
        getOwner()!.name = "Foo.Bar"
    }

Roots

  • solid.DEV.hooks.afterCreateOwner() for attaching the debugger to roots
    • to gather top-level roots like render()
    • but also to "re-attach roots back to the owner tree" when createRoot is used under owners, like in mapArray
    • source code
    • access to owner.owner and owner.cleanups to listen to parent's (root's detached owner) cleanup
      • so that if the root's parent disposes before the root, the root will need to be "reattached" to first found "alive" owner
      • this is rather uncommon

Issues

  • Any root created before solid.DEV.hooks.afterCreateOwner is set will be missed.
    This is annoying because the extension's content-script isn't garanteed to run before user scripts. Making the solid-devtools npm package and import "solid-devtools" required to capture all the roots.
    If there was access to any unowned root, the extension would work with any solid app with no setup.

Owners tree

  • Reads owner.owner and owner.owned to walk the tree in both directions

Owner disposing

  • Writing to owner.cleanups to listen to cleanup of any owner — source code

Issues

  • The cleanups are executed depth first—if I try to walk the tree on child's cleanup, I cannot tell if the parent is getting cleaned up as well or not. There is no isDisposed property.

  • There is no difference between disposing and rerunning—I have to listen to parents cleanup to when targetting disposal.

Computation updates

  • owner.fn is patched to observe when the computation reruns — source code

  • owner.value is read to get the current owner value and patched to listen to changes — source code

    • signals are being observed the same way
    • this is also separate from observing owner.fn because not every rerun will cause the value to update (thanks to equals option)
  • owner.sources — by listening to each of the sources, I'm able to tell which source caused the computation to rerun. — source code

Dependency graph

  • owner.sources and owner.observers to crawl the graph

  • sourceValue.graph — for signals to know to which owner they "belong to"

  • solid.DEV.hooks.afterUpdate() is used as a trigger to update the dependency graph

Props

  • solid.$PROXY is used to check if the props object is a proxy

  • If it's not a proxy, I can patch it with Object.defineProperty(props, key, {get}) to intercept accessing props—since accessing props might cause side-effects I have to wait for the user.

    • source code
    • solid.getListener() + solid.onCleanup() is useful there to know when the prop is no longer being listened to—I might not have the latest value anymore.
  • For proxy props I can only get keys with Object.keys(props)

Issues

  • I cannot intercept proxy props—users just see foo: unknown without the value

  • props object cannot be changed, just monkeypatched—replacing it would probably let me intercept reads to proxy props

  • solid.DEV.hooks.afterCreateOwner() gets called before props are attached to the owner, so I cannot get to them then.

    • so currently I cannot intercept the initial reads from props—which are the most common
    • I would have to do Object.defineProperty(owner, "props", {set}) to be able to patch props before component function executes probably
    • source code

Store

  • store.unwrap() with type reflection when serializing and reading stores

  • store.isWrappable()

  • store.DEV.hooks.onStoreNodeUpdate() for observing changes to stores — source code

Issues

  • There is no connection between signals and the store-nodes they are being used in.
    So if there is an effect that listens to some store property (e.g. createEffect(() => store.foo)), from devtools perspective this is just some random signal being observed in owner.sources.

Signals and other values

  • owner.sourceMap for getting which signals, stores or custom values belong to an owner.

  • solid.DEV.hooks.afterCreateSignal() is useful for getting unowned signals

    • which is important for the export const [state, setState] = solid.createSignal({...}) style of state management — source code

Issues

  • The two issues with afterCreateSignal are addressed by solidjs/solid#2396 and solidjs/solid#2393

  • The other is similar to afterCreateOwner — signals created before the hook callback is set will be missed. This one is trickier to solve because signals don't have a clear lifecycle unlike roots.

Additional issues

  • There is no way to connect a render effect to the dom property it updates, so I cannot give any meaningfull info about them, or to which element they even belong.

  • There is no connection between a signal accessor and the signal object it belongs to.

    This is an usually an issue with jsx and components returning signals. I cannot show what the value is without calling the accessor which might have side effects.

  • No tools to mark "custom primitives" for devtools. For example createResource doesn't have a single instance that I can inspect—it's made up from multiple separate signals, computations and other values.