Skip to content

Commit

Permalink
Merge pull request #428 from preactjs/inspect-signal
Browse files Browse the repository at this point in the history
Add basic support for Signals
  • Loading branch information
marvinhagemeister authored Sep 6, 2022
2 parents 56deaf8 + 43eec35 commit 3e9765b
Show file tree
Hide file tree
Showing 19 changed files with 397 additions and 97 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@parcel/css": "^1.12.2",
"@playwright/test": "^1.25.1",
"@preact/preset-vite": "^2.3.0",
"@preact/signals": "^0.0.3",
"@preact/signals": "^1.0.1",
"@testing-library/preact": "^2.0.0",
"@types/archiver": "^5.3.1",
"@types/babel__core": "^7.1.19",
Expand Down
11 changes: 9 additions & 2 deletions src/adapter/adapter/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ export interface RawFilterState {
dom: boolean;
hoc?: boolean;
root?: boolean;
textSignal?: boolean;
};
}

export type TypeFilterValue = "dom" | "fragment" | "hoc" | "root";
export type TypeFilterValue =
| "dom"
| "fragment"
| "hoc"
| "root"
| "textSignal";

export interface FilterState {
regex: RegExp[];
Expand All @@ -31,7 +37,7 @@ export type Filter = RegexFilter | TypeFilter;

export const DEFAULT_FIlTERS: FilterState = {
regex: [],
type: new Set(["dom", "fragment", "root", "hoc"]),
type: new Set(["dom", "fragment", "root", "hoc", "textSignal"]),
};

export function parseFilters(raw: RawFilterState): FilterState {
Expand All @@ -40,6 +46,7 @@ export function parseFilters(raw: RawFilterState): FilterState {
if (raw.type.dom) type.add("dom");
if (raw.type.hoc) type.add("hoc");
if (raw.type.root) type.add("root");
if (raw.type.textSignal) type.add("textSignal");

return {
regex: raw.regex.filter(x => x.enabled).map(x => new RegExp(x.value, "gi")),
Expand Down
9 changes: 7 additions & 2 deletions src/adapter/shared/inspectVNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { cleanContext, cleanProps, serialize } from "./serialize";
import { PreactBindings, SharedVNode } from "./bindings";
import { InspectData } from "../adapter/adapter";
import { ID } from "../../view/store/types";
import { getSignalTextName } from "./utils";

/**
* Serialize all properties/attributes of a `VNode` like `props`, `context`,
Expand Down Expand Up @@ -33,7 +34,11 @@ export function inspectVNode<T extends SharedVNode>(
c.state != null &&
Object.keys(c.state).length > 0;

const hasHooks = c != null && bindings.getComponentHooks(vnode) != null;
const isSignalTextNode =
typeof vnode.type === "function" && vnode.type.displayName === "_st";

const hasHooks =
c != null && !isSignalTextNode && bindings.getComponentHooks(vnode) != null;
const hooks =
supportsHooks && hasHooks
? inspectHooks(config, options, vnode, bindings)
Expand Down Expand Up @@ -69,7 +74,7 @@ export function inspectVNode<T extends SharedVNode>(
key: vnode.key || null,
hooks: supportsHooks ? hooks : !supportsHooks && hasHooks ? [] : null,
id,
name: bindings.getDisplayName(vnode, config),
name: getSignalTextName(bindings.getDisplayName(vnode, config)),
props,
state,
// TODO: We're not using this information anywhere yet
Expand Down
45 changes: 45 additions & 0 deletions src/adapter/shared/serialize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { RendererConfig } from "./renderer";
import type { ObjPath } from "../renderer";
import type { PreactBindings, SharedVNode } from "./bindings";
import type { Signal } from "@preact/signals";

export interface SerializedVNode {
type: "vnode";
Expand All @@ -22,6 +23,19 @@ export function serializeVNode<T extends SharedVNode>(
return null;
}

export function isSignal(x: any): x is Signal {
return (
x !== null &&
typeof x === "object" &&
typeof x.peek === "function" &&
"value" in x
);
}

export function isReadOnlySignal(signal: Signal): boolean {
return (signal as any)._r === true;
}

export function jsonify(
data: any,
getVNode: (x: any) => SerializedVNode | null,
Expand All @@ -46,6 +60,14 @@ export function jsonify(
const vnode = getVNode(data);
if (vnode != null) return vnode;

if (isSignal(data)) {
return {
type: "signal",
name: isReadOnlySignal(data) ? "computed Signal" : "Signal",
value: jsonify(data.peek(), getVNode, seen),
};
}

if (Array.isArray(data)) {
return data.map(x => jsonify(x, getVNode, seen));
}
Expand Down Expand Up @@ -134,6 +156,11 @@ export function setInCopy<T = any>(
): T {
if (idx >= path.length) return value;

// Signals bypass everything
if (path[path.length - 1] === "value" && maybeSetSignal(obj, path, value)) {
return obj;
}

const updated = clone(obj);
if (obj instanceof Set) {
const oldValue = Array.from(obj)[+path[idx]];
Expand Down Expand Up @@ -179,6 +206,24 @@ export function setIn(obj: Record<string, any>, path: ObjPath, value: any) {
}
}

export function maybeSetSignal(
obj: Record<string, any>,
path: ObjPath,
value: any,
) {
let current: any = obj;
for (let i = 0; i < path.length; i++) {
if (isSignal(current)) {
current.value = value;
return true;
}

current = current[path[i]];
}

return false;
}

export function hasIn(obj: Record<string, any>, path: ObjPath) {
let item = obj;
for (let i = 0; i < path.length; i++) {
Expand Down
16 changes: 12 additions & 4 deletions src/adapter/shared/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getDiffType, recordComponentStats } from "./stats";
import { measureUpdate } from "../adapter/highlightUpdates";
import { PreactBindings, SharedVNode } from "./bindings";
import { VNodeTimings } from "./timings";
import { getSignalTextName } from "./utils";

function getHocName(name: string) {
const idx = name.indexOf("(");
Expand Down Expand Up @@ -147,16 +148,23 @@ export function shouldFilter<T extends SharedVNode>(
return false;
} else if (bindings.isElement(vnode) && filters.type.has("dom")) {
return true;
} else if (filters.type.has("hoc")) {
}

if (filters.type.has("hoc")) {
const name = bindings.getDisplayName(vnode, config);

if (name.indexOf("(") > -1 && !name.startsWith("ForwardRef")) {
return true;
}
}

if (filters.type.has("textSignal")) {
const name = getSignalTextName(bindings.getDisplayName(vnode, config));
if (name === "__TextSignal") return true;
}

if (filters.regex.length > 0) {
const name = bindings.getDisplayName(vnode, config);
const name = getSignalTextName(bindings.getDisplayName(vnode, config));
return filters.regex.some(r => {
// Regexes with a global flag are stateful in JS :((
r.lastIndex = 0;
Expand Down Expand Up @@ -202,7 +210,7 @@ function mount<T extends SharedVNode>(
const root = bindings.isRoot(vnode, config);

const skip = shouldFilter(vnode, filters, config, bindings);
let name = bindings.getDisplayName(vnode, config);
let name = getSignalTextName(bindings.getDisplayName(vnode, config));

if (filters.type.has("hoc")) {
const hocName = getHocName(name);
Expand Down Expand Up @@ -433,7 +441,7 @@ function update<T extends SharedVNode>(
return;
}

const name = bindings.getDisplayName(vnode, config);
const name = getSignalTextName(bindings.getDisplayName(vnode, config));
if (filters.type.has("hoc")) {
const res = detectHocs(commit, name, id, hocs);
hocs = res.hocs;
Expand Down
72 changes: 0 additions & 72 deletions src/adapter/shared/update.ts

This file was deleted.

4 changes: 4 additions & 0 deletions src/adapter/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { PreactBindings, SharedVNode } from "./bindings";

export function getSignalTextName(name: string) {
return name === "_st" ? "__TextSignal" : name;
}

export function traverse<T extends SharedVNode>(
vnode: T,
fn: (vnode: T) => void,
Expand Down
2 changes: 2 additions & 0 deletions src/view/components/DataInput/parseValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export function genPreview(v: any): string {
return `Set(${v.entries.length}) ${truncate(genPreview(v.entries))})`;
} else if (v.type === "map") {
return `Map(${v.entries.length}) ${truncate(genPreview(v.entries))})`;
} else if (v.type === "signal") {
return ${v.name} (${truncate(genPreview(v.value))})`;
}

if (Array.isArray(v)) {
Expand Down
9 changes: 9 additions & 0 deletions src/view/components/elements/TreeBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export function FilterPopup() {
);
const [filterHoc, setFilterHoc] = useState(store.filter.filterHoc.value);
const [filterRoot, setFilterRoot] = useState(store.filter.filterRoot.value);
const [filterTextSignal, setFilterTextSignal] = useState(
store.filter.filterTextSignal.value,
);
const [filters, setFilters] = useState(store.filter.filters.value);

return (
Expand All @@ -153,6 +156,7 @@ export function FilterPopup() {
store.filter.filterFragment.value = filterFragment;
store.filter.filterRoot.value = filterRoot;
store.filter.filterHoc.value = filterHoc;
store.filter.filterTextSignal.value = filterTextSignal;

store.filter.filters.value = filters;

Expand Down Expand Up @@ -180,6 +184,11 @@ export function FilterPopup() {
onInput={checked => setFilterDom(checked)}
checked={filterDom}
/>
<FilterCheck
label="Text Signal nodes"
onInput={checked => setFilterTextSignal(checked)}
checked={filterTextSignal}
/>
{/* Custom user filters */}
{filters.map((x, i) => {
return (
Expand Down
Loading

0 comments on commit 3e9765b

Please sign in to comment.