diff --git a/.changeset/bright-mayflies-cross.md b/.changeset/bright-mayflies-cross.md
new file mode 100644
index 00000000..de28703f
--- /dev/null
+++ b/.changeset/bright-mayflies-cross.md
@@ -0,0 +1,5 @@
+---
+'@solid-devtools/transform': minor
+---
+
+Remove `wrapStores` transform — tracking stores is handled by the debugger.
diff --git a/.changeset/empty-carrots-attack.md b/.changeset/empty-carrots-attack.md
new file mode 100644
index 00000000..9ad39f76
--- /dev/null
+++ b/.changeset/empty-carrots-attack.md
@@ -0,0 +1,6 @@
+---
+'@solid-devtools/debugger': minor
+'@solid-devtools/shared': minor
+---
+
+Move value serialization logic to debugger
diff --git a/.changeset/real-dolphins-smash.md b/.changeset/real-dolphins-smash.md
new file mode 100644
index 00000000..b8f40f5e
--- /dev/null
+++ b/.changeset/real-dolphins-smash.md
@@ -0,0 +1,7 @@
+---
+'@solid-devtools/debugger': patch
+'@solid-devtools/logger': patch
+'@solid-devtools/shared': patch
+---
+
+Move some debugger related types from @solid-devtools/shared to @solid-devtools/debugger
diff --git a/.changeset/wet-camels-confess.md b/.changeset/wet-camels-confess.md
new file mode 100644
index 00000000..2bbec23c
--- /dev/null
+++ b/.changeset/wet-camels-confess.md
@@ -0,0 +1,11 @@
+---
+'@solid-devtools/debugger': minor
+'solid-devtools': minor
+'solid-devtools-extension': minor
+'@solid-devtools/frontend': minor
+'@solid-devtools/logger': minor
+'@solid-devtools/overlay': minor
+'@solid-devtools/shared': minor
+---
+
+Move graph types from `@solid-devtools/shared` to `@solid-devtools/debugger`
diff --git a/.changeset/witty-rockets-juggle.md b/.changeset/witty-rockets-juggle.md
new file mode 100644
index 00000000..54d02920
--- /dev/null
+++ b/.changeset/witty-rockets-juggle.md
@@ -0,0 +1,6 @@
+---
+'@solid-devtools/debugger': minor
+'@solid-devtools/frontend': minor
+---
+
+Track and show fine-grain updates to stores in inspected values.
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 00000000..03242497
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,12 @@
+{
+ "root": true,
+ "parser": "@typescript-eslint/parser",
+ "plugins": ["@typescript-eslint", "no-only-tests"],
+ "ignorePatterns": ["node_modules", "dist", "scripts", "examples"],
+ "rules": {
+ "no-console": "warn",
+ "no-debugger": "warn",
+ "@typescript-eslint/no-unused-vars": "warn",
+ "no-only-tests/no-only-tests": "warn"
+ }
+}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e8a37f11..45ecc832 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -27,6 +27,9 @@ jobs:
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
+ - name: Lint
+ run: pnpm lint
+
- name: Run Build and Tests
run: pnpm run build-test
env:
diff --git a/.npmrc b/.npmrc
index 4c2f52b3..8556057e 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1,2 +1,3 @@
auto-install-peers=true
strict-peer-dependencies=false
+link-workspace-packages=deep
diff --git a/configs/tsconfig.base.json b/configs/tsconfig.base.json
index 76e61a19..cabc6576 100644
--- a/configs/tsconfig.base.json
+++ b/configs/tsconfig.base.json
@@ -9,7 +9,6 @@
"esModuleInterop": true,
"noEmit": true,
"skipLibCheck": true,
- "noUnusedLocals": true,
"forceConsistentCasingInFileNames": true
}
}
diff --git a/configs/tsup.config.ts b/configs/tsup.config.ts
index a3da7560..c6ab5920 100644
--- a/configs/tsup.config.ts
+++ b/configs/tsup.config.ts
@@ -28,6 +28,7 @@ export default ({
return defineConfig(config => {
const options: Options = {
clean: config.watch ? false : true,
+ minify: config.watch ? false : true,
dts: {
entry: [entry, ...mappedAdditionalEntries],
},
diff --git a/examples/clock/package.json b/examples/clock/package.json
index ef505cdd..4fae3e7d 100644
--- a/examples/clock/package.json
+++ b/examples/clock/package.json
@@ -17,6 +17,6 @@
},
"dependencies": {
"solid-devtools": "workspace:^0.20.1",
- "solid-js": "^1.6.0"
+ "solid-js": "^1.6.2"
}
}
diff --git a/examples/sandbox/package.json b/examples/sandbox/package.json
index 5d4085cf..1a0af2cd 100644
--- a/examples/sandbox/package.json
+++ b/examples/sandbox/package.json
@@ -15,6 +15,7 @@
"@babel/plugin-syntax-typescript": "^7.18.6",
"@solid-devtools/transform": "workspace:^0.7.5",
"typescript": "^4.8.4",
+ "unocss": "^0.46.3",
"vite": "^3.1.8",
"vite-plugin-solid": "^2.3.10"
},
@@ -24,6 +25,6 @@
"@solid-devtools/overlay": "workspace:^0.0.7",
"@solid-primitives/timer": "^1.3.4",
"solid-devtools": "workspace:^0.20.1",
- "solid-js": "^1.6.0"
+ "solid-js": "^1.6.2"
}
}
diff --git a/examples/sandbox/src/App.tsx b/examples/sandbox/src/App.tsx
index c5536342..176a5975 100644
--- a/examples/sandbox/src/App.tsx
+++ b/examples/sandbox/src/App.tsx
@@ -15,6 +15,8 @@ import {
import Todos from './Todos'
import { disposeApp } from '.'
import { ThemeExample } from './Theme'
+import { createMutable } from 'solid-js/store'
+import Recursive from './Recursive'
const doMediumCalc = () => {
Array.from({ length: 1000000 }, (_, i) => i).sort(() => Math.random() - 5)
@@ -62,24 +64,51 @@ const PassChildren: ParentComponent = props => {
}
const Article: Component = () => {
+ const state = createMutable({
+ count: 0,
+ other: { name: 'article' },
+ content: {
+ title: 'A cool headline for testing :)',
+ body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorem odio culpa vel vitae? Quis deleniti soluta rem velit necessitatibus? ',
+ },
+ })
+
return (
- A cool headline for testing :)
+ {state.content.title}
- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorem odio culpa vel vitae? Quis
- deleniti soluta rem velit necessitatibus?{' '}
+ {state.content.body}
Saepe nulla omnis nobis minima perferendis odio doloremque deleniti dolore corrupti.
+ {/* Count: {state.count}
*/}
+
+ state.count++}>Increment article count
+
)
}
-const obj = {
- comp: () =>
This is an object property component
,
+const DynamicSpreadParent = () => {
+ const [props, setProps] = createSignal({
+ a: 1,
+ b: 2,
+ c: 3,
+ style: { width: '160px', height: '30px', background: '#fcb', color: 'black' },
+ textContent: 'Before Change',
+ onclick: () =>
+ setProps({
+ style: { width: '160px', height: '30px', background: '#eba', color: 'black' },
+ textContent: 'After Change',
+ d: 4,
+ e: 5,
+ }),
+ })
+ const DynamicSpreadChild = (props: any) =>
+ return
}
const App: Component = () => {
@@ -145,7 +174,7 @@ const App: Component = () => {
*/}
-
+
setRootCount(p => ++p)}>Update root count
disposeOuterRoot()}>Dispose OUTSIDE_ROOT
@@ -156,6 +185,7 @@ const App: Component = () => {
+
>
)
}
diff --git a/examples/sandbox/src/Recursive.tsx b/examples/sandbox/src/Recursive.tsx
new file mode 100644
index 00000000..31a1f8f8
--- /dev/null
+++ b/examples/sandbox/src/Recursive.tsx
@@ -0,0 +1,101 @@
+import { Component, For, splitProps, mergeProps, createContext, useContext } from 'solid-js'
+import { createStore } from 'solid-js/store'
+
+type NodeType = {
+ id: number
+ children?: Array
+ parents?: Array
+}
+
+let id: number = 3
+
+interface genNodePathInterface {
+ nodeId: number
+ parentsPath?: Array
+ accessKey?: string
+}
+function genNodePath({ nodeId, parentsPath = [], accessKey = 'children' }: genNodePathInterface) {
+ let path = parentsPath.concat(nodeId)
+ let nodePath: Array = []
+ path.forEach(id => {
+ nodePath.push(...[(child: NodeType) => child.id === id, accessKey])
+ })
+ return nodePath
+}
+
+// Printed url path
+function genNodeUrl(nodeProps: any) {
+ let urlFormat = `${nodeProps.id}`
+ let url = nodeProps?.url ? [...nodeProps.url, urlFormat] : [urlFormat]
+ return url
+}
+
+// Recursive component
+const Node: Component = props => {
+ let [childrenProps, nodeProps] = splitProps(props, ['children'])
+ // To store local component signals
+ type nodeSignalsType = {
+ parents: Array
+ url: Array
+ }
+ const [nodeSignals, setNodeSignals] = createStore({
+ parents: nodeProps?.parents ? nodeProps?.parents : [],
+ url: genNodeUrl(nodeProps),
+ })
+
+ const addNode = useContext(RecursiveContext)
+
+ return (
+
+
{nodeSignals.url.join(' > ')}
+
+ {node => {
+ const childProps = mergeProps(
+ {
+ parents: [...nodeSignals.parents, nodeProps.id],
+ url: nodeSignals.url,
+ },
+ node,
+ )
+ return
+ }}
+
+
addNode(nodeProps.id, nodeSignals.parents)}
+ >
+ Add item
+
+
+ )
+}
+
+const RecursiveContext = createContext<(nodeId: number, parentsPath: number[]) => void>(() => {})
+
+const Recursive: Component = props => {
+ const [data, setData] = createStore([
+ {
+ id: 0,
+ children: [
+ { id: 1, children: [{ id: 2, children: [] }] },
+ { id: 3, children: [] },
+ ],
+ },
+ ])
+
+ const addNode = (nodeId: number, parentsPath: number[]) => {
+ setData(
+ // @ts-ignore
+ ...genNodePath({ nodeId, parentsPath }),
+ (children: any) => [...children, { id: ++id, children: [] }],
+ )
+ }
+
+ return (
+
+ {page => }
+
+ )
+}
+
+export default Recursive
diff --git a/examples/sandbox/src/Theme.tsx b/examples/sandbox/src/Theme.tsx
index 295f4177..3d91e6e5 100644
--- a/examples/sandbox/src/Theme.tsx
+++ b/examples/sandbox/src/Theme.tsx
@@ -30,23 +30,16 @@ export const ThemeProvider: ParentComponent<{
color?: string
title?: string
}> = props => {
- const [color, setColor] = createSignal(props.color ?? defaultState.color)
- const [title, setTitle] = createSignal(props.title ?? defaultState.title)
+ const [theme, setTheme] = createStore({
+ color: props.color || defaultState.color,
+ title: props.title || defaultState.title,
+ })
- const changeColor = (color: string) => setColor(color)
- const changeTitle = (title: string) => setTitle(title)
-
- const state = {
- get color() {
- return color()
- },
- get title() {
- return title()
- },
- }
+ const changeColor = (color: string) => setTheme('color', color)
+ const changeTitle = (title: string) => setTheme('title', title)
return (
-
+
{props.children}
)
diff --git a/examples/sandbox/src/Todos.tsx b/examples/sandbox/src/Todos.tsx
index d5aa8391..bc1d6e18 100644
--- a/examples/sandbox/src/Todos.tsx
+++ b/examples/sandbox/src/Todos.tsx
@@ -1,13 +1,13 @@
-import { createEffect, createSignal, batch, For, Component, getOwner } from 'solid-js'
-import { createStore, Store, SetStoreFunction } from 'solid-js/store'
-// import { isSolidMemo } from "@solid-devtools/debugger"
+import { createSignal, batch, For, Component, createMemo, createEffect } from 'solid-js'
+import { createStore, Store, SetStoreFunction, produce, unwrap } from 'solid-js/store'
export function createLocalStore(
name: string,
init: T,
): [Store, SetStoreFunction] {
const localState = localStorage.getItem(name)
- const [state, setState] = createStore(localState ? JSON.parse(localState) : init)
+ // const localState = undefined
+ const [state, setState] = createStore(localState ? JSON.parse(localState) : init, { name })
createEffect(() => localStorage.setItem(name, JSON.stringify(state)))
return [state, setState]
}
@@ -25,10 +25,6 @@ const Todo: Component<{
onUpdate: (value: string) => void
onRemove: VoidFunction
}> = props => {
- // console.log(isSolidMemo(getOwner()!.owner))
-
- // debugProps(props)
-
return (
{
const [newTitle, setTitle] = createSignal('')
- const [todos, setTodos] = createLocalStore
('todos', [])
+ const [todos, setTodos] = createLocalStore('todos-2', {
+ values: [] as TodoItem[],
+ other: {
+ name: 'todos',
+ get newTitle() {
+ return { value: newTitle() }
+ },
+ countOuter: {
+ countInner: {
+ count: 0,
+ },
+ },
+ },
+ })
- // makeStoreObserver(todos, console.log)
+ const valuesInASignal = createMemo(() => ({ values: todos.values }))
- // debugStore(todos)
+ // @ts-ignore
+ setTodos('other', 'else', unwrap(todos.values))
const addTodo = (e: SubmitEvent) => {
e.preventDefault()
batch(() => {
- setTodos(todos.length, {
+ setTodos('values', todos.values.length, {
title: newTitle(),
done: false,
})
@@ -66,14 +76,15 @@ const Todos: Component = () => {
}
// setTimeout(() => {
- // setTodos(
- // 0,
- // reconcile({
- // title: "Learn Solid-JS",
- // done: false,
- // [Math.random() + ""]: "hello",
- // }),
- // )
+ // setTodos(
+ // 'values',
+ // 0,
+ // reconcile({
+ // title: 'Learn Solid-JS',
+ // done: false,
+ // [Math.random() + '']: 'hello',
+ // }),
+ // )
// }, 1000)
return (
@@ -88,13 +99,18 @@ const Todos: Component = () => {
/>
+
-
+
{(todo, i) => (
setTodos(i(), 'done', v)}
- onUpdate={v => setTodos(i(), 'title', v)}
- onRemove={() => setTodos(t => removeIndex(t, i()))}
+ onCheck={v => setTodos('values', i(), 'done', v)}
+ onUpdate={v => setTodos('values', i(), 'title', v)}
+ onRemove={() =>
+ setTodos(
+ 'values',
+ produce(t => t.splice(i(), 1)),
+ )
+ }
/>
)}
diff --git a/examples/sandbox/src/index.tsx b/examples/sandbox/src/index.tsx
index 632dd110..49f70c8b 100644
--- a/examples/sandbox/src/index.tsx
+++ b/examples/sandbox/src/index.tsx
@@ -7,6 +7,8 @@ import App from './App'
import { ThemeProvider } from './Theme'
import './locator'
+import 'uno.css'
+
export const disposeApp = render(
() => (
<>
diff --git a/examples/sandbox/vite.config.ts b/examples/sandbox/vite.config.ts
index 5db9d30f..d663f90a 100644
--- a/examples/sandbox/vite.config.ts
+++ b/examples/sandbox/vite.config.ts
@@ -1,15 +1,17 @@
import { defineConfig } from 'vite'
import solidPlugin from 'vite-plugin-solid'
import devtoolsPlugin from '@solid-devtools/transform'
+import Unocss from 'unocss/vite'
export default defineConfig({
plugins: [
devtoolsPlugin({
// wrapStores: true,
- jsxLocation: true,
+ // jsxLocation: true,
name: true,
}),
solidPlugin({ hot: false, dev: true }),
+ Unocss(),
],
resolve: {
conditions: ['browser', 'development'],
@@ -17,5 +19,9 @@ export default defineConfig({
mode: 'development',
build: {
target: 'esnext',
+ minify: false,
+ },
+ optimizeDeps: {
+ exclude: ['solid-js/store', '@solid-devtools/debugger'],
},
})
diff --git a/examples/start/package.json b/examples/start/package.json
index 53b04f9d..493fafe6 100644
--- a/examples/start/package.json
+++ b/examples/start/package.json
@@ -17,7 +17,7 @@
"@solidjs/meta": "^0.28.0",
"@solidjs/router": "^0.4.3",
"solid-devtools": "workspace:^0.20.1",
- "solid-js": "^1.6.0",
+ "solid-js": "^1.6.2",
"solid-start": "^0.1.8",
"undici": "^5.11.0"
},
diff --git a/examples/todomvc/package.json b/examples/todomvc/package.json
index de660d19..c971f075 100644
--- a/examples/todomvc/package.json
+++ b/examples/todomvc/package.json
@@ -33,7 +33,7 @@
"npm": ">=7.20.1"
},
"dependencies": {
- "solid-js": "^1.6.0",
+ "solid-js": "^1.6.2",
"todomvc-app-css": "^2.4.2",
"todomvc-common": "^1.0.5"
},
diff --git a/package.json b/package.json
index be95ab79..e83571d2 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,8 @@
"test": "turbo run test --filter=!./examples/*",
"typecheck": "turbo run typecheck --filter=!./examples/*",
"build-test": "turbo run build test typecheck --filter=!./examples/*",
- "format": "prettier -w \"packages/**/*.{js,ts,json,css,tsx,jsx}\" \"examples/**/*.{js,ts,json,css,tsx,jsx}\"",
+ "format": "prettier -w packages/**/*.{js,ts,json,css,tsx,jsx} examples/**/*.{js,ts,json,css,tsx,jsx}",
+ "lint": "eslint --ignore-path .gitignore --max-warnings 0 packages/**/*.{js,ts,tsx,jsx}",
"changeset": "changeset",
"release": "pnpm run build-test && changeset publish",
"version-packages": "changeset version && pnpm i",
@@ -27,20 +28,29 @@
"devDependencies": {
"@changesets/cli": "^2.25.0",
"@types/node": "^18.11.3",
+ "@typescript-eslint/eslint-plugin": "^5.42.0",
+ "@typescript-eslint/parser": "^5.42.0",
"babel-preset-solid": "^1.6.0",
"concurrently": "^7.4.0",
+ "cross-env": "^7.0.3",
"esbuild": "^0.15.12",
"esbuild-plugin-solid": "^0.4.2",
+ "eslint": "^8.26.0",
+ "eslint-plugin-no-only-tests": "^3.1.0",
"prettier": "2.7.1",
- "solid-js": "^1.6.0",
- "tsup": "^6.3.0",
+ "tsup": "^6.4.0",
"turbo": "^1.5.6",
"typescript": "^4.8.4",
"vite-plugin-solid": "^2.3.10",
- "vitest": "^0.23.4"
+ "vitest": "^0.23.4",
+ "vite": "^3.2.3",
+ "jsdom": "^20.0.2"
},
"packageManager": "pnpm@7.13.0",
"engines": {
"node": ">=16"
+ },
+ "dependencies": {
+ "solid-js": "^1.6.2"
}
}
diff --git a/packages/debugger/package.json b/packages/debugger/package.json
index 4a2f7382..1eb95f8a 100644
--- a/packages/debugger/package.json
+++ b/packages/debugger/package.json
@@ -32,27 +32,37 @@
"module": "./dist/server.js",
"types": "./dist/index.d.ts",
"exports": {
- "browser": {
- "development": {
- "import": "./dist/index.js",
- "require": "./dist/index.cjs"
+ ".": {
+ "browser": {
+ "development": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "import": "./dist/server.js",
+ "require": "./dist/server.cjs"
},
"import": "./dist/server.js",
- "require": "./dist/server.cjs"
+ "require": "./dist/server.cjs",
+ "types": "./dist/index.d.ts"
},
- "import": "./dist/server.js",
- "require": "./dist/server.cjs"
+ "./types": {
+ "import": "./dist/types.js",
+ "require": "./dist/types.cjs",
+ "types": "./dist/types.d.ts"
+ }
+ },
+ "typesVersions": {
+ "*": {
+ "types": [
+ "./dist/types.d.ts"
+ ]
+ }
},
"scripts": {
"dev": "tsup --watch",
"build": "tsup",
"test": "vitest",
- "typecheck": "tsc --noEmit"
- },
- "devDependencies": {
- "solid-js": "^1.6.0",
- "tsup": "^6.3.0",
- "typescript": "^4.8.4"
+ "typecheck": "tsc --noEmit --paths null"
},
"dependencies": {
"@solid-devtools/shared": "workspace:^0.9.2",
@@ -62,13 +72,12 @@
"@solid-primitives/event-listener": "^2.2.3",
"@solid-primitives/keyboard": "^1.0.3",
"@solid-primitives/platform": "^0.0.102",
- "@solid-primitives/scheduled": "^1.1.0",
+ "@solid-primitives/scheduled": "^1.2.0",
"@solid-primitives/utils": "^3.1.0",
- "object-observer": "^5.1.6",
- "type-fest": "^3.1.0"
+ "type-fest": "^3.2.0"
},
"peerDependencies": {
- "solid-js": "^1.5.5"
+ "solid-js": "^1.6.2"
},
"optionalDependencies": {
"@solid-devtools/transform": "workspace:^0.7.5"
diff --git a/packages/debugger/src/index.ts b/packages/debugger/src/index.ts
index 16484542..c35d7dcb 100644
--- a/packages/debugger/src/index.ts
+++ b/packages/debugger/src/index.ts
@@ -1,31 +1,31 @@
import { ParentComponent } from 'solid-js'
-import { attachDebugger } from './roots'
+import { attachDebugger } from './main/roots'
export const Debugger: ParentComponent = props => {
attachDebugger()
return props.children
}
-import plugin from './plugin'
+import plugin from './main/plugin'
export const useDebugger = plugin.useDebugger
export const useLocator = plugin.useLocator
-export type { BatchComputationUpdatesHandler } from './plugin'
+export type { BatchComputationUpdatesHandler } from './main/plugin'
-export { attachDebugger, enableRootsAutoattach } from './roots'
+export { attachDebugger, enableRootsAutoattach } from './main/roots'
export {
makeSolidUpdateListener,
makeCreateRootListener,
- makeStoreObserver,
observeComputationUpdate,
observeValueUpdate,
interceptComputationRerun,
makeValueUpdateListener,
removeValueUpdateObserver,
-} from './update'
-export type { AfterCrateRoot, ObjectObserver } from './update'
+} from './main/update'
+export type { AfterCrateRoot } from './main/update'
export {
+ getOwner,
getOwnerType,
getNodeType,
getNodeName,
@@ -34,11 +34,28 @@ export {
isSolidMemo,
isSolidOwner,
isSolidRoot,
+ isSolidStore,
onOwnerCleanup,
onParentCleanup,
getFunctionSources,
createUnownedRoot,
createInternalRoot,
-} from './utils'
+} from './main/utils'
-export type { LocatorOptions, TargetIDE, TargetURLFunction } from './locator'
+export type {
+ LocatorOptions,
+ TargetIDE,
+ TargetURLFunction,
+ HighlightElementPayload,
+} from './locator'
+
+export type {
+ InspectorUpdate,
+ SetInspectedNodeData,
+ ToggleInspectedValueData,
+ ProxyPropsUpdate,
+ StoreNodeUpdate,
+ ValueNodeUpdate,
+} from './inspector'
+
+export * from './types'
diff --git a/packages/debugger/src/inspect.ts b/packages/debugger/src/inspect.ts
deleted file mode 100644
index be65366e..00000000
--- a/packages/debugger/src/inspect.ts
+++ /dev/null
@@ -1,157 +0,0 @@
-import { Mapped, NodeID, Solid, NodeType, ValueUpdateListener } from '@solid-devtools/shared/graph'
-import { ElementMap, encodeValue, ValueType } from '@solid-devtools/shared/serialize'
-import { $PROXY } from 'solid-js'
-import { observeValueUpdate, removeValueUpdateObserver } from './update'
-import {
- getComponentRefreshNode,
- getNodeName,
- getNodeType,
- isSolidComponent,
- isSolidComputation,
- isSolidMemo,
- markNodeID,
- markNodesID,
- markOwnerName,
- markOwnerType,
-} from './utils'
-
-export type SignalUpdateHandler = (nodeId: NodeID, value: unknown) => void
-
-// Globals set before collecting the owner details
-let $elementMap!: ElementMap
-let $signalMap!: Record
-
-const INSPECTOR = Symbol('inspector')
-
-function mapSignalNode(node: Solid.Signal, handler: SignalUpdateHandler): Mapped.Signal {
- const id = markNodeID(node)
- $signalMap[id] = node
- observeValueUpdate(node, value => handler(id, value), INSPECTOR)
- return {
- type: getNodeType(node) as NodeType.Memo | NodeType.Signal,
- name: getNodeName(node),
- id,
- observers: markNodesID(node.observers),
- value: encodeValue(node.value, false, $elementMap),
- }
-}
-
-export function clearOwnerObservers(owner: Solid.Owner): void {
- if (isSolidComputation(owner)) {
- removeValueUpdateObserver(owner, INSPECTOR)
- }
- if (owner.sourceMap) {
- for (const node of Object.values(owner.sourceMap)) removeValueUpdateObserver(node, INSPECTOR)
- }
- if (owner.owned) {
- for (const node of owner.owned) removeValueUpdateObserver(node, INSPECTOR)
- }
-}
-
-export function encodeComponentProps(
- owner: Solid.Owner,
- config: { inspectedProps?: Set; elementMap: ElementMap },
-): Mapped.Props | null {
- if (!isSolidComponent(owner)) return null
- const { elementMap, inspectedProps } = config
- const { props } = owner
- const proxy = !!(props as any)[$PROXY]
- const record = Object.entries(Object.getOwnPropertyDescriptors(props)).reduce(
- (record, [key, descriptor]) => {
- record[key] =
- 'get' in descriptor
- ? { type: ValueType.Getter, value: key }
- : encodeValue(
- descriptor.value,
- inspectedProps ? inspectedProps.has(key) : false,
- elementMap,
- )
- return record
- },
- {} as Mapped.Props['record'],
- )
- return { proxy, record }
-}
-
-export function collectOwnerDetails(
- owner: Solid.Owner,
- config: {
- onSignalUpdate: SignalUpdateHandler
- onValueUpdate: ValueUpdateListener
- },
-): {
- details: Mapped.OwnerDetails
- signalMap: Record
- elementMap: ElementMap
- getOwnerValue: () => unknown
-} {
- const { onSignalUpdate, onValueUpdate } = config
-
- // Set globals
- $elementMap = new ElementMap()
- $signalMap = {}
-
- const type = markOwnerType(owner)
- let { sourceMap, owned } = owner
- let getValue = () => owner.value
-
- // handle context node specially
- if (type === NodeType.Context) {
- sourceMap = undefined
- owned = null
- const symbols = Object.getOwnPropertySymbols(owner.context)
- if (symbols.length !== 1) {
- console.warn('Context field has more than one symbol. This is not expected.')
- getValue = () => undefined
- } else {
- const contextValue = owner.context[symbols[0]]
- getValue = () => contextValue
- }
- }
-
- // marge component with refresh memo
- let refresh: Solid.Memo | null
- if (isSolidComponent(owner) && (refresh = getComponentRefreshNode(owner))) {
- sourceMap = refresh.sourceMap
- owned = refresh.owned
- getValue = () => refresh!.value
- }
-
- // map signals
- let signals: Mapped.Signal[]
- if (sourceMap) {
- const signalNodes = Object.values(sourceMap)
- signals = Array(signalNodes.length)
- for (let i = 0; i < signalNodes.length; i++) {
- signals[i] = mapSignalNode(signalNodes[i], onSignalUpdate)
- }
- } else signals = []
-
- // map memos
- if (owned) {
- for (const node of owned) {
- if (isSolidMemo(node)) signals.push(mapSignalNode(node, onSignalUpdate))
- }
- }
-
- const details: Mapped.OwnerDetails = {
- id: markNodeID(owner),
- name: markOwnerName(owner),
- type: markOwnerType(owner),
- signals,
- }
-
- if (isSolidComputation(owner)) {
- details.value = encodeValue(getValue(), false, $elementMap)
- observeValueUpdate(owner, onValueUpdate, INSPECTOR)
- details.sources = markNodesID(owner.sources)
- if (isSolidMemo(owner)) {
- details.observers = markNodesID(owner.observers)
- }
- // map component props
- const props = encodeComponentProps(owner, { elementMap: $elementMap })
- if (props) details.props = props
- }
-
- return { details, signalMap: $signalMap, elementMap: $elementMap, getOwnerValue: getValue }
-}
diff --git a/packages/debugger/src/inspector/index.ts b/packages/debugger/src/inspector/index.ts
new file mode 100644
index 00000000..12c9546e
--- /dev/null
+++ b/packages/debugger/src/inspector/index.ts
@@ -0,0 +1,193 @@
+import { Accessor, createEffect, onCleanup, untrack } from 'solid-js'
+import { throttle, scheduleIdle } from '@solid-primitives/scheduled'
+import { warn } from '@solid-devtools/shared/utils'
+import { DebuggerEventHub } from '../main/plugin'
+import { walkSolidRoot } from '../main/roots'
+import { Core, EncodedValue, NodeID, Solid, ValueItemID } from '../main/types'
+import { makeSolidUpdateListener } from '../main/update'
+import { NodeIDMap, encodeValue } from './serialize'
+import { observeStoreNode, StoreUpdateData } from './store'
+import { clearOwnerObservers, collectOwnerDetails, ValueNode, ValueNodeMap } from './inspector'
+
+export type ValueNodeUpdate = { id: ValueItemID; value: EncodedValue }
+
+export type StoreNodeUpdate = {
+ valueNodeId: ValueItemID
+ storeId: NodeID
+ path: readonly (string | number)[]
+ property: string | number
+ /**
+ * `undefined` - property deleted;
+ * `EncodedValue` - property updated;
+ * `number` - array length updated;
+ */
+ value: EncodedValue | undefined | number
+}
+/** List of new keys — all of the values are getters, so they won't change */
+export type ProxyPropsUpdate = { added: string[]; removed: string[] }
+export type InspectorUpdate =
+ | [type: 'value', update: ValueNodeUpdate]
+ | [type: 'store', update: StoreNodeUpdate]
+ | [type: 'props', update: ProxyPropsUpdate]
+
+export type SetInspectedNodeData = null | { rootId: NodeID; nodeId: NodeID }
+export type ToggleInspectedValueData = { id: ValueItemID; selected: boolean }
+
+/**
+ * Plugin module
+ */
+export function createInspector(
+ debuggerEnabled: Accessor,
+ { eventHub }: { eventHub: DebuggerEventHub },
+) {
+ let inspectedOwner: Solid.Owner | null = null
+ let nodeIdMap = new NodeIDMap()
+ let valueMap = new ValueNodeMap()
+ let checkProxyProps: (() => { added: string[]; removed: string[] } | undefined) | undefined
+
+ // Batch and dedupe inspector updates
+ // these will include updates to signals, stores, props, and node value
+ const { pushStoreUpdate, pushValueUpdate, triggerPropsCheck, clearUpdates } = (() => {
+ let valueUpdates = new Set()
+ let storeUpdates: [valueNodeId: ValueItemID, storeId: NodeID, data: StoreUpdateData][] = []
+ let checkProps = false
+
+ const flush = scheduleIdle(() => {
+ const batchedUpdates: InspectorUpdate[] = []
+
+ // Value Nodes (signals, props, and node value)
+ for (const id of valueUpdates) {
+ const node = valueMap.get(id)
+ if (!node || !node.getValue) continue
+ const selected = node.isSelected()
+ const encoded = encodeValue(
+ node.getValue(),
+ selected,
+ nodeIdMap,
+ selected && handleStoreNode.bind(null, id, node),
+ )
+ batchedUpdates.push(['value', { id, value: encoded }])
+ }
+ valueUpdates.clear()
+
+ // Stores
+ for (const [valueNodeId, storeId, data] of storeUpdates)
+ batchedUpdates.push([
+ 'store',
+ {
+ valueNodeId,
+ storeId,
+ value:
+ 'length' in data
+ ? data.length
+ : data.value === undefined
+ ? undefined
+ : encodeValue(data.value, true, nodeIdMap, undefined, true),
+ path: data.path,
+ property: data.property,
+ },
+ ])
+ storeUpdates = []
+
+ // Props (top-level key check of proxy props object)
+ if (checkProps && checkProxyProps) {
+ const keys = checkProxyProps()
+ if (keys) batchedUpdates.push(['props', { added: keys.added, removed: keys.removed }])
+ checkProps = false
+ }
+
+ // Emit updates
+ batchedUpdates.length && eventHub.emit('InspectorUpdate', batchedUpdates)
+ })
+
+ const flushPropsCheck = throttle(flush, 200)
+
+ return {
+ pushValueUpdate(id: ValueItemID) {
+ valueUpdates.add(id)
+ flush()
+ },
+ pushStoreUpdate(valueNodeId: ValueItemID, storeId: NodeID, data: StoreUpdateData) {
+ storeUpdates.push([valueNodeId, storeId, data])
+ flush()
+ },
+ triggerPropsCheck() {
+ checkProps = true
+ flushPropsCheck()
+ },
+ // since the updates are emitten on timeout, we need to make sure that
+ // switching off the debugger or unselecting the owner will clear the updates
+ clearUpdates() {
+ valueUpdates.clear()
+ storeUpdates = []
+ checkProps = false
+ flush.clear()
+ flushPropsCheck.clear()
+ },
+ }
+ })()
+
+ function handleStoreNode(
+ valueId: ValueItemID,
+ valueNode: ValueNode,
+ storeNodeId: NodeID,
+ storeNode: Core.Store.StoreNode,
+ ) {
+ valueNode.addStoreObserver(
+ observeStoreNode(storeNode, data => pushStoreUpdate(valueId, storeNodeId, data)),
+ )
+ }
+
+ function setInspectedDetails(owner: Solid.Owner | null) {
+ inspectedOwner && clearOwnerObservers(inspectedOwner)
+ inspectedOwner = owner
+ checkProxyProps = undefined
+ valueMap.reset()
+ clearUpdates()
+ if (!owner) return
+
+ untrack(() => {
+ const result = collectOwnerDetails(owner, {
+ onSignalUpdate: id => pushValueUpdate(`signal:${id}`),
+ onValueUpdate: () => pushValueUpdate('value'),
+ })
+ eventHub.emit('InspectedNodeDetails', result.details)
+ valueMap = result.valueMap
+ nodeIdMap = result.nodeIdMap
+ checkProxyProps = result.checkProxyProps
+ })
+ }
+
+ createEffect(() => {
+ if (!debuggerEnabled()) return
+
+ // Clear the inspected owner when the debugger is disabled
+ onCleanup(() => setInspectedDetails(null))
+
+ makeSolidUpdateListener(() => {
+ if (checkProxyProps) triggerPropsCheck()
+ })
+ })
+
+ return {
+ setInspectedNode(data: { rootId: NodeID; nodeId: NodeID } | null) {
+ if (!data) return setInspectedDetails(null)
+ const { rootId, nodeId } = data
+
+ const walkResult = walkSolidRoot(rootId, nodeId)
+ if (!walkResult || !walkResult.inspectedOwner) return setInspectedDetails(null)
+
+ setInspectedDetails(walkResult.inspectedOwner)
+ },
+ toggleValueNode({ id, selected }: ToggleInspectedValueData): void {
+ const node = valueMap.get(id)
+ if (!node) return warn('Could not find value node:', id)
+ node.setSelected(selected)
+ pushValueUpdate(id)
+ },
+ getElementById(id: NodeID): HTMLElement | undefined {
+ const el = nodeIdMap.get(id)
+ if (el instanceof HTMLElement) return el
+ },
+ }
+}
diff --git a/packages/debugger/src/inspector/inspector.ts b/packages/debugger/src/inspector/inspector.ts
new file mode 100644
index 00000000..ee7aa20f
--- /dev/null
+++ b/packages/debugger/src/inspector/inspector.ts
@@ -0,0 +1,217 @@
+import { $PROXY } from 'solid-js'
+import { NodeType, ValueType } from '../types'
+import type { Core, Mapped, NodeID, Solid, ValueItemID, ValueUpdateListener } from '../types'
+import { observeValueUpdate, removeValueUpdateObserver } from '../main/update'
+import {
+ getComponentRefreshNode,
+ getDisplayName,
+ getNodeName,
+ getNodeType,
+ getStoreNodeName,
+ isSolidComponent,
+ isSolidComputation,
+ isSolidMemo,
+ isSolidStore,
+ markNodeID,
+ markNodesID,
+ markOwnerName,
+ markOwnerType,
+} from '../main/utils'
+import { NodeIDMap, encodeValue } from './serialize'
+
+export class ValueNode {
+ private trackedStores: VoidFunction[] = []
+ private selected = false
+ constructor(public getValue: (() => unknown) | undefined) {}
+ addStoreObserver(unsub: VoidFunction) {
+ this.trackedStores.push(unsub)
+ }
+ private unsubscribe() {
+ for (const unsub of this.trackedStores) unsub()
+ this.trackedStores = []
+ }
+ reset() {
+ this.unsubscribe()
+ this.selected = false
+ }
+ isSelected() {
+ return this.selected
+ }
+ setSelected(selected: boolean) {
+ this.selected = selected
+ if (!selected) this.unsubscribe()
+ }
+}
+
+export class ValueNodeMap {
+ private record = {} as Record
+ get(id: ValueItemID): ValueNode | undefined {
+ return this.record[id]
+ }
+ add(id: ValueItemID, getValue: (() => unknown) | undefined) {
+ this.record[id] = new ValueNode(getValue)
+ }
+ reset() {
+ for (const signal of Object.values(this.record)) signal.reset()
+ }
+}
+
+// Globals set before collecting the owner details
+let $nodeIdMap!: NodeIDMap
+let $valueMap!: ValueNodeMap
+
+const INSPECTOR = Symbol('inspector')
+
+function mapSignalNode(
+ node: Solid.Signal | Solid.Store,
+ handler: (nodeId: NodeID, value: unknown) => void,
+): Mapped.Signal {
+ const { value } = node
+ const id = markNodeID(node)
+ let name: string
+ $valueMap.add(`signal:${id}`, () => node.value)
+
+ if (isSolidStore(node)) {
+ name = getDisplayName(getStoreNodeName(value as Core.Store.StoreNode))
+ } else {
+ name = getNodeName(node)
+ observeValueUpdate(node, v => handler(id, v), INSPECTOR)
+ }
+
+ return {
+ type: getNodeType(node) as NodeType.Memo | NodeType.Signal | NodeType.Store,
+ name,
+ id,
+ value: encodeValue(value, false, $nodeIdMap),
+ }
+}
+
+export function clearOwnerObservers(owner: Solid.Owner): void {
+ if (isSolidComputation(owner)) {
+ removeValueUpdateObserver(owner, INSPECTOR)
+ }
+ if (owner.sourceMap) {
+ for (const node of Object.values(owner.sourceMap)) removeValueUpdateObserver(node, INSPECTOR)
+ }
+ if (owner.owned) {
+ for (const node of owner.owned) removeValueUpdateObserver(node, INSPECTOR)
+ }
+}
+
+export function collectOwnerDetails(
+ owner: Solid.Owner,
+ config: {
+ onSignalUpdate: (nodeId: NodeID, value: unknown) => void
+ onValueUpdate: ValueUpdateListener
+ },
+) {
+ const { onSignalUpdate, onValueUpdate } = config
+
+ // Set globals
+ $nodeIdMap = new NodeIDMap()
+ $valueMap = new ValueNodeMap()
+
+ const type = markOwnerType(owner)
+ let { sourceMap, owned } = owner
+ let getValue = () => owner.value
+
+ // handle context node specially
+ if (type === NodeType.Context) {
+ sourceMap = undefined
+ owned = null
+ const symbols = Object.getOwnPropertySymbols(owner.context)
+ if (symbols.length !== 1) {
+ throw new Error('Context field has more than one symbol. This is not expected.')
+ } else {
+ const contextValue = owner.context[symbols[0]]
+ getValue = () => contextValue
+ }
+ }
+
+ // marge component with refresh memo
+ let refresh: Solid.Memo | null
+ if (isSolidComponent(owner) && (refresh = getComponentRefreshNode(owner))) {
+ sourceMap = refresh.sourceMap
+ owned = refresh.owned
+ getValue = () => refresh!.value
+ }
+
+ // map signals
+ let signals: Mapped.Signal[]
+ if (sourceMap) {
+ const signalNodes = Object.values(sourceMap)
+ signals = Array(signalNodes.length)
+ for (let i = 0; i < signalNodes.length; i++) {
+ signals[i] = mapSignalNode(signalNodes[i], onSignalUpdate)
+ }
+ } else signals = []
+
+ // map memos
+ if (owned) {
+ for (const node of owned) {
+ if (isSolidMemo(node)) signals.push(mapSignalNode(node, onSignalUpdate))
+ }
+ }
+
+ const details: Mapped.OwnerDetails = {
+ id: markNodeID(owner),
+ name: markOwnerName(owner),
+ type: markOwnerType(owner),
+ signals,
+ }
+
+ let checkProxyProps: (() => { added: string[]; removed: string[] } | undefined) | undefined
+
+ if (isSolidComputation(owner)) {
+ details.value = encodeValue(getValue(), false, $nodeIdMap)
+ observeValueUpdate(owner, onValueUpdate, INSPECTOR)
+ details.sources = markNodesID(owner.sources)
+
+ // Component Props
+ if (isSolidComponent(owner)) {
+ const { props } = owner
+ // proxy props need to be checked for changes
+ const proxy = !!(props as any)[$PROXY]
+ const record: Mapped.Props['record'] = {}
+ for (const [key, desc] of Object.entries(Object.getOwnPropertyDescriptors(props))) {
+ if (desc.get) {
+ record[key] = { type: ValueType.Getter, value: key }
+ } else {
+ record[key] = encodeValue(desc.value, false, $nodeIdMap)
+ // non-object props cannot be inspected (won't ever change and aren't deep)
+ desc.value instanceof Object && $valueMap.add(`prop:${key}`, () => desc.value)
+ }
+ }
+ details.props = { proxy, record }
+
+ if (proxy) {
+ let oldKeys: readonly string[] = Object.keys(record)
+ checkProxyProps = () => {
+ const newKeys = Object.keys(props)
+ const added = new Set(newKeys)
+ const removed: string[] = []
+ let changed = false
+ for (const key of oldKeys) {
+ if (added.has(key)) added.delete(key)
+ else {
+ changed = true
+ removed.push(key)
+ }
+ }
+ if (!changed && !added.size) return
+ oldKeys = newKeys
+ return { added: Array.from(added), removed }
+ }
+ }
+ }
+ }
+
+ $valueMap.add('value', getValue)
+
+ return {
+ details,
+ valueMap: $valueMap,
+ nodeIdMap: $nodeIdMap,
+ checkProxyProps,
+ }
+}
diff --git a/packages/debugger/src/inspector/serialize.ts b/packages/debugger/src/inspector/serialize.ts
new file mode 100644
index 00000000..62f46889
--- /dev/null
+++ b/packages/debugger/src/inspector/serialize.ts
@@ -0,0 +1,121 @@
+import { unwrap } from 'solid-js/store'
+import {
+ Core,
+ EncodedValue,
+ EncodedValueOf,
+ NodeID,
+ INFINITY,
+ NAN,
+ NEGATIVE_INFINITY,
+ ValueType,
+} from '../types'
+import { isStoreNode } from '../main/utils'
+
+/**
+ * Encodes any value to a JSON-serializable object.
+ * @param value
+ * @param deep shallow, or deep encoding
+ * @param nodeMap for HTML elements and store nodes, to assign a unique ID to each element
+ * @param handleStore handle encountered store nodes
+ * @returns encoded value
+ */
+export function encodeValue(
+ value: unknown,
+ deep: Deep,
+ nodeMap: NodeIDMap,
+ handleStore?:
+ | false
+ | undefined
+ | ((storeNodeId: NodeID, storeNode: Core.Store.StoreNode) => void),
+ inStore = false,
+): EncodedValue {
+ if (typeof value === 'number') {
+ if (value === Infinity) return { type: ValueType.Number, value: INFINITY }
+ if (value === -Infinity) return { type: ValueType.Number, value: NEGATIVE_INFINITY }
+ if (Number.isNaN(value)) return { type: ValueType.Number, value: NAN }
+ return { type: ValueType.Number, value }
+ }
+ if (typeof value === 'boolean') return { type: ValueType.Boolean, value }
+ if (typeof value === 'string') return { type: ValueType.String, value }
+ if (value === null) return { type: ValueType.Null }
+ if (value === undefined) return { type: ValueType.Undefined }
+ if (typeof value === 'symbol') return { type: ValueType.Symbol, value: value.description ?? '' }
+ if (typeof value === 'function') return { type: ValueType.Function, value: value.name }
+
+ if (value instanceof HTMLElement)
+ return {
+ type: ValueType.Element,
+ value: { name: value.tagName, id: nodeMap.set(value) },
+ }
+
+ if (!inStore && isStoreNode(value)) {
+ // might still pass in a proxy
+ const node = unwrap(value)
+ const id = nodeMap.set(node)
+ handleStore && handleStore(id, node)
+ return {
+ type: ValueType.Store,
+ value: {
+ value: encodeValue(node, deep, nodeMap, undefined, true) as EncodedValue,
+ id,
+ },
+ }
+ }
+
+ if (Array.isArray(value)) {
+ const payload = {
+ type: ValueType.Array,
+ value: value.length,
+ } as EncodedValueOf
+ if (deep)
+ (payload as EncodedValueOf).children = value.map(item =>
+ encodeValue(item, true, nodeMap, handleStore, inStore),
+ )
+ return payload
+ }
+
+ const s = Object.prototype.toString.call(value)
+ const name = s.slice(8, -1)
+ if (name === 'Object') {
+ const obj = value as Record
+ const payload: EncodedValueOf = {
+ type: ValueType.Object,
+ value: Object.keys(obj).length,
+ }
+ if (deep) {
+ const children = ((payload as unknown as EncodedValueOf).children =
+ {} as Record>)
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) {
+ children[key] = descriptor.get
+ ? { type: ValueType.Getter, value: key }
+ : encodeValue(descriptor.value, true, nodeMap, handleStore, inStore)
+ }
+ }
+ return payload
+ }
+
+ return { type: ValueType.Instance, value: name }
+}
+
+let lastId = 0
+
+export class NodeIDMap {
+ private obj: Record = {}
+ private map: WeakMap = new WeakMap()
+
+ get(id: NodeID): T | undefined {
+ return this.obj[id]
+ }
+ getId(element: T): NodeID | undefined {
+ return this.map.get(element)
+ }
+
+ set(element: T): NodeID {
+ let id = this.map.get(element)
+ if (id !== undefined) return id
+ id = (lastId++).toString(36)
+ this.obj[id] = element
+ this.map.set(element, id)
+ return id
+ }
+}
diff --git a/packages/debugger/src/inspector/store.ts b/packages/debugger/src/inspector/store.ts
new file mode 100644
index 00000000..ec7364a2
--- /dev/null
+++ b/packages/debugger/src/inspector/store.ts
@@ -0,0 +1,108 @@
+import { untrack } from 'solid-js'
+import { DEV as STORE_DEV, unwrap } from 'solid-js/store'
+import type { Core, Solid } from '../types'
+
+//
+// GLOBALS
+//
+
+const DEV = STORE_DEV!
+
+const $listeners = new WeakMap()
+
+// path solid global dev hook
+globalThis._$onStoreNodeUpdate = (node, property, value, prev) => {
+ const listeners = $listeners.get(node)
+ if (listeners) for (const fn of listeners) fn(node, property, value, prev)
+}
+
+//
+//
+
+export type StoreUpdateData = { path: readonly (string | number)[]; property: string | number } & (
+ | { value: unknown }
+ | { length: number }
+)
+export type StoreUpdateHandler = (data: StoreUpdateData) => void
+
+function forEachStoreProp(
+ node: Core.Store.StoreNode,
+ fn: (key: string, node: Core.Store.StoreNode) => void,
+): void {
+ if (Array.isArray(node)) {
+ for (let i = 0; i < node.length; i++) {
+ const child = node[i]
+ DEV.isWrappable(child) && fn(i.toString(), child)
+ }
+ } else {
+ for (const key in node) {
+ const { value, get } = Object.getOwnPropertyDescriptor(node, key)!
+ if (!get && DEV.isWrappable(value)) fn(key, value)
+ }
+ }
+}
+
+function matchHandler(
+ { storePath, storeSymbol }: Solid.OnStoreNodeUpdate,
+ symbol: symbol,
+ path: readonly (string | number)[],
+) {
+ if (storeSymbol !== symbol || storePath.length !== path.length) return false
+ if (storePath == path) return true
+ for (let i = 0; i < storePath.length; i++) {
+ // loose equality is intentional — we want to match numbers and strings as they access the same property
+ if (storePath[i] != path[i]) return false
+ }
+ return true
+}
+
+export function observeStoreNode(
+ rootNode: Core.Store.StoreNode,
+ onUpdate: StoreUpdateHandler,
+): VoidFunction {
+ // might still pass in a proxy
+ rootNode = unwrap(rootNode)
+
+ const symbol = Symbol('inspect-store')
+
+ return untrack(() => {
+ trackStore(rootNode, [])
+ return () => untrackStore(rootNode, [])
+ })
+
+ function trackStore(node: Core.Store.StoreNode, path: readonly (string | number)[]): void {
+ const handlers = $listeners.get(node)
+ if (handlers && handlers.some(fn => matchHandler(fn, symbol, path))) return
+
+ const handler: Solid.OnStoreNodeUpdate = ((_, property, value, prev) => {
+ if (typeof property === 'symbol') return
+ const propertyPath = [...path, property] as const
+ untrack(() => {
+ if (property === 'length' && typeof value === 'number' && Array.isArray(node)) {
+ // Update array length
+ onUpdate({ path, property, length: value })
+ } else {
+ if (DEV.isWrappable(prev)) untrackStore(prev as Core.Store.StoreNode, propertyPath)
+ if (DEV.isWrappable(value)) trackStore(value as Core.Store.StoreNode, propertyPath)
+ onUpdate({ path, property, value })
+ }
+ })
+ }) as Solid.OnStoreNodeUpdate
+ handler.storePath = path
+ handler.storeSymbol = symbol
+ handlers ? handlers.push(handler) : $listeners.set(node, [handler])
+ forEachStoreProp(node, (property, child) => trackStore(child, [...path, property]))
+ }
+
+ function untrackStore(node: Core.Store.StoreNode, path: readonly (string | number)[]) {
+ const handlers = $listeners.get(node)
+ if (!handlers) return
+ const r = handlers.splice(
+ handlers.findIndex(h => matchHandler(h, symbol, path)),
+ 1,
+ )
+ if (handlers.length === 0) $listeners.delete(node)
+ if (r.length)
+ forEachStoreProp(node, (property, child) => untrackStore(child, [...path, property]))
+ }
+}
diff --git a/packages/debugger/test/inspect.test.tsx b/packages/debugger/src/inspector/test/index.test.tsx
similarity index 62%
rename from packages/debugger/test/inspect.test.tsx
rename to packages/debugger/src/inspector/test/index.test.tsx
index 7284d6b3..e98e1385 100644
--- a/packages/debugger/test/inspect.test.tsx
+++ b/packages/debugger/src/inspector/test/index.test.tsx
@@ -1,6 +1,4 @@
import { describe, beforeEach, vi, it, expect } from 'vitest'
-import { getOwner, NodeType, Solid } from '@solid-devtools/shared/graph'
-import { ValueType } from '@solid-devtools/shared/serialize'
import {
createComputed,
createMemo,
@@ -9,10 +7,10 @@ import {
createSignal,
JSX,
} from 'solid-js'
-import type * as API from '../src/inspect'
+import { NodeType, Solid, ValueType } from '../../types'
+import { getOwner } from '../../main/utils'
-const getModule = async (): Promise =>
- (await import('../src/inspect')).collectOwnerDetails
+const getInspectModule = async () => await import('../inspector')
describe('collectOwnerDetails', () => {
beforeEach(() => {
@@ -21,7 +19,7 @@ describe('collectOwnerDetails', () => {
})
it('collects focused owner details', async () => {
- const collectOwnerDetails = await getModule()
+ const { collectOwnerDetails } = await getInspectModule()
createRoot(dispose => {
const [s] = createSignal(0, { name: 'source' })
@@ -50,7 +48,7 @@ describe('collectOwnerDetails', () => {
{ name: 'WRAPPER' },
)
- const { details, signalMap, elementMap } = collectOwnerDetails(owner, {
+ const { details, valueMap, nodeIdMap } = collectOwnerDetails(owner, {
onSignalUpdate: () => {},
onValueUpdate: () => {},
})
@@ -60,39 +58,34 @@ describe('collectOwnerDetails', () => {
name: 'focused',
type: NodeType.Memo,
value: { type: ValueType.String, value: 'value' },
- sources: ['3'],
- observers: ['4'],
+ sources: ['2'],
signals: [
{
type: NodeType.Signal,
id: '0',
name: 'element',
- observers: [],
value: { type: ValueType.Element, value: { name: 'DIV', id: '0' } },
},
{
type: NodeType.Memo,
id: '1',
name: 'memo',
- observers: ['2'],
value: { type: ValueType.Number, value: 0 },
},
],
})
- expect(signalMap).toHaveProperty('0')
- expect(signalMap).toHaveProperty('1')
- expect(signalMap['0'].sdtId).toBe('0')
- expect(signalMap['1'].sdtId).toBe('1')
+ expect(valueMap.get('signal:0')).toBeTruthy()
+ expect(valueMap.get('signal:1')).toBeTruthy()
- expect(elementMap.get('0')).toBe(div)
+ expect(nodeIdMap.get('0')).toBe(div)
dispose()
})
})
it('component props', async () => {
- const collectOwnerDetails = await getModule()
+ const { collectOwnerDetails } = await getInspectModule()
createRoot(dispose => {
let owner!: Solid.Owner
@@ -110,7 +103,7 @@ describe('collectOwnerDetails', () => {
))
- const { details, elementMap } = collectOwnerDetails(owner, {
+ const { details, nodeIdMap } = collectOwnerDetails(owner, {
onSignalUpdate: () => {},
onValueUpdate: () => {},
})
@@ -123,7 +116,7 @@ describe('collectOwnerDetails', () => {
type: NodeType.Component,
signals: [],
sources: [],
- value: { type: ValueType.Element, value: { id: '1', name: 'DIV' } },
+ value: { type: ValueType.Element, value: { id: '0', name: 'DIV' } },
props: {
proxy: false,
record: {
@@ -134,12 +127,12 @@ describe('collectOwnerDetails', () => {
},
})
- expect(elementMap.get('1')).toBeInstanceOf(HTMLDivElement)
+ expect(nodeIdMap.get('0')).toBeInstanceOf(HTMLDivElement)
})
})
it('dynamic component props', async () => {
- const collectOwnerDetails = await getModule()
+ const { collectOwnerDetails } = await getInspectModule()
createRoot(dispose => {
let owner!: Solid.Owner
@@ -152,7 +145,7 @@ describe('collectOwnerDetails', () => {
return
})
- const { details, elementMap } = collectOwnerDetails(owner, {
+ const { details, nodeIdMap } = collectOwnerDetails(owner, {
onSignalUpdate: () => {},
onValueUpdate: () => {},
})
@@ -163,9 +156,8 @@ describe('collectOwnerDetails', () => {
type: NodeType.Component,
signals: [],
sources: [],
- value: { type: ValueType.Element, value: { id: '2', name: 'BUTTON' } },
+ value: { type: ValueType.Element, value: { id: '0', name: 'BUTTON' } },
props: {
- // ! this should be true, don't know what's the reason. it's working in the browser
proxy: true,
record: {
onClick: { type: ValueType.Getter, value: 'onClick' },
@@ -174,71 +166,14 @@ describe('collectOwnerDetails', () => {
},
})
- expect(elementMap.get('2')).toBeInstanceOf(HTMLButtonElement)
+ expect(nodeIdMap.get('0')).toBeInstanceOf(HTMLButtonElement)
dispose()
})
})
- // * collectOwnerDetails doesn't allow for inspected props now
- // test("inspected component props", () => {
- // const collectOwnerDetails = getModule()
-
- // createRoot(dispose => {
-
- // let owner!: Solid.Owner
- // const TestComponent = (props: {
- // count: number
- // children: JSX.Element
- // nested: { foo: number; bar: string }
- // }) => {
- // owner = getOwner()!
- // return {props.children}
- // }
- // createRenderEffect(() => (
- //
- // Click me
- //
- // ))
-
- // const { details, elementMap } = collectOwnerDetails(owner, {
- // elementMap,
- // inspectedProps: new Set(["nested"]),
- // onSignalUpdate: () => {},
- // })
-
- // dispose()
-
- // expect(details).toEqual({
- // id: "0",
- // name: "TestComponent",
- // type: NodeType.Component,
- // signals: [],
- // sources: [],
- // value: { type: ValueType.Element, value: { id: "0", name: "DIV" } },
- // props: {
- // proxy: false,
- // record: {
- // count: { type: ValueType.Number, value: 123 },
- // children: { type: ValueType.Getter, value: "children" },
- // nested: {
- // type: ValueType.Object,
- // value: 2,
- // children: {
- // foo: { type: ValueType.Number, value: 1 },
- // bar: { type: ValueType.String, value: "2" },
- // },
- // },
- // },
- // },
- // })
-
- // expect(elementMap.get("0")).toBeInstanceOf(HTMLDivElement)
- // })
- // })
-
it('listens to value updates', async () => {
- const collectOwnerDetails = await getModule()
+ const { collectOwnerDetails } = await getInspectModule()
createRoot(dispose => {
let owner!: Solid.Owner
@@ -273,7 +208,7 @@ describe('collectOwnerDetails', () => {
})
it('listens to signal updates', async () => {
- const collectOwnerDetails = await getModule()
+ const { collectOwnerDetails } = await getInspectModule()
createRoot(dispose => {
let owner = getOwner()!
diff --git a/packages/shared/test/serialise.test.ts b/packages/debugger/src/inspector/test/serialise.test.ts
similarity index 86%
rename from packages/shared/test/serialise.test.ts
rename to packages/debugger/src/inspector/test/serialise.test.ts
index 82a3ca0d..9637babe 100644
--- a/packages/shared/test/serialise.test.ts
+++ b/packages/debugger/src/inspector/test/serialise.test.ts
@@ -1,13 +1,7 @@
-import { describe, test, expect } from 'vitest'
-import {
- ValueType,
- INFINITY,
- NEGATIVE_INFINITY,
- NAN,
- encodeValue,
- EncodedValue,
- ElementMap,
-} from '../src/serialize'
+import { describe, test, expect, vi } from 'vitest'
+import { EncodedValue, INFINITY, NAN, NEGATIVE_INFINITY, ValueType } from '../../types'
+
+const getModule = async () => await import('../serialize')
const _testFunction = () => {}
@@ -29,9 +23,13 @@ const encodePreviewExpectations: [string, unknown, EncodedValue][] = [
[
'Element div',
document.createElement('div'),
- { type: ValueType.Element, value: { name: 'DIV' } },
+ { type: ValueType.Element, value: { name: 'DIV', id: '0' } },
+ ],
+ [
+ 'Element a',
+ document.createElement('a'),
+ { type: ValueType.Element, value: { name: 'A', id: '1' } },
],
- ['Element a', document.createElement('a'), { type: ValueType.Element, value: { name: 'A' } }],
['Array empty', [], { type: ValueType.Array, value: 0 }],
['Array', [1, 2, 3], { type: ValueType.Array, value: 3 }],
['Object empty', {}, { type: ValueType.Object, value: 0 }],
@@ -43,10 +41,13 @@ const encodePreviewExpectations: [string, unknown, EncodedValue][] = [
['Set', new Set(), { type: ValueType.Instance, value: 'Set' }],
]
-describe('encodeValue Preview', () => {
+describe('encodeValue Preview', async () => {
+ vi.resetModules()
+ const { encodeValue, NodeIDMap } = await getModule()
+
for (const [testName, value, expectation] of encodePreviewExpectations) {
test(testName, () => {
- const s = encodeValue(value, false)
+ const s = encodeValue(value, false, new NodeIDMap())
expect(s).toEqual(expectation)
expect(JSON.parse(JSON.stringify(s))).toEqual(s)
})
@@ -157,17 +158,23 @@ const encodeDeepExpectations: [string, unknown, EncodedValue][] = [
],
]
-describe('encodeValue Deep', () => {
+describe('encodeValue Deep', async () => {
+ vi.resetModules()
+ const { encodeValue, NodeIDMap } = await getModule()
+
for (const [testName, value, expectation] of encodeDeepExpectations) {
test(testName, () => {
- const s = encodeValue(value, true)
+ const s = encodeValue(value, true, new NodeIDMap())
expect(s).toEqual(expectation)
expect(JSON.parse(JSON.stringify(s))).toEqual(s)
})
}
})
-describe('save elements to a map', () => {
+describe('save elements to a map', async () => {
+ vi.resetModules()
+ const { encodeValue, NodeIDMap } = await getModule()
+
const div1 = document.createElement('div')
const a1 = document.createElement('a')
const div2 = document.createElement('div')
@@ -186,7 +193,7 @@ describe('save elements to a map', () => {
],
]
- const map = new ElementMap()
+ const map = new NodeIDMap()
for (const [testName, value, expectation] of elMapExpectations) {
test(testName, () => {
const s = encodeValue(value, true, map)
diff --git a/packages/debugger/src/inspector/test/store.test.ts b/packages/debugger/src/inspector/test/store.test.ts
new file mode 100644
index 00000000..628b9710
--- /dev/null
+++ b/packages/debugger/src/inspector/test/store.test.ts
@@ -0,0 +1,262 @@
+import { describe, vi, it, expect } from 'vitest'
+import { createRoot } from 'solid-js'
+import {
+ createMutable,
+ createStore,
+ produce,
+ reconcile,
+ unwrap,
+ modifyMutable,
+} from 'solid-js/store'
+import { Solid } from '../../types'
+import { observeStoreNode, StoreUpdateHandler } from '../store'
+import { getOwner, isSolidStore } from '../../main/utils'
+
+const getOwnerStore = () =>
+ (Object.values(getOwner()!.sourceMap!).find(s => isSolidStore(s))! as Solid.Store).value
+
+describe('observeStoreNode', () => {
+ it('listens to simple store updates', () => {
+ createRoot(dispose => {
+ const [state, setState] = createStore({ count: 0 })
+ const store = getOwnerStore()
+ const cb = vi.fn, void>()
+ const unsub = observeStoreNode(store, cb)
+ expect(cb).not.toBeCalled()
+
+ setState({ count: 1 })
+ expect(cb).toBeCalledTimes(1)
+ let args: Parameters[0] = {
+ path: [],
+ property: 'count',
+ value: 1,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ setState('count', 2)
+ expect(cb).toBeCalledTimes(2)
+ args = {
+ path: [],
+ property: 'count',
+ value: 2,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ setState(reconcile({}))
+ expect(cb).toBeCalledTimes(3)
+ args = {
+ path: [],
+ property: 'count',
+ value: undefined,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ setState({ a: { foo: 123, bar: [1, 2, 3] }, b: 'hello' })
+ expect(cb).toBeCalledTimes(5)
+ args = {
+ path: [],
+ property: 'a',
+ value: unwrap(state.a),
+ }
+ expect(cb).toHaveBeenNthCalledWith(4, args)
+ args = {
+ path: [],
+ property: 'b',
+ value: 'hello',
+ }
+ expect(cb).toHaveBeenNthCalledWith(5, args)
+
+ setState(produce((proxy: any) => delete proxy.a.foo))
+ expect(cb).toBeCalledTimes(6)
+ args = {
+ path: ['a'],
+ property: 'foo',
+ value: undefined,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ setState(produce((proxy: any) => proxy.a.bar.push(4)))
+ expect(cb).toBeCalledTimes(7)
+ args = {
+ path: ['a', 'bar'],
+ property: '3',
+ value: 4,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ unsub()
+ setState({ count: 3 })
+ expect(cb).toBeCalledTimes(7)
+
+ dispose()
+ })
+ })
+
+ it('listens to mutable', () => {
+ createRoot(dispose => {
+ const state = createMutable({ count: 0 })
+ const store = getOwnerStore()
+ const cb = vi.fn, void>()
+ const unsub = observeStoreNode(store, cb)
+ expect(cb).not.toBeCalled()
+
+ state.count = 1
+ expect(cb).toBeCalledTimes(1)
+ let args: Parameters[0] = {
+ path: [],
+ property: 'count',
+ value: 1,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ modifyMutable(state, reconcile({}))
+ expect(cb).toBeCalledTimes(2)
+ args = {
+ path: [],
+ property: 'count',
+ value: undefined,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ state.a = { foo: 123, bar: [1, 2, 3] }
+ expect(cb).toBeCalledTimes(3)
+ args = {
+ path: [],
+ property: 'a',
+ value: unwrap(state.a),
+ }
+ expect(cb).toBeCalledWith(args)
+
+ delete state.a.foo
+ expect(cb).toBeCalledTimes(4)
+ args = {
+ path: ['a'],
+ property: 'foo',
+ value: undefined,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ state.a.bar.push(4)
+ expect(cb).toBeCalledTimes(5)
+ args = {
+ path: ['a', 'bar'],
+ property: '3',
+ value: 4,
+ }
+ expect(cb).toBeCalledWith(args)
+
+ unsub()
+ state.count = 3
+ expect(cb).toBeCalledTimes(5)
+
+ dispose()
+ })
+ })
+
+ it('ignores updates to removed object', () => {
+ createRoot(dispose => {
+ const state = createMutable({ a: { foo: 123, bar: [1, 2, 3] } })
+ const store = getOwnerStore()
+ const cb = vi.fn, void>()
+ const unsub = observeStoreNode(store, cb)
+
+ const detached = state.a.bar
+
+ const a1 = (state.a = { other: 'hello' })
+ let args: Parameters[0] = {
+ path: [],
+ property: 'a',
+ value: a1,
+ }
+ expect(cb).toBeCalledTimes(1)
+ expect(cb).toBeCalledWith(args)
+
+ detached.push(4)
+ expect(cb).toBeCalledTimes(1)
+
+ state.a.bar = detached
+ args = {
+ path: ['a'],
+ property: 'bar',
+ value: detached,
+ }
+ expect(cb).toBeCalledTimes(2)
+ expect(cb).toBeCalledWith(args)
+
+ delete state.a.bar
+ args = {
+ path: ['a'],
+ property: 'bar',
+ value: undefined,
+ }
+ expect(cb).toBeCalledTimes(3)
+ expect(cb).toBeCalledWith(args)
+
+ detached.push(5)
+ expect(cb).toBeCalledTimes(3)
+
+ unsub()
+ state.a.baz = "I'm back!"
+ expect(cb).toBeCalledTimes(3)
+
+ dispose()
+ })
+ })
+
+ it('handles multiple inspected nodes', () => {
+ createRoot(dispose => {
+ const state = createMutable({ a: { foo: 123, bar: [1, 2, 3] } })
+ const rootCb = vi.fn()
+ const unsubRoot = observeStoreNode(getOwnerStore(), rootCb)
+
+ const cb = vi.fn()
+ const unsubBranch = observeStoreNode(unwrap(state.a.bar), cb)
+
+ state.a.bar.push(4)
+ let args: Parameters[0] = {
+ path: ['a', 'bar'],
+ property: '3',
+ value: 4,
+ }
+ expect(rootCb).toBeCalledTimes(1)
+ expect(rootCb).toBeCalledWith(args)
+ args = {
+ path: [],
+ property: '3',
+ value: 4,
+ }
+ expect(cb).toBeCalledTimes(1)
+ expect(cb).toBeCalledWith(args)
+
+ const arr = state.a.bar
+ delete state.a.bar
+ args = {
+ path: ['a'],
+ property: 'bar',
+ value: undefined,
+ }
+ expect(rootCb).toBeCalledTimes(2)
+ expect(rootCb).toBeCalledWith(args)
+ expect(cb).toBeCalledTimes(1)
+
+ arr.push(5)
+ expect(rootCb).toBeCalledTimes(2)
+ args = {
+ path: [],
+ property: '4',
+ value: 5,
+ }
+ expect(cb).toBeCalledTimes(2)
+ expect(cb).toBeCalledWith(args)
+
+ unsubBranch()
+ arr.push(6)
+ unsubRoot()
+ state.baz = 'hello'
+ expect(rootCb).toBeCalledTimes(2)
+ expect(cb).toBeCalledTimes(2)
+
+ dispose()
+ })
+ })
+})
diff --git a/packages/debugger/src/locator/findComponent.ts b/packages/debugger/src/locator/findComponent.ts
index 4534cf9f..bae5295b 100644
--- a/packages/debugger/src/locator/findComponent.ts
+++ b/packages/debugger/src/locator/findComponent.ts
@@ -1,6 +1,6 @@
-import { Mapped, NodeID } from '@solid-devtools/shared/graph'
import { LOCATION_ATTRIBUTE_NAME } from '@solid-devtools/shared/variables'
import { isWindows } from '@solid-primitives/platform'
+import { Mapped, NodeID } from '../types'
import { ElementLocation } from './goToSource'
export type LocatorComponent = {
diff --git a/packages/debugger/src/locator/index.ts b/packages/debugger/src/locator/index.ts
index fcb291dc..199bd7f7 100644
--- a/packages/debugger/src/locator/index.ts
+++ b/packages/debugger/src/locator/index.ts
@@ -4,7 +4,7 @@ import { createKeyHold, KbdKey } from '@solid-primitives/keyboard'
import { onRootCleanup } from '@solid-primitives/utils'
import { createSimpleEmitter } from '@solid-primitives/event-bus'
import { atom, defer, makeHoverElementListener } from '@solid-devtools/shared/primitives'
-import { Mapped, NodeID } from '@solid-devtools/shared/graph'
+import { warn } from '@solid-devtools/shared/utils'
import { findLocatorComponent, getLocationFromElement, LocatorComponent } from './findComponent'
import {
getFullSourceCodeData,
@@ -13,9 +13,10 @@ import {
TargetIDE,
TargetURLFunction,
} from './goToSource'
-import { createInternalRoot } from '../utils'
-import { enableRootsAutoattach } from '../roots'
+import { createInternalRoot } from '../main/utils'
+import { enableRootsAutoattach } from '../main/roots'
import { attachElementOverlay } from './ElementOverlay'
+import { Mapped, NodeID } from '../types'
export type { TargetIDE, TargetURLFunction } from './goToSource'
export type { LocatorComponent } from './findComponent'
@@ -27,6 +28,11 @@ export type LocatorOptions = {
key?: KbdKey
}
+export type HighlightElementPayload =
+ | { rootId: NodeID; nodeId: NodeID }
+ | { elementId: string }
+ | null
+
export type ClickMiddleware = (
e: MouseEvent,
component: LocatorComponent,
@@ -105,21 +111,19 @@ export function createLocator({
return comp?.id
})
- function setPluginHighlightTarget(
- data: { elementId: NodeID } | { rootId: NodeID; nodeId: NodeID } | null | undefined,
- ) {
+ function setPluginHighlightTarget(data: HighlightElementPayload) {
if (!data) return pluginTarget(null)
// highlight component
if ('nodeId' in data) {
const { rootId, nodeId } = data
const component = findComponent(rootId, nodeId)
- if (!component) return console.warn('No component found', nodeId)
+ if (!component) return warn('No component found', nodeId)
pluginTarget({ ...component, rootId })
}
// highlight element
else {
const element = getElementById(data.elementId)
- if (!element) return console.warn('No element found', data)
+ if (!element) return warn('No element found', data)
pluginTarget(element)
}
}
@@ -185,7 +189,7 @@ export function createLocator({
function useLocator(options: LocatorOptions): void {
runWithOwner(owner, () => {
enableRootsAutoattach()
- if (locatorUsed) return console.warn('useLocator can be used called once.')
+ if (locatorUsed) return warn('useLocator can be used called once.')
locatorUsed = true
if (options.targetIDE) targetIDE = options.targetIDE
const isHoldingKey = createKeyHold(options.key ?? 'Alt', { preventDefault: true })
diff --git a/packages/debugger/test/locator.test.ts b/packages/debugger/src/locator/test/index.test.ts
similarity index 89%
rename from packages/debugger/test/locator.test.ts
rename to packages/debugger/src/locator/test/index.test.ts
index 4ebb9238..0bbf9e85 100644
--- a/packages/debugger/test/locator.test.ts
+++ b/packages/debugger/src/locator/test/index.test.ts
@@ -8,8 +8,7 @@ vi.mock('@solid-primitives/platform', () => ({
},
}))
-const fetchFunction = async () =>
- (await import('../src/locator/findComponent')).getLocationFromAttribute
+const fetchFunction = async () => (await import('../findComponent')).getLocationFromAttribute
describe('locator attribute pasting', () => {
beforeEach(() => {
diff --git a/packages/debugger/src/main/constants.ts b/packages/debugger/src/main/constants.ts
new file mode 100644
index 00000000..27424003
--- /dev/null
+++ b/packages/debugger/src/main/constants.ts
@@ -0,0 +1,32 @@
+export const INFINITY = '__$sdt-Infinity__'
+export const NEGATIVE_INFINITY = '__$sdt-NegativeInfinity__'
+export const NAN = '__$sdt-NaN__'
+
+export enum NodeType {
+ Root,
+ Component,
+ Effect,
+ Render,
+ Memo,
+ Computation,
+ Refresh,
+ Context,
+ Signal,
+ Store,
+}
+
+export enum ValueType {
+ Number,
+ Boolean,
+ String,
+ Null,
+ Undefined,
+ Symbol,
+ Array,
+ Object,
+ Function,
+ Getter,
+ Element,
+ Instance,
+ Store,
+}
diff --git a/packages/debugger/src/main/plugin.ts b/packages/debugger/src/main/plugin.ts
new file mode 100644
index 00000000..568f6f1b
--- /dev/null
+++ b/packages/debugger/src/main/plugin.ts
@@ -0,0 +1,178 @@
+import { Accessor, batch, createMemo, createSignal } from 'solid-js'
+import {
+ createEventHub,
+ createSimpleEmitter,
+ EventBus,
+ EventHub,
+} from '@solid-primitives/event-bus'
+import { throttle } from '@solid-primitives/scheduled'
+import { atom } from '@solid-devtools/shared/primitives'
+import { createBatchedUpdateEmitter, createInternalRoot } from './utils'
+import { ComputationUpdateHandler } from './walker'
+import { createLocator } from '../locator'
+import { createInspector, InspectorUpdate } from '../inspector'
+import { ComputationUpdate, Mapped, NodeID, RootsUpdates } from './types'
+
+export type BatchComputationUpdatesHandler = (payload: ComputationUpdate[]) => void
+
+type DebuggerEventHubMessages = {
+ ComputationUpdates: ComputationUpdate[]
+ StructureUpdates: RootsUpdates
+ InspectorUpdate: InspectorUpdate[]
+ InspectedNodeDetails: Mapped.OwnerDetails
+}
+export type DebuggerEventHub = EventHub<{
+ [K in keyof DebuggerEventHubMessages]: EventBus
+}>
+
+export default createInternalRoot(() => {
+ /** throttled global update */
+ const [onUpdate, triggerUpdate] = createSimpleEmitter()
+ /** forced — immediate global update */
+ const [onForceUpdate, forceTriggerUpdate] = createSimpleEmitter()
+
+ const eventHub: DebuggerEventHub = createEventHub(bus => ({
+ ComputationUpdates: bus(),
+ StructureUpdates: bus(),
+ InspectorUpdate: bus(),
+ InspectedNodeDetails: bus(),
+ }))
+
+ //
+ // Debugger Enabled
+ //
+ const [debuggerEnabled, toggleDebugger, addLocatorModeEnabledSignal] = (() => {
+ const locatorModeEnabledSignal = atom>()
+ const debuggerEnabled = atom(false)
+ const combinedEnabled = createMemo(() => debuggerEnabled() || !!locatorModeEnabledSignal()?.())
+
+ function toggleDebugger(state?: boolean) {
+ batch(() => {
+ const newState = debuggerEnabled(p => state ?? !p)
+ if (!newState) {
+ setComponents({})
+ locator.togglePluginLocatorMode(false)
+ }
+ })
+ }
+
+ function addLocatorModeEnabledSignal(signal: Accessor) {
+ locatorModeEnabledSignal(() => signal)
+ }
+
+ return [combinedEnabled, toggleDebugger, addLocatorModeEnabledSignal]
+ })()
+
+ //
+ // Components:
+ //
+ const [components, setComponents] = createSignal>({})
+
+ function findComponent(rootId: NodeID, nodeId: NodeID) {
+ const componentsList = components()[rootId] as Mapped.ResolvedComponent[] | undefined
+ if (!componentsList) return
+ for (const c of componentsList) {
+ if (c.id === nodeId) return c
+ }
+ }
+
+ function removeRoot(rootId: NodeID) {
+ setComponents(prev => {
+ const copy = Object.assign({}, prev)
+ delete copy[rootId]
+ return copy
+ })
+ pushStructureUpdate({ removed: rootId })
+ }
+ function updateRoot(newRoot: Mapped.Root, newComponents: Mapped.ResolvedComponent[]): void {
+ setComponents(prev => Object.assign(prev, { [newRoot.id]: newComponents }))
+ pushStructureUpdate({ updated: newRoot })
+ }
+
+ //
+ // Structure updates:
+ //
+ const pushStructureUpdate = (() => {
+ const updates: Mapped.Root[] = []
+ const removedIds = new Set()
+ const trigger = throttle(() => {
+ const updated: Record = {}
+ for (let i = updates.length - 1; i >= 0; i--) {
+ const update = updates[i]
+ const { id } = update
+ if (!removedIds.has(id) && !updated[id]) updated[id] = update
+ }
+ eventHub.emit('StructureUpdates', { updated, removed: [...removedIds] })
+ updates.length = 0
+ removedIds.clear()
+ }, 50)
+ const pushStructureUpdate = (update: { removed: NodeID } | { updated: Mapped.Root }) => {
+ if ('removed' in update) removedIds.add(update.removed)
+ else if (removedIds.has(update.updated.id)) return
+ else updates.push(update.updated)
+ trigger()
+ }
+ return pushStructureUpdate
+ })()
+
+ //
+ // Computation updates:
+ //
+ const _pushComputationUpdate = createBatchedUpdateEmitter(updates => {
+ eventHub.emit('ComputationUpdates', updates)
+ })
+ const pushComputationUpdate: ComputationUpdateHandler = (rootId, id) => {
+ _pushComputationUpdate({ rootId, id })
+ }
+
+ //
+ // Inspected Owner details:
+ //
+ const inspector = createInspector(debuggerEnabled, { eventHub })
+
+ //
+ // Locator
+ //
+ const locator = createLocator({
+ components,
+ debuggerEnabled,
+ findComponent,
+ getElementById: inspector.getElementById,
+ addLocatorModeEnabledSignal,
+ })
+
+ /**
+ * Used for connecting debugger to devtools
+ */
+ function useDebugger() {
+ return {
+ listenTo: eventHub.on,
+ enabled: debuggerEnabled,
+ toggleEnabled: toggleDebugger,
+ triggerUpdate,
+ forceTriggerUpdate,
+ inspector: {
+ setInspectedNode: inspector.setInspectedNode,
+ toggleValueNode: inspector.toggleValueNode,
+ },
+ locator: {
+ toggleEnabled: locator.togglePluginLocatorMode,
+ enabledByDebugger: locator.enabledByDebugger,
+ addClickInterceptor: locator.addClickInterceptor,
+ setHighlightTarget: locator.setPluginHighlightTarget,
+ onHoveredComponent: locator.onDebuggerHoveredComponentChange,
+ },
+ }
+ }
+
+ return {
+ onUpdate,
+ onForceUpdate,
+ enabled: debuggerEnabled,
+ useDebugger,
+ updateRoot,
+ removeRoot,
+ pushComputationUpdate,
+ useLocator: locator.useLocator,
+ }
+})
diff --git a/packages/debugger/src/roots.ts b/packages/debugger/src/main/roots.ts
similarity index 97%
rename from packages/debugger/src/roots.ts
rename to packages/debugger/src/main/roots.ts
index f4ad326f..824285d7 100644
--- a/packages/debugger/src/roots.ts
+++ b/packages/debugger/src/main/roots.ts
@@ -1,14 +1,5 @@
import { createEffect, onCleanup, untrack } from 'solid-js'
import { throttle } from '@solid-primitives/scheduled'
-import {
- DebuggerContext,
- getOwner,
- NodeID,
- NodeType,
- Solid,
- Core,
-} from '@solid-devtools/shared/graph'
-import { INTERNAL } from '@solid-devtools/shared/variables'
import { untrackedCallback } from '@solid-devtools/shared/primitives'
import { warn } from '@solid-devtools/shared/utils'
import { ComputationUpdateHandler, WalkerResult, walkSolidTree } from './walker'
@@ -22,8 +13,12 @@ import {
onOwnerCleanup,
removeDebuggerContext,
setDebuggerContext,
+ getOwner,
+ INTERNAL,
} from './utils'
import { makeCreateRootListener } from './update'
+import { Core, DebuggerContext, NodeID, Solid } from './types'
+import { NodeType } from './constants'
const RootMap: Record WalkerResult | null> = {}
export const walkSolidRoot = (rootId: NodeID, inspectedId?: NodeID): WalkerResult | null => {
diff --git a/packages/debugger/test/update.test.ts b/packages/debugger/src/main/test/update.test.ts
similarity index 95%
rename from packages/debugger/test/update.test.ts
rename to packages/debugger/src/main/test/update.test.ts
index eae3da8c..78a2ea6f 100644
--- a/packages/debugger/test/update.test.ts
+++ b/packages/debugger/src/main/test/update.test.ts
@@ -1,5 +1,4 @@
import { describe, it, expect } from 'vitest'
-import { getOwner, Solid } from '@solid-devtools/shared/graph'
import { createComputed, createRoot, createSignal } from 'solid-js'
import {
interceptComputationRerun,
@@ -7,8 +6,9 @@ import {
makeCreateRootListener,
observeValueUpdate,
removeValueUpdateObserver,
-} from '../src/update'
-import { createInternalRoot } from '../src/utils'
+} from '../update'
+import { createInternalRoot, getOwner } from '../utils'
+import { Solid } from '../../types'
describe('makeSolidUpdateListener', () => {
it('listens to solid updates', () =>
diff --git a/packages/debugger/test/utils.test.ts b/packages/debugger/src/main/test/utils.test.ts
similarity index 93%
rename from packages/debugger/test/utils.test.ts
rename to packages/debugger/src/main/test/utils.test.ts
index e300da9e..f65d9fa7 100644
--- a/packages/debugger/test/utils.test.ts
+++ b/packages/debugger/src/main/test/utils.test.ts
@@ -1,5 +1,4 @@
import { describe, it, expect } from 'vitest'
-import { Solid, getOwner, NodeType } from '@solid-devtools/shared/graph'
import {
createComponent,
createComputed,
@@ -9,7 +8,9 @@ import {
createRoot,
createSignal,
} from 'solid-js'
-import { getFunctionSources, getOwnerType, onDispose } from '../src/utils'
+import { getFunctionSources, getOwner, getOwnerType, onDispose } from '../utils'
+import { Solid } from '../types'
+import { NodeType } from '../constants'
describe('getOwnerType', () => {
it('identifies Component', () => {
diff --git a/packages/debugger/test/walker.test.tsx b/packages/debugger/src/main/test/walker.test.tsx
similarity index 95%
rename from packages/debugger/test/walker.test.tsx
rename to packages/debugger/src/main/test/walker.test.tsx
index 847402c8..48babdb6 100644
--- a/packages/debugger/test/walker.test.tsx
+++ b/packages/debugger/src/main/test/walker.test.tsx
@@ -1,5 +1,4 @@
import { describe, beforeEach, vi, it, expect } from 'vitest'
-import { getOwner, Mapped, NodeType, Solid } from '@solid-devtools/shared/graph'
import {
createComputed,
createEffect,
@@ -8,10 +7,11 @@ import {
createRoot,
createSignal,
} from 'solid-js'
-import type * as API from '../src/walker'
+import { getOwner } from '../utils'
+import { Solid, Mapped } from '../types'
+import { NodeType } from '../constants'
-const getModule = async (): Promise =>
- (await import('../src/walker')).walkSolidTree
+const getModule = async () => (await import('../walker')).walkSolidTree
const mockTree = () => {
const [s] = createSignal('foo', { name: 's0' })
diff --git a/packages/shared/src/graph.ts b/packages/debugger/src/main/types.ts
similarity index 58%
rename from packages/shared/src/graph.ts
rename to packages/debugger/src/main/types.ts
index de22ace6..4bd70686 100644
--- a/packages/shared/src/graph.ts
+++ b/packages/debugger/src/main/types.ts
@@ -1,31 +1,79 @@
-import { getOwner as _getOwner } from 'solid-js'
-import { Many } from '@solid-primitives/utils'
-import { INTERNAL } from './variables'
-import { EncodedValue } from './serialize'
-
-export enum NodeType {
- Root,
- Component,
- Effect,
- Render,
- Memo,
- Computation,
- Refresh,
- Context,
- Signal,
-}
+import type { Many } from '@solid-primitives/utils'
+import { INFINITY, NAN, NEGATIVE_INFINITY, NodeType, ValueType } from './constants'
+import type { INTERNAL } from './utils'
export type NodeID = string & {}
+export type ValueItemID = `signal:${NodeID}` | `prop:${string}` | `value`
+export type ValueItemType = 'signal' | 'prop' | 'value'
+
+export const getValueItemId = (
+ type: T,
+ id: T extends 'value' ? undefined : NodeID | string,
+): ValueItemID => {
+ if (type === 'value') return 'value'
+ return `${type}:${id}` as ValueItemID
+}
+
+export type EncodedPreviewPayloadMap = {
+ [ValueType.Array]: number
+ [ValueType.Object]: number
+ [ValueType.Number]: number | typeof INFINITY | typeof NEGATIVE_INFINITY | typeof NAN
+ [ValueType.Boolean]: boolean
+ [ValueType.String]: string
+ [ValueType.Symbol]: string
+ [ValueType.Function]: string
+ [ValueType.Getter]: string
+ [ValueType.Element]: { name: string; id: NodeID }
+ [ValueType.Instance]: string
+ [ValueType.Store]: { value: EncodedValue; id: NodeID }
+}
+
+export type EncodedPreviewChildrenMap = {
+ [ValueType.Array]: EncodedValue[]
+ [ValueType.Object]: Record>
+}
+
+export type EncodedValueOf = {
+ type: K
+} & (K extends keyof EncodedPreviewPayloadMap
+ ? { value: EncodedPreviewPayloadMap[K] }
+ : { value?: undefined }) &
+ (Deep extends true
+ ? K extends keyof EncodedPreviewChildrenMap
+ ? { children: EncodedPreviewChildrenMap[K] }
+ : { children?: undefined }
+ : { children?: undefined })
+
+export type EncodedValue = {
+ [K in ValueType]: EncodedValueOf
+}[ValueType]
+
+export type ValueUpdateListener = (newValue: unknown, oldValue: unknown) => void
+
+export type DebuggerContext =
+ | {
+ rootId: NodeID
+ triggerRootUpdate: VoidFunction
+ forceRootUpdate: VoidFunction
+ }
+ | typeof INTERNAL
+
export namespace Core {
export type Owner = import('solid-js/types/reactive/signal').Owner
export type SignalState = import('solid-js/types/reactive/signal').SignalState
export type Computation = import('solid-js/types/reactive/signal').Computation
export type RootFunction = import('solid-js/types/reactive/signal').RootFunction
export type EffectFunction = import('solid-js/types/reactive/signal').EffectFunction
+ export namespace Store {
+ export type StoreNode = import('solid-js/store').StoreNode
+ export type NotWrappable = import('solid-js/store/types/store').NotWrappable
+ export type OnStoreNodeUpdate = import('solid-js/store/types/store').OnStoreNodeUpdate
+ }
}
declare module 'solid-js/types/reactive/signal' {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
interface SignalState {
sdtId?: NodeID
sdtName?: string
@@ -35,6 +83,7 @@ declare module 'solid-js/types/reactive/signal' {
sdtName?: string
sdtType?: NodeType
}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Computation {
sdtId?: NodeID
sdtType?: NodeType
@@ -59,10 +108,20 @@ export namespace Solid {
observers: Computation[] | null
}
+ export type OnStoreNodeUpdate = Core.Store.OnStoreNodeUpdate & {
+ storePath: readonly (string | number)[]
+ storeSymbol: symbol
+ }
+
+ export interface Store {
+ value: Core.Store.StoreNode
+ sdtId?: NodeID
+ }
+
export interface Root extends Core.Owner {
owned: Computation[] | null
owner: Owner | null
- sourceMap?: Record
+ sourceMap?: Record
// Used by the debugger
isDisposed?: boolean
sdtAttached?: Owner | null
@@ -97,33 +156,14 @@ export namespace Solid {
export interface Component extends Memo {
props: Record
+ componentName: string
}
export type Owner = Computation | Root
}
-export const getOwner = _getOwner as () => Solid.Owner | null
-
-export type DebuggerContext =
- | {
- rootId: NodeID
- triggerRootUpdate: VoidFunction
- forceRootUpdate: VoidFunction
- }
- | typeof INTERNAL
-
-export type ComputationUpdate = { rootId: NodeID; id: NodeID }
-
-export type RootsUpdates = {
- removed: NodeID[]
- updated: Record
-}
-
-export type ValueUpdateListener = (newValue: unknown, oldValue: unknown) => void
-
//
-// "Mapped___" — owner/signal/etc. objects created by the solid-devtools-debugger runtime library
-// They should be JSON serialisable — to be able to send them with chrome.runtime.sendMessage
+// "Mapped___" should be JSON serialisable — to be able to send them with chrome.runtime.sendMessage
//
export namespace Mapped {
@@ -146,10 +186,9 @@ export namespace Mapped {
}
export interface Signal {
- type: NodeType.Signal | NodeType.Memo
+ type: NodeType.Signal | NodeType.Memo | NodeType.Store
name: string
id: NodeID
- observers: NodeID[]
value: EncodedValue
}
@@ -177,7 +216,12 @@ export namespace Mapped {
value?: EncodedValue
/** for computations */
sources?: NodeID[]
- /** for memos */
- observers?: NodeID[]
}
}
+
+export type ComputationUpdate = { rootId: NodeID; id: NodeID }
+
+export type RootsUpdates = {
+ removed: NodeID[]
+ updated: Record
+}
diff --git a/packages/debugger/src/update.ts b/packages/debugger/src/main/update.ts
similarity index 82%
rename from packages/debugger/src/update.ts
rename to packages/debugger/src/main/update.ts
index bae7ba03..f7e1c086 100644
--- a/packages/debugger/src/update.ts
+++ b/packages/debugger/src/main/update.ts
@@ -1,8 +1,6 @@
import { untrack } from 'solid-js'
-import { Observable, Observer as ObjectObserver } from 'object-observer'
-import { Solid, Core, ValueUpdateListener } from '@solid-devtools/shared/graph'
-import { WINDOW_WRAP_STORE_PROPERTY } from '@solid-devtools/shared/variables'
import { skipInternalRoot, tryOnCleanup } from './utils'
+import { Core, Solid, ValueUpdateListener } from './types'
//
// AFTER UPDATE
@@ -61,34 +59,6 @@ export function makeCreateRootListener(onUpdate: AfterCrateRoot): VoidFunction {
return tryOnCleanup(() => CreateRootListeners.delete(onUpdate))
}
-//
-// WRAP STORES
-//
-
-export type { ObjectObserver }
-
-declare global {
- interface Window {
- [WINDOW_WRAP_STORE_PROPERTY]?: (init: T) => T
- }
-}
-
-// window[WINDOW_WRAP_STORE_PROPERTY] is internal — no need to worry about other users
-window[WINDOW_WRAP_STORE_PROPERTY] = init => {
- return Observable.from(init)
-}
-
-export function makeStoreObserver(state: object, onUpdate: ObjectObserver): VoidFunction {
- if (!Observable.isObservable(state)) {
- console.warn(`Object ${state} is not wrapped`)
- return () => {
- /* noop */
- }
- }
- Observable.observe(state, onUpdate)
- return tryOnCleanup(() => Observable.unobserve(state, onUpdate))
-}
-
//
// OBSERVE NODES
//
diff --git a/packages/debugger/src/utils.ts b/packages/debugger/src/main/utils.ts
similarity index 82%
rename from packages/debugger/src/utils.ts
rename to packages/debugger/src/main/utils.ts
index f96b9e08..f73e549c 100644
--- a/packages/debugger/src/utils.ts
+++ b/packages/debugger/src/main/utils.ts
@@ -1,30 +1,41 @@
-import { createComputed, createRoot, onCleanup, runWithOwner } from 'solid-js'
+import {
+ createComputed,
+ createRoot,
+ onCleanup,
+ runWithOwner,
+ getOwner as _getOwner,
+} from 'solid-js'
import { Emit } from '@solid-primitives/event-bus'
import { throttle } from '@solid-primitives/scheduled'
-import {
- DebuggerContext,
- NodeType,
- Solid,
- Core,
- getOwner,
- NodeID,
-} from '@solid-devtools/shared/graph'
-import { INTERNAL, UNNAMED } from '@solid-devtools/shared/variables'
import { trimString } from '@solid-devtools/shared/utils'
+import { DEV as _STORE_DEV } from 'solid-js/store'
+import { Core, DebuggerContext, NodeID, Solid } from './types'
+import { NodeType } from './constants'
+
+const STORE_DEV = _STORE_DEV!
+
+export const getOwner = _getOwner as () => Solid.Owner | null
export const isSolidComputation = (o: Readonly): o is Solid.Computation => 'fn' in o
export const isSolidMemo = (o: Readonly): o is Solid.Memo =>
'sdtType' in o ? o.sdtType === NodeType.Memo : isSolidComputation(o) && _isMemo(o)
-export const isSolidOwner = (o: Readonly | Solid.Signal): o is Solid.Owner =>
- 'owned' in o
+export const isSolidOwner = (
+ o: Readonly,
+): o is Solid.Owner => 'owned' in o
export const isSolidRoot = (o: Readonly): o is Solid.Root =>
o.sdtType === NodeType.Root || !isSolidComputation(o)
export const isSolidComponent = (o: Readonly): o is Solid.Component => 'props' in o
+export const isStoreNode = (o: object): o is Core.Store.StoreNode => STORE_DEV.$NAME in o
+
+export const isSolidStore = (o: Readonly): o is Solid.Store => {
+ return !('observers' in o) && STORE_DEV.$NAME in o.value
+}
+
const _isMemo = (o: Readonly): boolean =>
'value' in o && 'comparator' in o && o.pure === true
@@ -32,20 +43,31 @@ export function getOwnerName(owner: Readonly): string {
const { name, componentName: component } = owner
if (component && typeof component === 'string')
return component.startsWith('_Hot$$') ? component.slice(6) : component
- return name || UNNAMED
+ return name || '(unnamed)'
}
export function getSignalName(signal: Readonly): string {
- return signal.name || UNNAMED
+ return signal.name || '(unnamed)'
}
-export function getNodeName(o: Readonly): string {
- const name = isSolidOwner(o) ? getOwnerName(o) : getSignalName(o)
+export const getStoreNodeName = (node: Core.Store.StoreNode): string =>
+ node[STORE_DEV.$NAME] || '(unnamed)'
+
+export function getNodeName(o: Readonly): string {
+ const name = isSolidOwner(o)
+ ? getOwnerName(o)
+ : isSolidStore(o)
+ ? getStoreNodeName(o)
+ : getSignalName(o)
+ return getDisplayName(name)
+}
+
+export function getDisplayName(name: string): string {
return trimString(name, 16)
}
-export function getNodeType(o: Readonly): NodeType {
+export function getNodeType(o: Readonly): NodeType {
if (isSolidOwner(o)) return getOwnerType(o)
- return NodeType.Signal
+ return isSolidStore(o) ? NodeType.Store : NodeType.Signal
}
export const getOwnerType = (o: Readonly): NodeType => {
@@ -72,6 +94,26 @@ export const getOwnerType = (o: Readonly): NodeType => {
return NodeType.Computation
}
+let LAST_ID = 0
+export const getNewSdtId = (): NodeID => (LAST_ID++).toString(36)
+
+export function markOwnerName(o: Solid.Owner): string {
+ if (o.sdtName !== undefined) return o.sdtName
+ return (o.sdtName = getNodeName(o))
+}
+export function markOwnerType(o: Solid.Owner, type?: NodeType): NodeType {
+ if (o.sdtType !== undefined) return o.sdtType
+ return (o.sdtType = type ?? getOwnerType(o))
+}
+export function markNodeID(o: { sdtId?: NodeID }): NodeID {
+ if (o.sdtId !== undefined) return o.sdtId
+ return (o.sdtId = getNewSdtId())
+}
+export function markNodesID(nodes?: { sdtId?: NodeID }[] | null): NodeID[] {
+ if (!nodes || !nodes.length) return []
+ return nodes.map(markNodeID)
+}
+
export function getComponentRefreshNode(owner: Readonly): Solid.Memo | null {
const { owned } = owner
let refresh: Solid.Owner
@@ -175,6 +217,8 @@ export function onParentCleanup(
}
}
+// TODO: move onDispose to solid-primitives
+
const DISPOSE_ID = Symbol('Dispose ID')
export function onDispose(
fn: () => T,
@@ -182,6 +226,7 @@ export function onDispose(
): () => T {
const owner = getOwner()
if (!owner) {
+ // eslint-disable-next-line no-console
console.warn('onDispose called outside of a reactive owner')
return fn
}
@@ -219,28 +264,10 @@ export function getFunctionSources(fn: () => unknown): Solid.Signal[] {
return nodes ?? []
}
-let LAST_ID = 0
-export const getNewSdtId = (): NodeID => (LAST_ID++).toString(36)
-
-export function markOwnerName(o: Solid.Owner): string {
- if (o.sdtName !== undefined) return o.sdtName
- return (o.sdtName = getNodeName(o))
-}
-export function markOwnerType(o: Solid.Owner, type?: NodeType): NodeType {
- if (o.sdtType !== undefined) return o.sdtType
- return (o.sdtType = type ?? getOwnerType(o))
-}
-export function markNodeID(o: { sdtId?: NodeID }): NodeID {
- if (o.sdtId !== undefined) return o.sdtId
- return (o.sdtId = getNewSdtId())
-}
-export function markNodesID(nodes?: { sdtId?: NodeID }[] | null): NodeID[] {
- if (!nodes || !nodes.length) return []
- return nodes.map(markNodeID)
-}
-
let SkipInternalRoot: Core.RootFunction | null = null
+export const INTERNAL = Symbol('internal')
+
/**
* Sold's `createRoot` primitive that won't be tracked by the debugger.
*/
@@ -260,6 +287,18 @@ export const skipInternalRoot = () => {
return skip
}
+export function dedupeArrayById(input: T[]): T[] {
+ const ids = new Set()
+ const deduped: T[] = []
+ for (let i = input.length - 1; i >= 0; i--) {
+ const update = input[i]
+ if (ids.has(update.id)) continue
+ ids.add(update.id)
+ deduped.push(update)
+ }
+ return deduped
+}
+
/**
* Batches series of updates to a single array of updates.
*
@@ -271,15 +310,7 @@ export function createBatchedUpdateEmitter(
const updates: T[] = []
const triggerUpdateEmit = throttle(() => {
- // dedupe updates
- const ids = new Set()
- const deduped: T[] = []
- for (let i = updates.length - 1; i >= 0; i--) {
- const update = updates[i]
- if (ids.has(update.id)) continue
- ids.add(update.id)
- deduped.push(update)
- }
+ const deduped = dedupeArrayById(updates)
updates.length = 0
emit(deduped)
})
diff --git a/packages/debugger/src/walker.ts b/packages/debugger/src/main/walker.ts
similarity index 97%
rename from packages/debugger/src/walker.ts
rename to packages/debugger/src/main/walker.ts
index ab554437..05945341 100644
--- a/packages/debugger/src/walker.ts
+++ b/packages/debugger/src/main/walker.ts
@@ -1,4 +1,3 @@
-import { NodeType, NodeID, Solid, Mapped } from '@solid-devtools/shared/graph'
import {
getComponentRefreshNode,
markNodeID,
@@ -7,6 +6,8 @@ import {
resolveElements,
} from './utils'
import { observeComputationUpdate } from './update'
+import { Mapped, NodeID, Solid } from './types'
+import { NodeType } from './constants'
export type ComputationUpdateHandler = (rootId: NodeID, nodeId: NodeID) => void
diff --git a/packages/debugger/src/plugin.ts b/packages/debugger/src/plugin.ts
deleted file mode 100644
index b9f347e3..00000000
--- a/packages/debugger/src/plugin.ts
+++ /dev/null
@@ -1,308 +0,0 @@
-import { Accessor, batch, createEffect, createMemo, createSignal, untrack } from 'solid-js'
-import { createEventHub, createSimpleEmitter } from '@solid-primitives/event-bus'
-import { throttle } from '@solid-primitives/scheduled'
-import {
- Mapped,
- Solid,
- RootsUpdates,
- NodeID,
- ComputationUpdate,
-} from '@solid-devtools/shared/graph'
-import { EncodedValue, encodeValue, ElementMap } from '@solid-devtools/shared/serialize'
-import { atom, untrackedCallback } from '@solid-devtools/shared/primitives'
-import { createBatchedUpdateEmitter, createInternalRoot } from './utils'
-import { ComputationUpdateHandler } from './walker'
-import { walkSolidRoot } from './roots'
-import {
- clearOwnerObservers,
- collectOwnerDetails,
- encodeComponentProps,
- SignalUpdateHandler,
-} from './inspect'
-import { makeSolidUpdateListener } from './update'
-import { createLocator } from './locator'
-
-/*
-DETAILS:
-
-- type of the node
-- path
-- signals declared in it (memos too)
- - their observers and sources
-- stores
-- their observers and sources as well (this may be too complicated to do for now)
-- current and previous value (only if the node is a computation)
-- sources (only if the node is a computation)
-- observers (only if the node is a memo)
-- rendered HTML element if node is a component
-- component props
-*/
-
-export type BatchComputationUpdatesHandler = (payload: ComputationUpdate[]) => void
-
-type RootUpdate = { removed: NodeID } | { updated: Mapped.Root }
-
-export default createInternalRoot(() => {
- /** throttled global update */
- const [onUpdate, triggerUpdate] = createSimpleEmitter()
- /** forced — immediate global update */
- const [onForceUpdate, forceTriggerUpdate] = createSimpleEmitter()
-
- const eventHub = createEventHub(bus => ({
- ComputationUpdates: bus(),
- SignalUpdates: bus<{ id: NodeID; value: EncodedValue }[]>(),
- PropsUpdate: bus(),
- ValueUpdate: bus<{ value: EncodedValue; update: boolean }>(),
- StructureUpdates: bus(),
- InspectedNodeDetails: bus(),
- }))
-
- //
- // Debugger Enabled
- //
- const [debuggerEnabled, toggleDebugger, addLocatorModeEnabledSignal] = (() => {
- const locatorModeEnabledSignal = atom>()
- const debuggerEnabled = atom(false)
- const combinedEnabled = createMemo(() => debuggerEnabled() || !!locatorModeEnabledSignal()?.())
-
- function toggleDebugger(state?: boolean) {
- batch(() => {
- const newState = debuggerEnabled(p => state ?? !p)
- if (!newState) {
- setComponents({})
- locator.togglePluginLocatorMode(false)
- }
- })
- }
-
- function addLocatorModeEnabledSignal(signal: Accessor) {
- locatorModeEnabledSignal(() => signal)
- }
-
- return [combinedEnabled, toggleDebugger, addLocatorModeEnabledSignal]
- })()
-
- //
- // Components:
- //
- const [components, setComponents] = createSignal>({})
-
- function findComponent(rootId: NodeID, nodeId: NodeID) {
- const componentsList = components()[rootId] as Mapped.ResolvedComponent[] | undefined
- if (!componentsList) return
- for (const c of componentsList) {
- if (c.id === nodeId) return c
- }
- }
-
- function removeRoot(rootId: NodeID) {
- setComponents(prev => {
- const copy = Object.assign({}, prev)
- delete copy[rootId]
- return copy
- })
- pushStructureUpdate({ removed: rootId })
- }
- function updateRoot(newRoot: Mapped.Root, newComponents: Mapped.ResolvedComponent[]): void {
- setComponents(prev => Object.assign(prev, { [newRoot.id]: newComponents }))
- pushStructureUpdate({ updated: newRoot })
- }
-
- //
- // Structure updates:
- //
- const pushStructureUpdate = (() => {
- const updates: Mapped.Root[] = []
- const removedIds = new Set()
- const trigger = throttle(() => {
- const updated: Record = {}
- for (let i = updates.length - 1; i >= 0; i--) {
- const update = updates[i]
- const { id } = update
- if (!removedIds.has(id) && !updated[id]) updated[id] = update
- }
- eventHub.emit('StructureUpdates', { updated, removed: [...removedIds] })
- updates.length = 0
- removedIds.clear()
- }, 50)
- const pushStructureUpdate = (update: RootUpdate) => {
- if ('removed' in update) removedIds.add(update.removed)
- else if (removedIds.has(update.updated.id)) return
- else updates.push(update.updated)
- trigger()
- }
- return pushStructureUpdate
- })()
-
- //
- // Inspected Owner details:
- //
- const inspected = {
- elementMap: new ElementMap(),
- signalMap: {} as Record,
- owner: null as Solid.Owner | null,
- signals: new Set(),
- props: new Set(),
- value: false,
- getValue: () => null as unknown,
- }
-
- const getElementById = (id: NodeID): HTMLElement | undefined => inspected.elementMap.get(id)
-
- const pushSignalUpdate = createBatchedUpdateEmitter<{
- id: NodeID
- value: EncodedValue
- }>(updates => eventHub.emit('SignalUpdates', updates))
- const onSignalUpdate: SignalUpdateHandler = untrackedCallback((id, value) => {
- if (!debuggerEnabled() || !inspected.owner) return
- const isSelected = inspected.signals.has(id)
- pushSignalUpdate({ id, value: encodeValue(value, isSelected, inspected.elementMap) })
- })
-
- const triggerValueUpdate = (() => {
- let updateNext = false
- const forceValueUpdate = () => {
- if (!debuggerEnabled() || !inspected.owner) return (updateNext = false)
- eventHub.emit('ValueUpdate', {
- value: encodeValue(inspected.getValue(), inspected.value, inspected.elementMap),
- update: updateNext,
- })
- updateNext = false
- }
- const triggerValueUpdate = throttle(forceValueUpdate)
- function handleValueUpdate(update: boolean, force = false) {
- if (update) updateNext = true
- if (force) forceValueUpdate()
- else triggerValueUpdate()
- }
- return handleValueUpdate
- })()
-
- const setInspectedDetails = untrackedCallback((owner: Solid.Owner) => {
- inspected.owner && clearOwnerObservers(inspected.owner)
- inspected.props.clear()
- inspected.signals.clear()
- inspected.owner = owner
- inspected.value = false
- const result = collectOwnerDetails(owner, {
- onSignalUpdate,
- onValueUpdate: () => triggerValueUpdate(true),
- })
- eventHub.emit('InspectedNodeDetails', result.details)
- inspected.signalMap = result.signalMap
- inspected.elementMap = result.elementMap
- inspected.getValue = result.getOwnerValue
- })
- const clearInspectedDetails = () => {
- inspected.owner && clearOwnerObservers(inspected.owner)
- inspected.owner = null
- inspected.signals.clear()
- inspected.props.clear()
- inspected.value = false
- }
-
- function updateInspectedProps() {
- if (!inspected.owner) return
- const props = encodeComponentProps(inspected.owner, {
- inspectedProps: inspected.props,
- elementMap: inspected.elementMap,
- })
- props && eventHub.emit('PropsUpdate', props)
- }
-
- createEffect(() => {
- // make sure we clear the owner observers when the plugin is disabled
- if (!debuggerEnabled()) inspected.owner && clearOwnerObservers(inspected.owner)
- // re-observe the owner when the plugin is enabled
- else inspected.owner && setInspectedDetails(inspected.owner)
-
- // update the owner details whenever there is a change in solid's internals
- makeSolidUpdateListener(
- throttle(() => {
- updateInspectedProps()
- triggerValueUpdate(false)
- }, 150),
- )
- })
-
- function setInspectedNode(data: { rootId: NodeID; nodeId: NodeID } | null) {
- if (!data) return clearInspectedDetails()
- const { rootId, nodeId } = data
-
- const walkResult = walkSolidRoot(rootId, nodeId)
- if (!walkResult || !walkResult.inspectedOwner) return clearInspectedDetails()
-
- setInspectedDetails(walkResult.inspectedOwner)
- }
-
- function setInspectedSignal(id: NodeID, selected: boolean): EncodedValue | null {
- const signal = inspected.signalMap[id] as Solid.Signal | undefined
- if (!signal) return null
- if (selected) inspected.signals.add(id)
- else inspected.signals.delete(id)
- return untrack(() => encodeValue(signal.value, selected, inspected.elementMap))
- }
- function setInspectedProp(key: NodeID, selected: boolean) {
- if (selected) inspected.props.add(key)
- else inspected.props.delete(key)
- updateInspectedProps()
- }
- function setInspectedValue(selected: boolean) {
- if (!inspected.owner) return null
- inspected.value = selected
- triggerValueUpdate(false, true)
- }
-
- //
- // Computation updates:
- //
- const _pushComputationUpdate = createBatchedUpdateEmitter(updates =>
- eventHub.emit('ComputationUpdates', updates),
- )
- const pushComputationUpdate: ComputationUpdateHandler = (rootId, id) => {
- _pushComputationUpdate({ rootId, id })
- }
-
- //
- // Locator
- //
- const locator = createLocator({
- components,
- debuggerEnabled,
- findComponent,
- getElementById,
- addLocatorModeEnabledSignal,
- })
-
- function useDebugger() {
- return {
- listenTo: eventHub.on,
- enabled: debuggerEnabled,
- toggleEnabled: toggleDebugger,
- triggerUpdate,
- forceTriggerUpdate,
- setInspectedNode,
- setInspectedSignal,
- setInspectedProp,
- setInspectedValue,
- locator: {
- toggleEnabled: locator.togglePluginLocatorMode,
- enabledByDebugger: locator.enabledByDebugger,
- addClickInterceptor: locator.addClickInterceptor,
- setHighlightTarget: locator.setPluginHighlightTarget,
- onHoveredComponent: locator.onDebuggerHoveredComponentChange,
- },
- }
- }
-
- return {
- onUpdate,
- onForceUpdate,
- enabled: debuggerEnabled,
- useDebugger,
- updateRoot,
- removeRoot,
- pushComputationUpdate,
- useLocator: locator.useLocator,
- }
-})
diff --git a/packages/debugger/src/server.ts b/packages/debugger/src/server.ts
index 4e0b442e..ea5ff4db 100644
--- a/packages/debugger/src/server.ts
+++ b/packages/debugger/src/server.ts
@@ -1,7 +1,9 @@
-import { NodeType, Solid } from '@solid-devtools/shared/graph'
-import { UNNAMED } from '@solid-devtools/shared/variables'
-import * as API from './index'
+import type * as API from './index'
import { createRoot } from 'solid-js'
+import type { Solid } from './main/types'
+import { NodeType } from './main/constants'
+
+export * from './types'
export { createUnownedRoot } from './index'
@@ -21,6 +23,10 @@ export const useDebugger: typeof API.useDebugger = () => ({
setInspectedValue: () => null,
enabled: () => false,
toggleEnabled: () => {},
+ inspector: {
+ setInspectedNode: () => {},
+ toggleValueNode: () => {},
+ },
locator: {
toggleEnabled: () => {},
addClickInterceptor: () => {},
@@ -36,7 +42,6 @@ export const useLocator: typeof API.useLocator = () => {}
// update
export const makeSolidUpdateListener: typeof API.makeSolidUpdateListener = () => () => {}
export const makeCreateRootListener: typeof API.makeCreateRootListener = () => () => {}
-export const makeStoreObserver: typeof API.makeStoreObserver = () => () => {}
export const observeComputationUpdate: typeof API.observeComputationUpdate = () => {}
export const interceptComputationRerun: typeof API.interceptComputationRerun = () => {}
export const observeValueUpdate: typeof API.observeValueUpdate = () => {}
@@ -44,14 +49,16 @@ export const makeValueUpdateListener: typeof API.makeValueUpdateListener = () =>
export const removeValueUpdateObserver: typeof API.removeValueUpdateObserver = () => {}
// utils
+export const getOwner: typeof API.getOwner = () => null
export const getOwnerType: typeof API.getOwnerType = () => NodeType.Computation
export const getNodeType: typeof API.getNodeType = () => NodeType.Computation
-export const getNodeName: typeof API.getNodeName = () => UNNAMED
+export const getNodeName: typeof API.getNodeName = () => '(unnamed)'
export const isSolidComputation: typeof API.isSolidComputation = (o): o is Solid.Computation =>
false
export const isSolidMemo: typeof API.isSolidMemo = (o): o is Solid.Memo => false
export const isSolidOwner: typeof API.isSolidOwner = (o): o is Solid.Owner => false
export const isSolidRoot: typeof API.isSolidRoot = (o): o is Solid.Root => false
+export const isSolidStore: typeof API.isSolidStore = (o): o is Solid.Store => false
export const onOwnerCleanup: typeof API.onOwnerCleanup = () => () => {}
export const onParentCleanup: typeof API.onParentCleanup = () => () => {}
export const getFunctionSources: typeof API.getFunctionSources = () => []
diff --git a/packages/debugger/src/types.ts b/packages/debugger/src/types.ts
new file mode 100644
index 00000000..75a908ae
--- /dev/null
+++ b/packages/debugger/src/types.ts
@@ -0,0 +1,11 @@
+export * from './main/types'
+export * from './main/constants'
+export type {
+ InspectorUpdate,
+ SetInspectedNodeData,
+ ToggleInspectedValueData,
+ ProxyPropsUpdate,
+ StoreNodeUpdate,
+ ValueNodeUpdate,
+ HighlightElementPayload,
+} from '.'
diff --git a/packages/debugger/tsup.config.ts b/packages/debugger/tsup.config.ts
index bfcbd275..b0a26e96 100644
--- a/packages/debugger/tsup.config.ts
+++ b/packages/debugger/tsup.config.ts
@@ -1,3 +1,8 @@
import defineConfig from '../../configs/tsup.config'
-export default defineConfig({ extension: 'ts', server: true, jsx: true })
+export default defineConfig({
+ extension: 'ts',
+ server: true,
+ jsx: true,
+ additionalEntries: ['types'],
+})
diff --git a/packages/ext-client/package.json b/packages/ext-client/package.json
index 63aabdc5..b6290a83 100644
--- a/packages/ext-client/package.json
+++ b/packages/ext-client/package.json
@@ -50,12 +50,20 @@
"import": "./dist/vite.js",
"require": "./dist/vite.cjs",
"types": "./dist/vite.d.ts"
+ },
+ "./bridge": {
+ "import": "./dist/bridge.js",
+ "require": "./dist/bridge.cjs",
+ "types": "./dist/bridge.d.ts"
}
},
"typesVersions": {
"*": {
"vite": [
"./dist/vite.d.ts"
+ ],
+ "bridge": [
+ "./dist/bridge.d.ts"
]
}
},
@@ -63,23 +71,17 @@
"dev": "tsup --watch",
"build": "tsup",
"test": "vitest",
- "typecheck": "tsc --noEmit"
- },
- "devDependencies": {
- "solid-js": "^1.6.0",
- "tsup": "^6.3.0",
- "typescript": "^4.8.4",
- "vitest": "^0.23.4"
+ "typecheck": "tsc --noEmit --paths null"
},
"dependencies": {
"@solid-devtools/debugger": "workspace:^0.13.1",
"@solid-devtools/shared": "workspace:^0.9.2",
"@solid-devtools/transform": "workspace:^0.7.5",
"@solid-primitives/utils": "^3.1.0",
- "type-fest": "^3.1.0"
+ "type-fest": "^3.2.0"
},
"peerDependencies": {
- "solid-js": "^1.5.5"
+ "solid-js": "^1.6.2"
},
"packageManager": "pnpm@7.13.0"
}
diff --git a/packages/shared/src/bridge.ts b/packages/ext-client/src/bridge.ts
similarity index 78%
rename from packages/shared/src/bridge.ts
rename to packages/ext-client/src/bridge.ts
index f36f4326..053de3e8 100644
--- a/packages/shared/src/bridge.ts
+++ b/packages/ext-client/src/bridge.ts
@@ -1,6 +1,14 @@
-import { ComputationUpdate, Mapped, NodeID, RootsUpdates } from './graph'
-import { EncodedValue } from './serialize'
-import { log } from './utils'
+import type {
+ InspectorUpdate,
+ SetInspectedNodeData,
+ ToggleInspectedValueData,
+ Mapped,
+ NodeID,
+ ComputationUpdate,
+ RootsUpdates,
+ HighlightElementPayload,
+} from '@solid-devtools/debugger/types'
+import { log } from '@solid-devtools/shared/utils'
export const LOG_MESSAGES = false
@@ -19,12 +27,8 @@ export interface Messages {
ResetPanel: {}
StructureUpdate: RootsUpdates
ComputationUpdates: ComputationUpdate[]
- /** client -> devtools: signal deep value */
- SignalUpdates: { signals: { id: NodeID; value: EncodedValue }[]; update: boolean }
- /** client -> devtools: encoded props object */
- PropsUpdate: Mapped.Props
- /** client -> devtools: inspected node value update */
- ValueUpdate: { value: EncodedValue; update: boolean }
+ /** client -> devtools: updates from the inspector */
+ InspectorUpdate: InspectorUpdate[]
/** devtools -> client: force the debugger to walk the whole tree and send it */
ForceUpdate: {}
/** client -> devtools: send component clicked with the locator to the extension */
@@ -32,12 +36,10 @@ export interface Messages {
/** client -> devtools: send updates to the owner details */
SetInspectedDetails: Mapped.OwnerDetails
/** devtools -> client: request for node/signal/prop details — subscribe or unsubscribe */
- ToggleInspected:
- | { type: 'node'; data: null | { rootId: NodeID; nodeId: NodeID } }
- | { type: 'signal' | 'prop'; data: { id: NodeID; selected: boolean } }
- | { type: 'value'; data: boolean }
+ ToggleInspectedValue: ToggleInspectedValueData
+ SetInspectedNode: SetInspectedNodeData
/** devtools -> client: user hovered over component/element signal in devtools panel */
- HighlightElement: { rootId: NodeID; nodeId: NodeID } | { elementId: string } | null
+ HighlightElement: HighlightElementPayload
/** client -> devtools: send hovered (by the locator) owner to the extension */
ClientHoveredComponent: { nodeId: NodeID; state: boolean }
/** devtools -> client: user is selecting component from the page */
diff --git a/packages/ext-client/src/client.ts b/packages/ext-client/src/client.ts
index 7ff5a9b4..3a58de5f 100644
--- a/packages/ext-client/src/client.ts
+++ b/packages/ext-client/src/client.ts
@@ -1,10 +1,6 @@
import { batch, createEffect, onCleanup } from 'solid-js'
import { createInternalRoot, useDebugger } from '@solid-devtools/debugger'
-import {
- onWindowMessage,
- postWindowMessage,
- startListeningWindowMessages,
-} from '@solid-devtools/shared/bridge'
+import { onWindowMessage, postWindowMessage, startListeningWindowMessages } from './bridge'
import { defer } from '@solid-devtools/shared/primitives'
startListeningWindowMessages()
@@ -26,7 +22,7 @@ createInternalRoot(() => {
onWindowMessage('PanelClosed', () => {
batch(() => {
debug.toggleEnabled(false)
- debug.setInspectedNode(null)
+ debug.inspector.setInspectedNode(null)
})
})
@@ -38,35 +34,16 @@ createInternalRoot(() => {
onCleanup(onWindowMessage('ForceUpdate', () => debug.forceTriggerUpdate()))
- onCleanup(
- onWindowMessage('ToggleInspected', payload => {
- if (payload.type === 'node') debug.setInspectedNode(payload.data)
- else if (payload.type === 'value') debug.setInspectedValue(payload.data)
- else if (payload.type === 'prop')
- debug.setInspectedProp(payload.data.id, payload.data.selected)
- else if (payload.type === 'signal') {
- const { id, selected } = payload.data
- const value = debug.setInspectedSignal(id, selected)
- if (value) postWindowMessage('SignalUpdates', { signals: [{ id, value }], update: false })
- }
- }),
- )
+ onCleanup(onWindowMessage('ToggleInspectedValue', debug.inspector.toggleValueNode))
+ onCleanup(onWindowMessage('SetInspectedNode', debug.inspector.setInspectedNode))
debug.listenTo('StructureUpdates', updates => postWindowMessage('StructureUpdate', updates))
- debug.listenTo('ComputationUpdates', updates =>
- postWindowMessage('ComputationUpdates', updates),
- )
-
- debug.listenTo('SignalUpdates', updates => {
- postWindowMessage('SignalUpdates', { signals: updates, update: true })
+ debug.listenTo('ComputationUpdates', updates => {
+ postWindowMessage('ComputationUpdates', updates)
})
- debug.listenTo('PropsUpdate', updates => postWindowMessage('PropsUpdate', updates))
-
- debug.listenTo('ValueUpdate', ({ value, update }) => {
- postWindowMessage('ValueUpdate', { value, update })
- })
+ debug.listenTo('InspectorUpdate', update => postWindowMessage('InspectorUpdate', update))
// send the focused owner details
debug.listenTo('InspectedNodeDetails', details => {
diff --git a/packages/ext-client/tsup.config.ts b/packages/ext-client/tsup.config.ts
index ba674f5c..7b63baa4 100644
--- a/packages/ext-client/tsup.config.ts
+++ b/packages/ext-client/tsup.config.ts
@@ -4,7 +4,7 @@ import { version } from './package.json'
export default defineConfig({
server: true,
- additionalEntries: ['vite'],
+ additionalEntries: ['vite', 'bridge'],
overwrite: config => {
config.env = {
...config.env,
diff --git a/packages/extension/background/background.ts b/packages/extension/background/background.ts
index c0aa97bb..51b63cd8 100644
--- a/packages/extension/background/background.ts
+++ b/packages/extension/background/background.ts
@@ -1,6 +1,5 @@
-import { createCallbackStack } from '@solid-primitives/utils'
-import { log } from '@solid-devtools/shared/utils'
-import { OnMessageFn, PostMessageFn } from '@solid-devtools/shared/bridge'
+import { createCallbackStack, log } from '@solid-devtools/shared/utils'
+import { OnMessageFn, PostMessageFn } from 'solid-devtools/bridge'
import {
createPortMessanger,
createRuntimeMessanger,
@@ -90,10 +89,8 @@ chrome.runtime.onConnect.addListener(port => {
onPortMessage('StructureUpdate', e => postRuntimeMessage('StructureUpdate', e))
onPortMessage('ComputationUpdates', e => postRuntimeMessage('ComputationUpdates', e))
- onPortMessage('SignalUpdates', e => postRuntimeMessage('SignalUpdates', e))
onPortMessage('SetInspectedDetails', e => postRuntimeMessage('SetInspectedDetails', e))
- onPortMessage('PropsUpdate', e => postRuntimeMessage('PropsUpdate', e))
- onPortMessage('ValueUpdate', e => postRuntimeMessage('ValueUpdate', e))
+ onPortMessage('InspectorUpdate', e => postRuntimeMessage('InspectorUpdate', e))
onPortMessage('ClientHoveredComponent', e => postRuntimeMessage('ClientHoveredComponent', e))
onPortMessage('ClientInspectedNode', e => postRuntimeMessage('ClientInspectedNode', e))
@@ -106,7 +103,10 @@ chrome.runtime.onConnect.addListener(port => {
}),
)
- addCleanup(onRuntimeMessage('ToggleInspected', e => postPortMessage('ToggleInspected', e)))
+ addCleanup(
+ onRuntimeMessage('ToggleInspectedValue', e => postPortMessage('ToggleInspectedValue', e)),
+ )
+ addCleanup(onRuntimeMessage('SetInspectedNode', e => postPortMessage('SetInspectedNode', e)))
addCleanup(onRuntimeMessage('HighlightElement', e => postPortMessage('HighlightElement', e)))
diff --git a/packages/extension/content/content.ts b/packages/extension/content/content.ts
index d32de45f..412ba428 100644
--- a/packages/extension/content/content.ts
+++ b/packages/extension/content/content.ts
@@ -2,7 +2,7 @@ import {
onWindowMessage,
postWindowMessage,
startListeningWindowMessages,
-} from '@solid-devtools/shared/bridge'
+} from 'solid-devtools/bridge'
import { warn } from '@solid-devtools/shared/utils'
import { createPortMessanger, DEVTOOLS_CONTENT_PORT } from '../shared/messanger'
@@ -18,6 +18,7 @@ startListeningWindowMessages()
const { postPortMessage, onPortMessage } = createPortMessanger(port)
onWindowMessage('SolidOnPage', clientVersion => {
+ // eslint-disable-next-line no-console
console.log(
'🚧 %csolid-devtools%c is in early development! 🚧\nPlease report any bugs to https://github.com/thetarnav/solid-devtools/issues',
'color: #fff; background: rgba(181, 111, 22, 0.7); padding: 1px 4px;',
@@ -56,13 +57,9 @@ onWindowMessage('StructureUpdate', graph => postPortMessage('StructureUpdate', g
onWindowMessage('ComputationUpdates', e => postPortMessage('ComputationUpdates', e))
-onWindowMessage('SignalUpdates', e => postPortMessage('SignalUpdates', e))
-
onWindowMessage('SetInspectedDetails', e => postPortMessage('SetInspectedDetails', e))
-onWindowMessage('PropsUpdate', e => postPortMessage('PropsUpdate', e))
-
-onWindowMessage('ValueUpdate', e => postPortMessage('ValueUpdate', e))
+onWindowMessage('InspectorUpdate', e => postPortMessage('InspectorUpdate', e))
onWindowMessage('ClientHoveredComponent', e => postPortMessage('ClientHoveredComponent', e))
@@ -73,7 +70,8 @@ onPortMessage('PanelClosed', e => postWindowMessage('PanelClosed', e))
onPortMessage('ForceUpdate', () => postWindowMessage('ForceUpdate'))
-onPortMessage('ToggleInspected', e => postWindowMessage('ToggleInspected', e))
+onPortMessage('ToggleInspectedValue', e => postWindowMessage('ToggleInspectedValue', e))
+onPortMessage('SetInspectedNode', e => postWindowMessage('SetInspectedNode', e))
onPortMessage('HighlightElement', e => postWindowMessage('HighlightElement', e))
diff --git a/packages/extension/devtools/devtools.ts b/packages/extension/devtools/devtools.ts
index 5a5dc8c9..70d177d3 100644
--- a/packages/extension/devtools/devtools.ts
+++ b/packages/extension/devtools/devtools.ts
@@ -1,6 +1,6 @@
import { createRuntimeMessanger, DEVTOOLS_CONNECTION_NAME } from '../shared/messanger'
-import { once } from '@solid-devtools/shared/bridge'
-import { log } from '@solid-devtools/shared/utils'
+import { once } from 'solid-devtools/bridge'
+import { error, log } from '@solid-devtools/shared/utils'
log('Devtools script working.')
@@ -22,8 +22,8 @@ once(onRuntimeMessage, 'SolidOnPage', async () => {
log('Panel created.')
panel.onShown.addListener(onPanelShown)
panel.onHidden.addListener(onPanelHidden)
- } catch (error) {
- console.error(error)
+ } catch (err) {
+ error(err)
}
})
diff --git a/packages/extension/package.json b/packages/extension/package.json
index 845e735b..c71e2e32 100644
--- a/packages/extension/package.json
+++ b/packages/extension/package.json
@@ -11,7 +11,7 @@
"build": "npm run clean && vite build && node scripts/zip.cjs",
"clean": "rimraf dist dist.zip",
"test": "vitest",
- "typecheck": "tsc --noEmit"
+ "typecheck": "tsc --noEmit --paths null"
},
"devDependencies": {
"@crxjs/vite-plugin": "^1.0.14",
@@ -19,18 +19,21 @@
"@types/webextension-polyfill": "^0.9.1",
"jsdom": "^20.0.1",
"rimraf": "^3.0.2",
- "solid-devtools": "workspace:^0.20.1",
- "typescript": "^4.8.4",
- "vite": "^3.1.8",
- "vite-plugin-solid": "^2.3.10",
- "vitest": "^0.23.4",
+ "vite": "^3.2.3",
"webextension-polyfill": "^0.10.0",
"zip-a-folder": "^1.1.5"
},
"dependencies": {
"@solid-devtools/frontend": "workspace:^0.0.7",
"@solid-devtools/shared": "workspace:^0.9.2",
- "@solid-primitives/utils": "^3.1.0"
+ "@solid-primitives/utils": "^3.1.0",
+ "solid-devtools": "workspace:^0.20.1",
+ "solid-js": "^1.6.2"
+ },
+ "overrides": {
+ "@crxjs/vite-plugin": {
+ "vite": "^3"
+ }
},
"packageManager": "pnpm@7.13.0"
}
diff --git a/packages/extension/shared/messanger.ts b/packages/extension/shared/messanger.ts
index e409c560..e1a344b0 100644
--- a/packages/extension/shared/messanger.ts
+++ b/packages/extension/shared/messanger.ts
@@ -1,4 +1,4 @@
-import { LOG_MESSAGES, OnMessageFn, PostMessageFn, Messages } from '@solid-devtools/shared/bridge'
+import { LOG_MESSAGES, OnMessageFn, PostMessageFn, Messages } from 'solid-devtools/bridge'
import { log } from '@solid-devtools/shared/utils'
export const DEVTOOLS_CONTENT_PORT = 'DEVTOOLS_CONTENT_PORT'
@@ -20,7 +20,7 @@ export function createPortMessanger(port: chrome.runtime.Port): {
port.onMessage.removeListener(onMessage)
})
- function onMessage(event: unknown, port: chrome.runtime.Port) {
+ function onMessage(event: unknown) {
if (!event || typeof event !== 'object') return
const e = event as Record
if (typeof e.id !== 'string') return
diff --git a/packages/extension/src/bridge.ts b/packages/extension/src/bridge.ts
index c096abe4..b1a9d27f 100644
--- a/packages/extension/src/bridge.ts
+++ b/packages/extension/src/bridge.ts
@@ -1,6 +1,6 @@
import { Controller } from '@solid-devtools/frontend'
-import { once } from '@solid-devtools/shared/bridge'
import { createRuntimeMessanger } from '../shared/messanger'
+import { once } from 'solid-devtools/bridge'
export default function createBridge({
setVersions,
@@ -25,8 +25,11 @@ export default function createBridge({
onHighlightElementChange(data) {
postRuntimeMessage('HighlightElement', data)
},
- onInspect(payloa) {
- postRuntimeMessage('ToggleInspected', payloa)
+ onInspectValue(payload) {
+ postRuntimeMessage('ToggleInspectedValue', payload)
+ },
+ onInspectNode(node) {
+ postRuntimeMessage('SetInspectedNode', node)
},
})
@@ -38,11 +41,7 @@ export default function createBridge({
onRuntimeMessage('SetInspectedDetails', controller.setInspectedDetails.bind(controller))
- onRuntimeMessage('SignalUpdates', controller.updateSignals.bind(controller))
-
- onRuntimeMessage('PropsUpdate', controller.updateProps.bind(controller))
-
- onRuntimeMessage('ValueUpdate', controller.updateValue.bind(controller))
+ onRuntimeMessage('InspectorUpdate', controller.updateInspector.bind(controller))
onRuntimeMessage('ClientLocatorMode', controller.setLocatorState.bind(controller))
diff --git a/packages/extension/vite.config.ts b/packages/extension/vite.config.ts
index 28d05291..e1980f28 100644
--- a/packages/extension/vite.config.ts
+++ b/packages/extension/vite.config.ts
@@ -14,14 +14,13 @@ const r = (str: TemplateStringsArray) => resolve(__dirname, str.join(''))
export default defineConfig({
plugins: [solidPlugin({ dev: false }), crx({ manifest })],
resolve: {
- conditions: ['browser', 'development'],
alias: {
'@solid-devtools/shared': r`../shared/src`,
},
},
define: {
// need to insert the "" quotes manually, because vite just inserts the value as-is.
- __CLIENT_VERSION__: `"${pkg.devDependencies['solid-devtools'].match(/\d+.\d+.\d+/)![0]}"`,
+ __CLIENT_VERSION__: `"${pkg.dependencies['solid-devtools'].match(/\d+.\d+.\d+/)![0]}"`,
},
build: {
emptyOutDir: false,
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 55603bdb..3dbbfe26 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -36,6 +36,7 @@
"dependencies": {
"@floating-ui/dom": "^1.0.3",
"@otonashixav/solid-flip": "^0.10.5",
+ "@solid-devtools/debugger": "workspace:^0.13.1",
"@solid-devtools/shared": "workspace:^0.9.2",
"@solid-primitives/context": "^0.1.2",
"@solid-primitives/cursor": "^0.0.101",
@@ -43,7 +44,7 @@
"@solid-primitives/event-bus": "^0.1.3",
"@solid-primitives/event-listener": "^2.2.3",
"@solid-primitives/keyboard": "^1.0.3",
- "@solid-primitives/keyed": "^1.1.3",
+ "@solid-primitives/keyed": "^1.1.4",
"@solid-primitives/media": "^2.0.3",
"@solid-primitives/props": "^2.2.2",
"@solid-primitives/range": "^0.1.3",
@@ -56,23 +57,18 @@
"@vanilla-extract/private": "^1.0.3",
"clsx": "^1.2.1",
"csstype": "^3.1.1",
- "solid-floating-ui": "^0.1.0",
- "solid-headless": "^0.12.6",
- "solid-transition-group": "^0.0.11",
- "type-fest": "^3.1.0"
+ "solid-floating-ui": "^0.2.0",
+ "solid-headless": "^0.13.0",
+ "solid-transition-group": "^0.0.12",
+ "type-fest": "^3.2.0"
},
"devDependencies": {
- "@types/node": "^18.11.3",
"@vanilla-extract/esbuild-plugin": "^2.2.0",
"autoprefixer": "^10.4.12",
- "postcss": "^8.4.18",
- "solid-js": "^1.6.0",
- "tsup": "^6.3.0",
- "typescript": "^4.8.4",
- "vitest": "^0.24.3"
+ "postcss": "^8.4.18"
},
"peerDependencies": {
- "solid-js": "^1.5.7"
+ "solid-js": "^1.6.2"
},
"packageManager": "pnpm@7.13.0"
}
diff --git a/packages/frontend/src/App.css.ts b/packages/frontend/src/App.css.ts
index 4978c595..3f594278 100644
--- a/packages/frontend/src/App.css.ts
+++ b/packages/frontend/src/App.css.ts
@@ -24,9 +24,6 @@ export const header = style({
columnGap: spacing[2],
backgroundColor: panelBg,
borderBottom: panelBorder,
-})
-
-export const h3 = style({
color: color.black,
...media({
[dark]: {
@@ -34,6 +31,7 @@ export const h3 = style({
},
}),
})
+
export const select = style({
width: spacing[7],
height: spacing[7],
diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx
index b25ba007..f937462f 100644
--- a/packages/frontend/src/App.tsx
+++ b/packages/frontend/src/App.tsx
@@ -8,7 +8,7 @@ import Structure from './modules/structure/Structure'
import { useController } from './controller'
import * as styles from './App.css'
-const SelectComponent: Component<{}> = props => {
+const SelectComponent: Component<{}> = () => {
const ctx = useController()
return (
= props => {
)
}
-const Options: Component<{}> = props => {
+const Options: Component<{}> = () => {
return (
{({ isOpen, setState }) => {
@@ -85,7 +85,7 @@ const App: Component<{ headerSubtitle?: JSX.Element }> = props => {