Skip to content

fictitious/solid-devtools-multirepo

Repository files navigation

Solid devtools chrome extension

work in progress, not quite usable yet

  • requires changes to solid-js and dom-expressions code
  • so it works only with examples built from this multirepo, which are using modified solid-js from this repo
  • it's buggy, does not handle Portal yet
  • only shows component tree and component props, no signals yet

It does not (yet) use the solid-debugger, and it does not rely on the development mode provided by solid, because

  • the ability to use devtools on a production site might turn out to be useful
  • devtools should be usable on any code that uses Solid JS, users should not need to add anything to their code for that

Installation

git clone https://github.com/fictitious/solid-devtools-multirepo.git
cd solid-devtools-multirepo
git submodule update --init
node .yarn/releases/yarn-3.0.2.cjs install --mode=skip-build
./node_modules/.bin/yarn build

Install unpacked extension in chrome from submodules/solid-devtools/dist/unpacked

Start examples

./node_modules/.bin/yarn start-examples
solid-devtools-demo.mp4

The apps used for examples are:

Example apps are included in this multirepo so they are using modified solid-js also from this repo.

After the first full build which builds all dependencies including solid-js, the updated devtools version can be built by

./node_modules/.bin/yarn build-solid-devtools

Then you will need to reload it on the chrome extensions page.

Overview

Component Tree

Let's take this slightly modified example from Solid tutorial

import type {Component} from 'solid-js';
import {createSignal, For} from 'solid-js';

const Cat: Component<{index: number; cat: {id: string; name: string}}> = props => 
    <li>
        <a target="_blank" href={`https://www.youtube.com/watch?v=${props.cat.id}`}>
            {props.index + 1}: {props.cat.name}
        </a>
    </li>
;

const App: Component = () => {
    const [cats, setCats] = createSignal([
        { id: 'J---aiyznGQ', name: 'Keyboard Cat' },
        { id: 'z_AbfPXTKms', name: 'Maru' },
        { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' }
    ]);

    return <ul>
        <For each={cats()}>{(cat, i) => <Cat index={i()} cat={cat} />}</For>
    </ul>
    ;
};

export {App};

The imaginary component tree for App looks like this:

Component tree

  • ovals are components
  • diamonds are DOM elements

This tree is imaginary because components are not part of the tree - in fact, there's nothing left of the components after they are rendered, except their result - DOM nodes.

For devtools, one way to show the component tree is to look where the DOM nodes from each component are inserted into the DOM tree, and recover parent/child component relationship from that.

Changes to Solid JS code

To recover this information, Solid JS code is modified to intercept component creation (so we know which DOM nodes were created by each component), and to intercept DOM node insertion (to see where in the tree the components are inserted). This is done by the devtools hook script, which is injected into the page and uses wrappers called by modified Solid JS code in createComponent and dom-expressions insertParent. There's also another wrapper in dom-expressions render which tracks DOM elements that are the roots of DOM generated by Solid.

When the devtools page is not open, the wrappers are no-op functions which just return immediately. Although they are always called by the Solid JS code, this does not seem to affect performance, according to the benchmark.

The hook script

The hook script collects information from createComponent, insertParent and render wrappers and stored it in the registry. It assigns IDs to component instances and DOM nodes which are results of components, and keeps the map of components and the map of component results:

Hook

The channel and the devtools panel

To show component tree, that information must be transferred from the hook to the devtools panel which runs in a separate javascript context inside chrome devtools. Channel is used for that. Channel code was taken from React devtools (it's called the bridge there) and modified to adapt to chrome extension manifest V3.

Channel

Message types from page to devtools and from devtools to page are defined here.

On the devtools side, there's RegistryMirror which also has the map of components and map of their results. RegistryMirror is used by the code that infers parent/child relationship for the components.

Links for the devtools UI code:

Running the benchmark

There's a submodule for js-framework-benchmark in examples directory here. It can be used to run benchmark for original and modified solid-js code, after the dev dependencies are built:

./node_modules/.bin/yarn build-dev-dependencies
./node_modules/.bin/yarn run-benchmark

The script installs the original solid-js benchmark using npm ci in frameworks/keyed/solid, so it uses the version of solid-js from npm as specified in package.json there.

The "patched" benchmark is in examples/benchmark-solid-patchced, it's included as workspace in this monorepo, installed via yarn, and symlinked under frameworks/keyed/solid-patched. So the only difference from original benchmark is that it uses modified solid-js also from this monorepo.

There's no significant difference in the results - I'm getting similar differences between multiple runs of the benchmark for the same code.

solid-benchmark-comparison

Releases

No releases published

Packages

No packages published